Skip to content

feat(mcp): suspend/resume the agent on long-running UiPath jobs (uipath.com/job)#906

Open
edis-uipath wants to merge 3 commits into
chore/upgrade-mcp-1.27.2from
feat/mcp-uipath-jobs
Open

feat(mcp): suspend/resume the agent on long-running UiPath jobs (uipath.com/job)#906
edis-uipath wants to merge 3 commits into
chore/upgrade-mcp-1.27.2from
feat/mcp-uipath-jobs

Conversation

@edis-uipath

Copy link
Copy Markdown
Contributor

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 no agent.json change.

Flow (single key uipath.com/job)

  1. Advertise — on initialize a job-capable server returns InitializeResult._meta["uipath.com/job"] = {version:1}; McpClient records is_job_aware/job_version (re-read on every fresh initialize, so it's stable across a production suspend/resume).
  2. START — for a job-aware session the wrapper sends params._meta["uipath.com/job"]={version:N} on tools/call; a job-backed tool returns an immediate result._meta handle {key, folderKey}.
  3. Suspend — the default LangGraphJobExecutor interrupts with WaitJobRaw inside @durable_interrupt (same mechanism as process_tool); the runtime persists a JOB/JOB_RAW resume trigger and Orchestrator resumes the agent when the child job is terminal.
  4. FETCH — on resume it re-derives {key, folderKey} from the terminal Job and re-calls the tool with the FETCH _meta; the server returns the formatted result — that's the tool's output.

interrupt is confined to LangGraphJobExecutor; the neutral _meta/executor core lives in uipath.platform.mcp_jobs (uipath-python). Non-job tools on a job-aware server resolve via SkipInterruptValue (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

  • 9 new tests (test_mcp_jobs.py: advertisement, START/FETCH _meta, executor suspend/resume/faulted); full test_mcp suite green (232 incl. circular-import guard); ruff + mypy clean.

Stacked / dependency

  • Base: chore/upgrade-mcp-1.27.2 (chore(mcp): upgrade mcp client to 1.27.2 (2025-11-25 protocol) #904).
  • Needs uipath-python#1717 (the uipath.platform.mcp_jobs core). The uipath-platform floor is bumped to >=0.1.65; uv.lock will be regenerated and CI goes green once that release publishes. (Developed against an editable local link, reverted.)

🤖 Generated with Claude Code

edis-uipath and others added 2 commits June 14, 2026 10:27
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>
Copilot AI review requested due to automatic review settings June 15, 2026 05:19

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 _meta plumbing (call_tool(..., meta=...)) to McpClient.
  • Route job-aware tool invocations through a new LangGraphJobExecutor that uses @durable_interrupt + WaitJobRaw to suspend/resume, then FETCH.
  • Add a dedicated test_mcp_jobs.py suite and update existing MCP tool tests/docs; bump uipath-platform minimum 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 thread pyproject.toml
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>
@edis-uipath edis-uipath force-pushed the feat/mcp-uipath-jobs branch from 326ca2a to 9e3a3b1 Compare June 15, 2026 09:14
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>
@edis-uipath edis-uipath force-pushed the chore/upgrade-mcp-1.27.2 branch from ccb95d3 to 9212b63 Compare June 16, 2026 10:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants