feat(mcp): suspend/resume the agent on long-running UiPath jobs (uipath.com/job)#906
Open
edis-uipath wants to merge 3 commits into
Open
feat(mcp): suspend/resume the agent on long-running UiPath jobs (uipath.com/job)#906edis-uipath wants to merge 3 commits into
edis-uipath wants to merge 3 commits into
Conversation
Upgrades the `mcp` SDK from 1.26.0 to the latest stable 1.27.2, whose LATEST_PROTOCOL_VERSION is "2025-11-25". The MCP client negotiates the SDK's latest protocol version on initialize, so this is what lets UiPath MCP servers offer tasks to the agent (prerequisite for suspend-on-UiPath-task). The forked streamable-HTTP transport (which adds UiPath's session-id save/provide layer, needed so a suspended agent resumes onto the same MCP session) is kept as-is: the official client/streamable_http.py is byte-identical between 1.26.0 and 1.27.2, so there is nothing to re-sync, and the official client still offers no way to provide an initial session id through its public entrypoint. Adds a guard test that LATEST_PROTOCOL_VERSION is 2025-11-25, the task result types (CreateTaskResult / GetTaskResult / TaskMetadata) are importable, and Result exposes _meta. Full test suite green (2081 passed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Removes tests/agent/tools/test_mcp/test_protocol_version.py. It pinned task-type imports (CreateTaskResult / TaskMetadata) and the 2025-11-25 constant — assumptions the long-running-job design has moved away from: the chosen _meta-based contract doesn't use the SDK task types (which 2.x removes anyway). The version bump stands on the rest of the mcp suite. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds client-side support for the uipath.com/job MCP contract so long-running, job-backed tools can suspend a LangGraph agent and later resume with the final (FETCH) result, instead of blocking on the START call. This extends the MCP tooling layer (McpClient + tool wrapper) and introduces a LangGraph-specific job executor, with tests and docs.
Changes:
- Add job advertisement parsing (
initialize_meta) and request_metaplumbing (call_tool(..., meta=...)) toMcpClient. - Route job-aware tool invocations through a new
LangGraphJobExecutorthat uses@durable_interrupt+WaitJobRawto suspend/resume, then FETCH. - Add a dedicated
test_mcp_jobs.pysuite and update existing MCP tool tests/docs; bumpuipath-platformminimum version.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/agent/tools/test_mcp/test_mcp_tool.py | Updates mocks to include is_job_aware so tool wrapper tests remain valid. |
| tests/agent/tools/test_mcp/test_mcp_jobs.py | New tests for advertisement parsing, START/FETCH _meta, and executor suspend/resume behavior. |
| tests/agent/tools/test_mcp/claude.md | Updates test documentation to include the new jobs test suite and counts. |
| src/uipath_langchain/agent/tools/mcp/mcp_tool.py | Adds job-aware invocation path, _meta START/FETCH handling, and shared result normalization. |
| src/uipath_langchain/agent/tools/mcp/mcp_client.py | Adds job negotiation state, InitializeResult handling, and call_tool(meta=...) support. |
| src/uipath_langchain/agent/tools/mcp/job_executor.py | Introduces LangGraphJobExecutor implementing suspend/resume + FETCH for job-backed tools. |
| src/uipath_langchain/agent/tools/mcp/claude.md | Documents the uipath.com/job flow, responsibilities, and invariants. |
| src/uipath_langchain/agent/tools/mcp/init.py | Exports LangGraphJobExecutor as part of the MCP tools package API. |
| pyproject.toml | Bumps uipath-platform floor to >=0.1.65 for the new uipath.platform.mcp_jobs dependency. |
Comment on lines
+264
to
+268
| version = read_job_version(init_result.meta) | ||
| if version is not None: | ||
| self._job_aware = True | ||
| self._job_version = version | ||
| logger.info(f"MCP server advertised uipath.com/job v{version}") |
Comment on lines
236
to
246
| existing_session_id = ( | ||
| await self._session_info.get_session_id() if self._session_info else None | ||
| ) | ||
| logger.info( | ||
| f"Initializing MCP session (session_info id: {existing_session_id})" | ||
| ) | ||
|
|
||
| if existing_session_id is None: | ||
| await self._session.initialize() | ||
| init_result = await self._session.initialize() | ||
| self._apply_job_advertisement(init_result) | ||
|
|
Comment on lines
8
to
11
| "uipath>=2.10.79, <2.11.0", | ||
| "uipath-core>=0.5.17, <0.6.0", | ||
| "uipath-platform>=0.1.61, <0.2.0", | ||
| "uipath-platform>=0.1.65, <0.2.0", | ||
| "uipath-runtime>=0.11.0, <0.12.0", |
…meta
Implements Option 6 of the long-running-jobs design (mcp-longrunning-jobs-design.md):
a UiPath MCP server that backs a tool with an Orchestrator job advertises
`uipath.com/job` on initialize; the client then opts in per call and suspends the
LangGraph agent while the job runs, resuming with the result — all over MCP `_meta`,
so deployed agents gain the behavior on package upgrade with no agent.json change.
Client core (C1):
- `McpClient`: read the `InitializeResult._meta["uipath.com/job"]` advertisement
(`_apply_job_advertisement` → `is_job_aware`/`job_version`), and thread request
`_meta` through `call_tool(..., meta=)`.
- `mcp_tool._invoke_job_aware`: on a job-aware session, send the START `_meta`
(`{version}`) on every call; if the server returns a `{key, folderKey}` handle,
delegate to the injected `McpJobExecutor` with neutral `start`/`fetch` closures
(FETCH re-calls the tool with the handle `_meta`). Non-advertising/old servers
keep today's plain blocking path (back-compat).
LangGraph executor (C2):
- `LangGraphJobExecutor` (default for `create_mcp_tools_and_clients`): starts the
job inside `@durable_interrupt`, interrupts with `WaitJobRaw` (JOB/JOB_RAW
trigger), and on resume re-derives the handle from the terminal `Job` and FETCHes
the server-formatted result. `interrupt` is confined here; the neutral core lives
in `uipath.platform.mcp_jobs` (uipath-python). Non-job results resolve via
`SkipInterruptValue` to keep the durable index aligned without suspending.
Drops the fragile Option-10 notification-string bridge: advertisement-gating alone
gives back-compat (old server → no advertisement → plain path) and the free gain on
central server upgrade.
Tests: 9 new (advertisement, START/FETCH `_meta`, executor suspend/resume/faulted);
full test_mcp suite green (232 incl. circular-import guard); ruff + mypy clean.
Note: stacked on uipath-python's unreleased `uipath-platform>=0.1.65` (the
`mcp_jobs` neutral core). Floor bumped; uv.lock to be regenerated and CI goes green
once that release publishes. Developed against an editable local link (reverted).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
326ca2a to
9e3a3b1
Compare
edis-uipath
added a commit
that referenced
this pull request
Jun 15, 2026
…th.com/job), self-contained Implements Option 6 of the long-running-jobs design entirely within uipath-langchain, with NO change to base uipath-python. A UiPath MCP server that backs a tool with an Orchestrator job advertises `uipath.com/job` on initialize; the client opts in per call and suspends the LangGraph agent while the job runs, resuming with the result — all over MCP `_meta`, so deployed agents gain the behavior on package upgrade with no agent.json change. Why self-contained (vs the base-SDK core in uipath-python #1717): base uipath-platform has zero MCP-protocol code today (only WaitJob*/CreateTask* wait-models and McpService CRUD). All MCP protocol code and every framework tool (process_tool, escalation_tool) already live here; an McpJobExecutor is the same kind of glue. This keeps base generic, removes the cross-package release coupling, and the base already provides the generic durable primitive we build on (WaitJobRaw + jobs service). See the §3b placement debate in mcp-longrunning-jobs-design.md. - `jobs.py` (new): the `uipath.com/job` `_meta` helpers + `UiPathJobHandle`, `JobStart`, `McpJobExecutor` Protocol, `BlockingJobExecutor` — framework- and MCP-SDK-neutral (plain dicts; imports only the already-published `Job`/`JobState`/jobs service). - `mcp_client.py`: read the initialize advertisement (`is_job_aware`/`job_version`), thread request `_meta` through `call_tool(..., meta=)`. - `mcp_tool.py`: `_invoke_job_aware` sends START `_meta`, parses the handle, delegates to the injected `McpJobExecutor` with neutral `start`/`fetch` closures. - `job_executor.py`: `LangGraphJobExecutor` (default) — START inside `@durable_interrupt`, interrupt with `WaitJobRaw`, on resume re-derive the handle and FETCH. `interrupt` is confined here. No `uipath-platform` floor bump, no #1717 dependency → CI is green standalone. This is an alternative to #906 (which stacks on #1717); pick one. Tests: 9 new (advertisement, START/FETCH `_meta`, executor suspend/resume/faulted); full test_mcp suite green (233 incl. circular-import guard); ruff + mypy clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ccb95d3 to
9212b63
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Suspend/resume the agent on long-running UiPath jobs (
uipath.com/job)Implements Option 6 of the design on the client side: when a UiPath MCP server backs a tool with an Orchestrator job, the agent suspends while the job runs and resumes with its result instead of blocking — negotiated entirely through MCP
_meta, so a deployed agent gains the behavior on package upgrade with noagent.jsonchange.Flow (single key
uipath.com/job)initializea job-capable server returnsInitializeResult._meta["uipath.com/job"] = {version:1};McpClientrecordsis_job_aware/job_version(re-read on every freshinitialize, so it's stable across a production suspend/resume).params._meta["uipath.com/job"]={version:N}ontools/call; a job-backed tool returns an immediateresult._metahandle{key, folderKey}.LangGraphJobExecutorinterrupts withWaitJobRawinside@durable_interrupt(same mechanism asprocess_tool); the runtime persists a JOB/JOB_RAWresume trigger and Orchestrator resumes the agent when the child job is terminal.{key, folderKey}from the terminalJoband re-calls the tool with the FETCH_meta; the server returns the formatted result — that's the tool's output.interruptis confined toLangGraphJobExecutor; the neutral_meta/executor core lives inuipath.platform.mcp_jobs(uipath-python). Non-job tools on a job-aware server resolve viaSkipInterruptValue(no real suspend, durable index stays aligned). Old/non-UiPath servers never advertise → today's blocking path (back-compat).Drops the fragile Option-10 notification-string bridge: advertisement-gating alone gives back-compat and the free gain on central server upgrade.
Testing
test_mcp_jobs.py: advertisement, START/FETCH_meta, executor suspend/resume/faulted); fulltest_mcpsuite green (232 incl. circular-import guard);ruff+mypyclean.Stacked / dependency
chore/upgrade-mcp-1.27.2(chore(mcp): upgrade mcp client to 1.27.2 (2025-11-25 protocol) #904).uipath.platform.mcp_jobscore). Theuipath-platformfloor is bumped to>=0.1.65;uv.lockwill be regenerated and CI goes green once that release publishes. (Developed against an editable local link, reverted.)🤖 Generated with Claude Code