Latest Results
feat(langchain): answer MCP elicitation with a LangGraph interrupt
An MCP server can need input before it can finish a tool call — a date, a
confirmation, a choice among options. `MCPAdapter(target,
elicitation="interrupt")` routes those questions to whoever is already
reviewing the agent's work: the call raises a LangGraph `interrupt()` carrying
the server's question, and resuming with an answer lets the call finish.
```python
adapter = MCPAdapter(server, elicitation="interrupt")
agent = create_agent(model, await adapter.get_tools(), checkpointer=checkpointer)
paused = await agent.ainvoke({"messages": [...]}, config)
[question] = paused["__interrupt__"][0].value["requests"]
await agent.ainvoke(
Command(resume={"responses": {question["key"]: {"action": "accept", "content": {...}}}}),
config,
)
```
The loop is driven directly rather than through FastMCP's elicitation handler,
because FastMCP converts anything a handler raises into an MCP error — a
`GraphInterrupt` raised there is swallowed, and its payload ends up stringified
into a tool error the model reads. Driving
`session.call_tool(..., allow_input_required=True)` ourselves means
`interrupt()` is called from our own frame, where it propagates normally.
Requests are correlated by the keys the server chose, so no interrupt ids are
needed on resume. Several questions in one round share a single interrupt and a
single answer payload; successive rounds each get their own interrupt, and
LangGraph's per-task interrupt index replays earlier answers while the newest
round raises.
Interrupting unwinds the whole tool call, so resuming re-issues it from its
first round. A server that asks before doing work — the shape the protocol is
built around — repeats nothing, which the tests pin down by counting how often
the tool body actually runs. A server that works first and asks later repeats
that work once per round, and the module says so.
Kept deliberately narrow:
- Opt-in. The default declines elicitation exactly as before, and a client only
advertises the capability when the adapter is asked for interrupts.
- Elicitation only. Embedded sampling and roots requests raise, since driving
this loop by hand bypasses the FastMCP callbacks that would answer them.
- Modern servers only. The legacy server-initiated path cannot be answered this
way at all, and says so rather than failing obscurely.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>sydney-runkle/langchain/simplify-mcp-adapter feat(langchain): port MCP tool conversion from `langchain-mcp-adapters`
Tool conversion now follows `langchain-mcp-adapters` rather than a parallel
implementation, so a tool loaded through `langchain.mcp` reaches a model in the
same shape as the same tool loaded through that package, and the conversion
rules have one home rather than two.
`convert_mcp_tool_to_langchain_tool` is public, under the name it already has
in `langchain-mcp-adapters`. It takes an MCP tool and the FastMCP client to
call it through, so callers who manage their own client can convert tools
without an `MCPAdapter`.
What the port changes for callers:
- Results become real LangChain content blocks. Text, images, resource links,
and embedded text or blob resources map onto `create_text_block`,
`create_image_block`, and `create_file_block` instead of raw MCP model
dumps. Audio raises `NotImplementedError`, since it has no content block yet.
- A tool that runs and reports failure reaches the model as a `ToolMessage`
with `status="error"`, carrying the server's own error content, so an agent
can correct itself. Transport failures and unconvertible content still
propagate, since a model cannot act on them.
- Structured content arrives as `MCPToolArtifact`, a `TypedDict` with a
`structured_content` key, replacing a nested `{"mcp": {...}}` dict.
Typing the conversion against `mcp.types.Tool` and FastMCP's `CallToolResult`
also removes a layer of `getattr` probing that was quietly broken.
`CallToolResult` is a dataclass with no `model_dump`, so the `isError` branch of
the old error helper could never be reached and structured content was only
ever picked up through its `data` fallback.
Two pieces of adapter-side logic go with it. `_client_target` pre-resolved URL
strings to a transport to avoid a filesystem probe inside FastMCP's inference;
that probe is upstream's to reorder, and blocking briefly in a constructor is
not worth the duplicated inference. Duplicate-name validation is gone too:
MCP requires tool names to be unique per server, and FastMCP prefixes them per
server for multi-server configs, so a collision means a broken server rather
than something the adapter should police.
Finally, the adapter's own reference counting is gone. FastMCP clients are
already reentrant with their own reference counting, so the adapter simply
enters the client, and converted tools do the same when invoked — which is why
tools now stay callable after the adapter's context exits.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>sydney-runkle/langchain/simplify-mcp-adapter Latest Branches
0%
sydney-runkle/langchain/simplify-mcp-adapter 0%
mdrxy/langchain/bump-vcrpy-min 0%
hunter/langchain/new-mcp-adapteres © 2026 CodSpeed Technology