Skip to content

feat(mcp): long-running UiPath jobs over MCP (uipath.com/job) — self-contained, no uipath-python dep#907

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

feat(mcp): long-running UiPath jobs over MCP (uipath.com/job) — self-contained, no uipath-python dep#907
edis-uipath wants to merge 3 commits into
chore/upgrade-mcp-1.27.2from
feat/mcp-uipath-jobs-standalone

Conversation

@edis-uipath

Copy link
Copy Markdown
Contributor

Suspend/resume the agent on long-running UiPath jobs (uipath.com/job) — self-contained

Implements Option 6 of the 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 a deployed agent gains the behavior on package upgrade with no agent.json change.

This is the self-contained alternative to #906 (which stacks on the base-SDK core in uipath-python #1717). Pick one. This PR has no uipath-platform floor bump and no #1717 dependency, so CI is green standalone.

Why self-contained

Base uipath-platform has zero MCP-protocol code today — only the generic WaitJob/WaitJobRaw/CreateTask/WaitEscalation wait-models and McpService (MCP-server-as-resource CRUD). All MCP protocol code (the streamable_http/mcp_client/mcp_tool fork) and every framework tool (process_tool, escalation_tool, context_tool) already live in this package and consume a generic base wait-model. An McpJobExecutor is the same kind of glue, and the generic durable primitive it builds on (WaitJobRaw + the jobs service) already ships in base. Putting MCP _meta semantics in base would be the first MCP-protocol code there — against the grain — and would add cross-package release coupling. (Full reasoning: §3b "placement debate" in the design doc; uipath-agents already imports the MCP layer from this package, not from base.)

What's here

  • 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: reads the initialize advertisement (is_job_aware/job_version); threads 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; the neutral core never imports langgraph.

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).

Testing

  • 9 new tests (test_mcp_jobs.py); full test_mcp suite green (233 incl. circular-import guard); ruff + mypy clean.

Base: chore/upgrade-mcp-1.27.2 (#904). Pairs with AgentHubService #1332 (server side).

🤖 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 07:21

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 long-running UiPath Orchestrator jobs surfaced via MCP tools (uipath.com/job) by negotiating capability on initialize, sending START/FETCH _meta, and (by default) suspending/resuming a LangGraph agent instead of blocking.

Changes:

  • Introduces a framework-neutral uipath.com/job _meta contract layer (jobs.py) plus a polling fallback executor.
  • Extends McpClient to parse job capability advertisement and to thread request _meta through call_tool(..., meta=...).
  • Routes tool invocations through an injected job executor when the server advertises job support; adds a LangGraph suspend/resume executor and corresponding tests/docs.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/agent/tools/test_mcp/test_mcp_tool.py Updates existing MCP tool factory tests to account for job-awareness gating.
tests/agent/tools/test_mcp/test_mcp_jobs.py Adds new test coverage for advertisement parsing, START/FETCH _meta, and LangGraph executor behavior.
tests/agent/tools/test_mcp/claude.md Updates test inventory documentation to include new job tests.
src/uipath_langchain/agent/tools/mcp/mcp_tool.py Adds job-aware invocation path, START/FETCH meta wiring, and executor injection/defaulting.
src/uipath_langchain/agent/tools/mcp/mcp_client.py Parses initialize job advertisement and adds meta passthrough on call_tool.
src/uipath_langchain/agent/tools/mcp/jobs.py New framework-neutral job _meta helpers and a blocking (polling) executor.
src/uipath_langchain/agent/tools/mcp/job_executor.py New LangGraph-specific executor that suspends on WaitJobRaw and fetches on resume.
src/uipath_langchain/agent/tools/mcp/claude.md Documents the uipath.com/job flow and where each piece lives.
src/uipath_langchain/agent/tools/mcp/init.py Exposes new job-related public exports.

Comment on lines +265 to +269
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 +195 to +199
async def _wait_until_terminal(self, handle: UiPathJobHandle) -> None:
jobs = self._jobs_service()
loop = asyncio.get_event_loop()
deadline = None if self._timeout is None else loop.time() + self._timeout
while True:
…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 feat/mcp-uipath-jobs-standalone branch from 7133dc6 to a046bbc Compare June 15, 2026 09:14
@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