Skip to content

fix: FastMCP 4 denials + dual-line MCP CI - #542

Open
aimable100 wants to merge 3 commits into
mainfrom
fix/fastmcp4-compat
Open

fix: FastMCP 4 denials + dual-line MCP CI#542
aimable100 wants to merge 3 commits into
mainfrom
fix/fastmcp4-compat

Conversation

@aimable100

Copy link
Copy Markdown
Collaborator

Summary

  • Fix FastMCP 4 middleware denials: return ToolResult(is_error=True) on 4.x; keep the FastMCP 3 duck-typed to_mcp_result() wrapper.
  • Split mcp-smoke into FastMCP 3 (mcp 1.x) and FastMCP 4 (mcp 2.x) CI jobs with major-version install gates so CrewAI’s MCP 1.x pin cannot hide regressions.
  • Expand integration smoke tests, weekly compatibility matrix (FastAPI / Google ADK / MCP / FastMCP), upstream release monitoring, and FastAPI trusted-roots test isolation.

Test plan

  • CI MCP Smoke (FastMCP 3) and MCP Smoke (FastMCP 4) both green
  • test_denial_return_matches_fastmcp_toolresult_contract passes on both lines
  • FastMCP middleware + e2e suites pass under FastMCP 4.0.1 / mcp 2.1.1
  • Same suites still pass under FastMCP 3.2.1 / mcp 1.x
  • Confirm main matrix still installs CrewAI without forcing MCP 2 into that env

FastMCP 4 requires ToolResult(is_error=True) on deny paths; keep the
FastMCP 3 duck-typed wrapper. Add FastMCP 3/4 smoke jobs with major
gates, expand integration smoke/matrix coverage, and isolate FastAPI
config tests.
Runtime
- Denials are a real ToolResult subclass on every FastMCP line: FastMCP 4
  only normalizes ToolResult returns, and FastMCP's bundled caching /
  response-limiting middleware read .content / .structured_content
  directly. to_mcp_result() always emits isError=True; is_error is set
  where ToolResult has it (>= 3.4). Replaces the duck-typed wrapper and
  the per-call try/except TypeError probe with one import-time constant.
- Merge request_context.meta under params.meta whenever params.meta lacks
  "tenuo". FastMCP 4 stamps only _meta.fastmcp.version on the middleware
  params for version-pinned calls, which previously hid the warrant and
  denied the call.
- Add _compat.tool_input_schema() (SDK 2.x input_schema / 1.x inputSchema,
  dict-only) and use it in both the client and the LangChain bridge. The
  previous attribute-order change stripped every argument for duck-typed
  tools and left the LangChain bridge reading an empty schema on MCP 2.x.

Tests
- Contract test asserts the ToolResult shape on all lines; new test for
  the version-pinned meta merge; restore trailing newline (ruff W292);
  print installed versions from distribution metadata only (no namespace
  package AttributeError).

CI
- Compatibility matrix: build a wheel instead of `maturin develop` (which
  needs an activated venv), add rust-cache, and replace the mis-indented
  heredoc with scripts/check_installed_majors.py, shared with the
  mcp-smoke lanes.
- mcp-smoke: FastMCP 3 lane installs latest 3.x (the 3.2.1 floor runs in
  the weekly matrix); share one rust-cache across legs; single pytest run.
- test_mcp_fastmcp_middleware: import tenuo.mcp._compat after the
  importorskip guards (it loads mcp.types); Python 3.9 cells no longer
  error at collection.
- Compatibility matrix minimum lanes: pin httpx<0.28 for fastapi 0.100 /
  starlette 0.27 (TestClient still passes app=), and install the floors
  the code actually needs instead of 1.0/1.1.
- Raise declared floors to match reality: tenuo[mcp]/tenuo[fastmcp] need
  mcp>=1.9.4 (streamable-HTTP transport yields the get_session_id triple;
  CallToolRequestParams.meta), tenuo[crewai] needs crewai>=1.5
  (crewai.hooks). Probed 1.6.0-1.12.0 and 1.1.0-1.8.0 respectively.
- Fix the stale "requires crewai>=0.80.0" message in tenuo.crewai and the
  CrewAI examples; update docs/compatibility-matrix.md and CHANGELOG.
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.

1 participant