Skip to content

v2.0.1 — support FastMCP Code Mode (nested tool calls and listings) - #43

Merged
naji247 merged 2 commits into
mainfrom
code-mode-support
Aug 6, 2026
Merged

v2.0.1 — support FastMCP Code Mode (nested tool calls and listings)#43
naji247 merged 2 commits into
mainfrom
code-mode-support

Conversation

@naji247

@naji247 naji247 commented Aug 6, 2026

Copy link
Copy Markdown
Member

What

Patch release making AgentCat's community adapter fully compatible with FastMCP Code Mode and, more generally, with any server that calls itself mid-request (catalog meta-tools, tools composing sibling tools).

The bug

Code Mode's meta-tools fetch the real catalog mid-call via CatalogTransform.get_tool_catalog, which lists with run_middleware=True. That transform-bypassed backend listing re-entered on_list_tools, which rebuilt the injection registries from it — replacing the agent-facing registry wholesale. Two visible consequences on any Code Mode server:

  1. Hard breakage: after the first execute call, the strip no longer removed the injected session_id/context arguments, so an agent echoing the handle it was just issued (as the mint-back instructs) failed FastMCP validation with Unexpected keyword argument 'session_id' on every subsequent call.
  2. The outer call's structured _mcp_instructions mirror was silently dropped.

Independently, inner sandbox calls each minted their own session (one logical execute fragmented across 2+ sessions) and their results carried mint-back decoration into the sandbox, where generated code trips over the extra key.

The fix

The adapter now installs a re-entrancy frame for the duration of each on_call_tool, carried in FastMCP's request-scoped context state (shared by reference with every nested context; frames are scoped by server identity so mounted/multi-server setups stay isolated). While a frame is open on the same server:

  • Nested tools/list → served verbatim: no injection, no registry writes. Agent-facing registries survive; sandbox-facing catalogs stay clean.
  • Nested tools/call → still publishes its event (inner-call visibility inside Code Mode is a feature), but joins the enclosing call's session, carries a new presence-gated agentcat_nested: "true" tag (same pattern as agentcat_mrtr; agentcat_session_id_source keeps the outer call's value), and is never decorated.

Tests

tests/test_utils/community_catalog_server.py reproduces the Code Mode mechanism without pydantic-monty (a CatalogTransform meta-tool that fetches the catalog and drives hidden tools). 10 regression tests on the fastmcp 3.x leg + a 4.x sibling; 9 of them fail against the pre-fix adapter, reproducing the exact production symptom. Full suites green on all three dependency legs (mcp 1.x + fastmcp 3.4.5: 878; mcp 2.x + fastmcp 4.0.0b1: 740; no-fastmcp: 760), and verified end to end over streamable HTTP against a live Code Mode server with events confirmed in the dashboard.

Notes

  • AGENTCAT_TAG_NESTED should be mirrored into the TypeScript SDK at the next cross-SDK sync.
  • uv.lock is stale (predates the 2.0.0 metadata) and is deliberately left for a separate housekeeping PR.

The community adapter now distinguishes agent-facing traffic from calls a
server makes to itself — a Code Mode sandbox chaining tools through
call_tool, a catalog meta-tool fetching the hidden catalog, one tool
composing another:

- A nested tools/list is served verbatim. Previously it rebuilt the
  injection registries from the raw backend catalog, after which the strip
  no longer removed the injected session_id/context arguments — so an agent
  echoing the handle it was just issued failed FastMCP validation with
  "Unexpected keyword argument" on its next call, and the outer call's
  structured mint-back mirror was silently dropped.
- A nested tools/call still publishes its event, but joins the enclosing
  call's session (one logical call, one session, instead of a fresh mint
  per inner call), carries the new presence-gated agentcat_nested tag, and
  is never decorated — mint-back blocks no longer leak into sandbox-visible
  results where generated code trips over them.

The re-entrancy marker rides FastMCP's request-scoped context state, which
nested contexts share by reference; frames are scoped by server identity so
mounted and multi-server setups stay isolated. Regression tests reproduce
the Code Mode mechanism without pydantic-monty via a CatalogTransform
factory, on both the fastmcp 3.x and 4.x legs.
@naji247
naji247 force-pushed the code-mode-support branch from 4e7c6ff to 1059d5f Compare August 6, 2026 13:27
…ases

Two hardening changes surfaced by reviewing the nested-call test plan:

- The frame now rides a module ContextVar tuple-stack (the _open_seams
  pattern) instead of FastMCP's Context._request_state. The state dict is
  only shared by reference with nested contexts from mid-3.x — on earlier
  releases the frame silently never propagated, so nested calls kept
  minting their own sessions there. A ContextVar has the same reach on
  every release, and concurrent inner calls each get their own context
  copy instead of racing restores in a shared dict.

- The test module's version gate was too coarse: everything skipped
  without CatalogTransform, quietly dropping nested-call coverage from
  the daily version sweep on pre-transform releases. Only the
  hidden-catalog tests carry that guard now; new composing-server tests
  (a listed tool calling a sibling via ctx.fastmcp.call_tool) run on
  every community fastmcp release — which is exactly how the carrier bug
  above was caught, on fastmcp 3.0.0.

New coverage: plain composing-tool nesting, per-call identify resolution
on nested events, and concurrent inner calls via asyncio.gather all
joining the outer session with a clean follow-up call.
@naji247
naji247 merged commit c8803b5 into main Aug 6, 2026
40 checks passed
@naji247
naji247 deleted the code-mode-support branch August 6, 2026 14:01
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