fix: skip tools with langgraph-injected args in bind_tools MCP bridge - #6
Open
jasoncarreira wants to merge 1 commit into
Open
Conversation
``ClaudeCodeChatModel.bind_tools`` wraps each LangChain tool into an
SDK MCP server tool by invoking ``tool._arun(**user_args)`` directly
inside ``_wrap_langchain_tool``. This bypasses LangChain's normal
tool-invocation pipeline — including the langgraph injection step
that populates ``ToolRuntime`` and other ``InjectedToolArg``-marked
parameters.
The result: any tool whose underlying function takes a runtime-
injected arg (notably ``ToolRuntime`` from
``langgraph.prebuilt.tool_node``) raises on every invocation through
the bridge:
TypeError: missing 1 required positional argument: 'runtime'
In the wild this surfaces when an application built on
``deepagents`` is bound through ``ClaudeCodeChatModel``: the
framework's middleware-injected filesystem tools (``read_file``,
``write_file``, ``edit_file``, ``ls``, ``glob``, ``grep``) all carry
the ``ToolRuntime`` injection, so all of them fail when routed
through MCP. The agent typically retries a couple of times then
falls back to a same-named framework-native path that works — at
the cost of wasted turns and confusing log traces.
Fix: detect these tools at bind time via a new
``_has_runtime_injected_args`` helper and skip them. Detection walks
the tool's coroutine / func / class-method signatures and checks
each parameter's annotation against the ``_DirectlyInjectedToolArg``
base class (covers ``ToolRuntime[X, Y]`` bare-type annotations) and
the ``InjectedToolArg`` marker (covers ``Annotated[T,
InjectedToolArg(...)]`` form). Both imports are guarded by try/except
so the helper degrades to a no-op when langgraph isn't installed.
Skipped tools are logged but otherwise silent — the framework's
native tool path still serves them when the model needs them
(deepagents auto-injects filesystem tools into every model request
via SubAgent middleware, regardless of what's in the MCP allowlist).
Adds ``langgraph>=0.2`` to dev dependencies for the new test —
``langgraph`` stays optional at runtime via the import guards.
This was referenced May 22, 2026
jasoncarreira
added a commit
to jasoncarreira/mimir
that referenced
this pull request
May 22, 2026
Pins the dep to the ``mimir-bundled-fixes`` integration branch on jasoncarreira/langchain-claude-code, which combines three fixes currently open as PRs upstream: * thehumanworks/langchain-claude-code#2 — preserve full ``ResultMessage`` field set on ``response_metadata`` in both streaming and non-streaming paths * thehumanworks/langchain-claude-code#4 — capture all tool calls/results via SDK hooks instead of inferring from the message stream (closes upstream issue #3) * thehumanworks/langchain-claude-code#6 — skip tools with langgraph-injected args in the bind_tools MCP bridge, fixing ``TypeError: missing 1 required positional argument: 'runtime'`` on every ``mcp__langchain-tools__read_file`` / ``write_file`` / ``edit_file`` invocation when running on deepagents 0.6+ The third one is the load-bearing fix for the production-observed duplicate-tools failure on mimirbot (turn 078eb0f98a1b on 2026-05-22): the framework's middleware-injected filesystem tools were also being bridged through MCP, and every bridged invocation failed with the missing-runtime error. The agent retried a few times before falling back to the native framework tool (``Read``), wasting prompt tokens and turn cycles in the process. Adds ``[tool.hatch.metadata] allow-direct-references = true`` so hatchling permits the ``pkg @ git+https://...`` form in ``optional-dependencies``. Reverts to the registry version (and drops the flag) once upstream PRs land + a new PyPI release ships. Three places in pyproject.toml carry the pin (claude-code extra, dev extra, deepagents extra) — all updated to the same SHA. Co-authored-by: Jason Carreira <jason@visotrust.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jasoncarreira
added a commit
to jasoncarreira/mimir
that referenced
this pull request
May 24, 2026
Pins the dep to the ``mimir-bundled-fixes`` integration branch on jasoncarreira/langchain-claude-code, which combines three fixes currently open as PRs upstream: * thehumanworks/langchain-claude-code#2 — preserve full ``ResultMessage`` field set on ``response_metadata`` in both streaming and non-streaming paths * thehumanworks/langchain-claude-code#4 — capture all tool calls/results via SDK hooks instead of inferring from the message stream (closes upstream issue #3) * thehumanworks/langchain-claude-code#6 — skip tools with langgraph-injected args in the bind_tools MCP bridge, fixing ``TypeError: missing 1 required positional argument: 'runtime'`` on every ``mcp__langchain-tools__read_file`` / ``write_file`` / ``edit_file`` invocation when running on deepagents 0.6+ The third one is the load-bearing fix for the production-observed duplicate-tools failure on mimirbot (turn 078eb0f98a1b on 2026-05-22): the framework's middleware-injected filesystem tools were also being bridged through MCP, and every bridged invocation failed with the missing-runtime error. The agent retried a few times before falling back to the native framework tool (``Read``), wasting prompt tokens and turn cycles in the process. Adds ``[tool.hatch.metadata] allow-direct-references = true`` so hatchling permits the ``pkg @ git+https://...`` form in ``optional-dependencies``. Reverts to the registry version (and drops the flag) once upstream PRs land + a new PyPI release ships. Three places in pyproject.toml carry the pin (claude-code extra, dev extra, deepagents extra) — all updated to the same SHA. Co-authored-by: Jason Carreira <jason@visotrust.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jasoncarreira
added a commit
to jasoncarreira/mimir
that referenced
this pull request
May 25, 2026
Pins the dep to the ``mimir-bundled-fixes`` integration branch on jasoncarreira/langchain-claude-code, which combines three fixes currently open as PRs upstream: * thehumanworks/langchain-claude-code#2 — preserve full ``ResultMessage`` field set on ``response_metadata`` in both streaming and non-streaming paths * thehumanworks/langchain-claude-code#4 — capture all tool calls/results via SDK hooks instead of inferring from the message stream (closes upstream issue #3) * thehumanworks/langchain-claude-code#6 — skip tools with langgraph-injected args in the bind_tools MCP bridge, fixing ``TypeError: missing 1 required positional argument: 'runtime'`` on every ``mcp__langchain-tools__read_file`` / ``write_file`` / ``edit_file`` invocation when running on deepagents 0.6+ The third one is the load-bearing fix for the production-observed duplicate-tools failure on mimirbot (turn 078eb0f98a1b on 2026-05-22): the framework's middleware-injected filesystem tools were also being bridged through MCP, and every bridged invocation failed with the missing-runtime error. The agent retried a few times before falling back to the native framework tool (``Read``), wasting prompt tokens and turn cycles in the process. Adds ``[tool.hatch.metadata] allow-direct-references = true`` so hatchling permits the ``pkg @ git+https://...`` form in ``optional-dependencies``. Reverts to the registry version (and drops the flag) once upstream PRs land + a new PyPI release ships. Three places in pyproject.toml carry the pin (claude-code extra, dev extra, deepagents extra) — all updated to the same SHA. Co-authored-by: Jason Carreira <jason@visotrust.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
`ClaudeCodeChatModel.bind_tools` wraps each LangChain tool into an SDK MCP server tool by invoking `tool._arun(**user_args)` directly inside `_wrap_langchain_tool`. This bypasses LangChain's normal tool-invocation pipeline — including the langgraph injection step that populates `ToolRuntime` and other `InjectedToolArg`-marked parameters.
The result: any tool whose underlying function takes a runtime-injected arg raises on every invocation through the bridge:
```
TypeError: missing 1 required positional argument: 'runtime'
```
Where this bites
Applications built on `deepagents` (and other langgraph-based frameworks) routinely declare middleware-injected tools — `read_file`, `write_file`, `edit_file`, `ls`, `glob`, `grep` from `deepagents.middleware.filesystem.FilesystemMiddleware` all take a `runtime: ToolRuntime[...]` parameter. When such an app is bound through `ClaudeCodeChatModel`, every one of those tools fails on every invocation. The agent typically retries a couple of times then falls back to a same-named framework-native path that works — at the cost of wasted turns and confusing traces in logs.
Real-world failure observed in a deepagents 0.6-based agent calling `mcp__langchain-tools__read_file` three times before falling back to the framework's native `Read` tool.
Fix
Detect tools with runtime-injected args at bind time and skip them. The new `_has_runtime_injected_args` helper:
Skipped tools log an INFO line but the bind is otherwise silent — the framework's native tool path still serves them when the model needs them.
Why skip rather than fix the bridge
The MCP transport doesn't carry langgraph state (no access to the graph runtime, channels, store, etc.). Even if we constructed a synthetic `ToolRuntime` and passed it in, the tool body would then call `_get_backend(runtime)` and fail because the synthetic runtime has no backend wired up. The right answer is: don't bridge tools that need state they can't get over MCP. Frameworks like deepagents already expose these tools natively to the model through their own middleware, so the bridge wrapping was redundant in the first place.
Tests
New `test_bind_tools_skips_runtime_injected_args`:
`langgraph>=0.2` added to dev dependencies (only used in the new test; production runtime stays optional via import guards).
All 12 existing tests pass.
🤖 Generated with Claude Code