Skip to content

fix(community-v3): inject tool context without deep-copying the tool#38

Merged
naji247 merged 3 commits into
mainfrom
fix/community-v3-tool-context-deepcopy
Jul 9, 2026
Merged

fix(community-v3): inject tool context without deep-copying the tool#38
naji247 merged 3 commits into
mainfrom
fix/community-v3-tool-context-deepcopy

Conversation

@naji247

@naji247 naji247 commented Jul 9, 2026

Copy link
Copy Markdown
Member

Summary

  • Bug: In the FastMCP v3 middleware, _inject_context_into_tools deep-copied the entire tool object on every tools/list request in order to add the AgentCat context parameter to its input schema. FastMCP v3 tools generated from an OpenAPI specification hold a reference to their httpx.AsyncClient, which contains a threading.RLock. Deep-copying that object raises TypeError: cannot pickle '_thread.RLock' object. The failure was caught and the tool returned unmodified, so context injection was silently skipped for every affected tool while the error was logged on each request — generating a high volume of SDK diagnostic errors.
  • Fix: Only the parameters schema (a plain dictionary) is mutated during injection, so we now copy just that schema and reuse the rest of the tool by reference via Tool.model_copy. This avoids traversing non-copyable fields, restores context injection for OpenAPI-generated tools, and leaves the server's original tool objects unmutated.
  • Diagnostics: The remaining copy-failure log line now records the resolved FastMCP version to aid future diagnosis.
  • Release: Patch version bump 1.0.0 -> 1.0.1.

Impact

Customers running OpenAPI-generated FastMCP v3 servers on agentcat==1.0.0 were not receiving tool-call context/intent capture, and their processes emitted a copy-failure log per tool on every tools/list. This restores the intended behavior and removes the error volume.

Test plan

  • uv run pytest tests/community/test_community_v3_inject_context.py -v - new regression tests pass:
    • reproduces the RLock deep-copy failure on an OpenAPI-generated tool
    • verifies context is injected into the tool's parameters and marked required
    • verifies the server's original tool object is not mutated
    • verifies the fallback log line includes the resolved FastMCP version
  • uv run pytest tests/community/ tests/test_context_parameters.py -q - full community suite passes (no regressions)
  • Verified against FastMCP 3.0.0b1 (pinned) and FastMCP 3.4.4 (overlay): 85 passed on each

naji247 added 3 commits July 9, 2026 13:43
When enriching a tool's input schema with the AgentCat `context` parameter,
the FastMCP v3 middleware deep-copied the entire tool object on every
`tools/list` request. FastMCP v3 tools generated from an OpenAPI specification
hold a reference to their `httpx.AsyncClient`, which contains a
`threading.RLock`. Deep-copying that object raised
`TypeError: cannot pickle '_thread.RLock' object`, and the resulting fallback
returned the tool unmodified. As a consequence, context injection was silently
skipped for every affected tool and the failure was logged on each request,
producing a high volume of diagnostic errors.

Only the `parameters` schema (a plain dictionary) is modified during injection,
so this change copies just that schema and reuses the rest of the tool by
reference via `Tool.model_copy`. This avoids traversing non-copyable fields
entirely, restores context injection for OpenAPI-generated tools, and leaves the
server's original tool objects unmutated.

The remaining copy-failure log line now records the resolved FastMCP version to
aid future diagnosis. Adds regression coverage that reproduces the RLock failure
with an OpenAPI-generated tool and verifies injection, non-mutation, and the
version-tagged log message. Verified against FastMCP 3.0.0b1 and 3.4.4.
…absent

The regression tests exercise the FastMCP v3 OpenAPI middleware path and require
FastMCP v3+. Guard the module so it is skipped rather than failing collection in
the no-FastMCP CI job and on the FastMCP v2 compatibility matrix: defer the
fastmcp/httpx imports behind a try/except and gate the module with a skipif on
the resolved FastMCP major version.
@naji247 naji247 merged commit e167664 into main Jul 9, 2026
43 checks passed
naji247 added a commit that referenced this pull request Jul 10, 2026
The community test suite previously exercised only plain function tools, which
deep-copy cleanly. That gap let the context-injection regression fixed in #38
ship green: FastMCP v3 tools that hold live runtime state (an httpx client on
OpenAPI-generated tools, a client factory on proxy tools, a sub-server reference
on mounted tools) failed to deep-copy and silently lost context injection.

Add end-to-end coverage that drives the real middleware dispatch against those
tool types, entirely in-process via httpx.MockTransport (no network):

- tests/test_utils/community_openapi_server.py: factories for OpenAPI, proxy, and
  mounted FastMCP v3 servers, plus a multi-endpoint OpenAPI spec.
- tests/community/test_community_v3_openapi.py: tools/list injects context on
  every tool, no copy failure is logged, cached tools are not mutated, tools/call
  strips context and captures it as intent without leaking it downstream, HTTP
  errors are captured, the tools/list event serializes OpenAPI tools, and
  get_more_tools coexists with generated tools.
- tests/community/test_community_v3_runtime_state_tools.py: parametrized guard
  over openapi/proxy/mounted asserting context injection with no copy failures.

All modules are FastMCP v3-only and skip cleanly when FastMCP is absent or on the
v2 compatibility matrix. Verified on FastMCP 3.0.0b1 and 3.4.4.
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