Skip to content

fix(mcp): don't fail tool calls without an MCP session (#18391) - #18417

Open
salimkt wants to merge 1 commit into
PaddlePaddle:mainfrom
salimkt:fix/mcp-optional-request-context
Open

salimkt wants to merge 1 commit into
PaddlePaddle:mainfrom
salimkt:fix/mcp-optional-request-context

Conversation

@salimkt

@salimkt salimkt commented Oct 26, 2026

Copy link
Copy Markdown

What

Task._invoke_tool() (shared by ocr, pp_structurev3, paddleocr_vl, …) sent an informational notification with:

ctx = get_context()
await ctx.info(...)

With FastMCP 4.x, Context.info() goes through Context.session, which raises when the MCP session is not established (request_context is None):

RuntimeError: session is not available because the MCP session has not been established yet. Check `context.request_context` for None before accessing this attribute.

and get_context() raises No active context found. when the tool is called outside a request. Because the server uses mask_error_details=True, clients only see Error calling tool 'pp_structurev3'.

I reproduced both errors in-process with fastmcp 4.1.4 / mcp 2.3.0 by calling the pp_structurev3 tool (stub inference) under a Context without a request context, and without any context.

Changes

  • New _notify_client() helper in tasks/base.py: gets the context and probes ctx.session. If either raises RuntimeError, the notification is skipped instead of failing the tool. This still notifies in FastMCP's background-task mode, where the session is stored on the context but request_context is None.
  • mcp_server/pyproject.toml: mcp>=1.5.0 / fastmcp>=2.0.0 → mcp>=2.0.0,<3.0.0 / fastmcp>=4.0.0,<5.0.0. Several fastmcp releases (2.10.0, 2.14.0) declare mcp without an upper bound, and fastmcp 2.x/3.x import names that mcp 2.x renamed, so the old bounds allowed combinations that fail at import. The new bounds match what a fresh resolve already installs (fastmcp 4.1.4 + mcp 2.3.0).
  • MCP docs badge: "FastMCP v2" → "FastMCP v4".

If you would rather keep supporting the fastmcp 2.x / mcp 1.x line, I can change this to fastmcp>=2.14,<3 + mcp>=1.24,<2 instead. The code change works on both.

Tests

tests/unit/test_mcp_task_context.py calls the pp_structurev3 tool:

  1. with no FastMCP context at all,
  2. inside a Context whose request_context is None,
  3. through an in-memory fastmcp.Client, asserting the info notification is still delivered.

(1) and (2) fail on main, and all three pass with the fix on fastmcp 4.0.0 + mcp 2.0.0 (the lower bounds) and on fastmcp 4.1.4 + mcp 2.3.0. python -m paddleocr_mcp --help starts on both. As with PR 1, the module skips when the MCP deps are not installed.

Fixes #18391

🤖 Generated with Claude Code

…18391)

Task._invoke_tool() sent an info notification through get_context()
unconditionally. Without an active context, or with a context whose MCP
session is not established, FastMCP raises and the tool call fails with
"session is not available because the MCP session has not been
established yet". The notification is now best-effort.

Also constrain the dependencies to a combination that imports and runs:
fastmcp>=4.0.0,<5.0.0 with mcp>=2.0.0,<3.0.0. The previous open-ended
bounds allowed older fastmcp releases (e.g. 2.14.x) next to mcp 2.x,
which fails at import time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

paddleocr-mcp 0.8.5: pp_structurev3 tool always fails (unguarded context.request_context); fastmcp/mcp bounds unsatisfiable

2 participants