MCPAdapter bridges MCP servers and LangChain agents: it discovers the tools a server advertises and adapts them into standard LangChain tools. Pass the tools from listTools() to createAgent as you would any other LangChain tool.
This page covers what is specific to that bridge: identifying MCP tools, controlling their execution, handling their outputs, and responding when a server needs input during a call. For the runnable discovery-and-agent example, see the MCP quickstart.
Use MCP tools in an agent
Discover the server’s catalog withMCPAdapter.listTools, then give the returned tools to createAgent. From the agent’s perspective, they behave like LangChain tools: the model chooses a tool, LangChain invokes it, and the resulting ToolMessage returns to the model.
await adapter.close() when you finish using the agent.
For general guidance on defining, binding, and using LangChain tools, see Tools. For several MCP servers and their namespaced tool catalogs, see Connections.
Handle tool outputs
MCP tool results becomeToolMessage objects with content the model can read, an artifact for application data, and a status indicating success or failure.
Multimodal content
The adapter converts model-visible MCP content into LangChain content blocks. For example, a tool that returns an MCP image block with a screenshot reaches the model as animage block.
Structured content
When a tool returns structured content, the adapter attaches it to theToolMessage as an artifact rather than folding it into the model-visible text. Run the agent, then read the artifact from the ToolMessage instances in the result:
artifact is an array of MCP result entries. Structured content appears in the entry with type: "mcp_structured_content", and its data field holds the tool result’s structuredContent.
Errors
When a server returnsisError: true during an agent tool call, the adapter returns a ToolMessage with status: "error" and the server’s message. The model can read the error and try again.
The adapter throws transport failures. When a tool runs inside createAgent, the agent’s default error handling turns those failures into error tool messages too. The message’s status alone does not distinguish the cause:
isError: true throws a ToolException instead, with the MCP result in error.result.
Tool metadata
The adapter stores MCP tool annotations intool.metadata.annotations:
annotations holds the server’s MCP tool annotations, such as readOnlyHint and destructiveHint. A server may omit them, so the field can be undefined.
Read optional metadata defensively, so a missing field returns a default rather than failing:
This example and the human-in-the-loop example import types from @modelcontextprotocol/client; add it as a direct dependency.
Human-in-the-loop
Reading annotations lets you gate a tool based on what the server declares about it, rather than hardcoding tool names. The MCP annotation classifies the tool and LangChain’s human-in-the-loop middleware enforces the approval policy. Read the destructive hint from metadata once during tool discovery. Then give the human-in-the-loop configuration awhen predicate that receives each pending tool call and returns whether the call needs approval:
delete_file call targeting a protected path.
Access the arguments through request.toolCall.args.
Combine the metadata classification and argument check to gate a tool only when its type and inputs warrant approval. For the full approval workflow, see Human-in-the-loop.
Server requests during tool execution
Most tools finish without asking the client for anything mid-call. When a server needs input,MCPAdapter surfaces elicitation as a LangGraph interrupt.
Elicitation
Elicitation lets an MCP server request input during a tool call. The adapter pauses the run with a LangGraph interrupt. Your application presents the request to the user and resumes the run with their answer. Attach a checkpointer to the agent and use the samethread_id when invoking and resuming it.
This example assumes the booking tool asks for one date and pauses once. Replace the sample date with the user’s answer.
Interrupt-driven elicitation requires a modern MCP server and is enabled by default. Set elicitation: false on the server to opt out. For a legacy server, configure an onElicitation handler with mode: "legacy" instead. Without a checkpointer, a tool that asks for input fails with an error ToolMessage instead of pausing.
createMCPElicitationResume addresses the answer to the interrupt that requested it. The response uses the server’s request key. Build every resume value with createMCPElicitationResume rather than a bare { responses } object, which does not say which interrupt it answers.
Each interrupt’s value is an MCPElicitationInterrupt: type: "mcp_elicitation", server, tool (the server’s own tool name, without the prefix), arguments (the effective tool arguments), and requests. Check type when the same agent can also raise human-in-the-loop interrupts.
When several calls pause at once, answer every interrupt. Merge the objects that createMCPElicitationResume returns into one Command({ resume }), for example Object.assign({}, ...paused.__interrupt__!.map((q) => createMCPElicitationResume(q, answers))), or resume until __interrupt__ is empty. A resume that answers only the first interrupt leaves the others paused.
Resuming reruns the tool from the beginning. Any work performed before the server asks for input can repeat. Make that work safe to repeat without duplicating side effects.
accept: Provide formcontentmatching the request’s schema, or confirm completion of a URL interaction withoutcontent.decline: Refuse to provide the requested information.cancel: Indicate that the user canceled the interaction.
ToolException, which createAgent returns to the model as an error ToolMessage. See Sampling and roots.
Interrupt-driven elicitation answers a server that returns its request as an
InputRequiredResult. A server that only pushes elicitation over a legacy handshake session cannot be answered this way.See also
Connect these docs to your agent of choice via MCP for real-time answers.

