
This tutorial is part of Build an MCP Server, within The MCP Guide. Want it built for you? See MCP development.
LangGraph meets MCP from two sides. Your graph can use tools from any MCP server, and a deployed graph can be an MCP tool that Claude, Cursor or another agent calls. This tutorial covers both.
For the first half you load MCP tools with LangChain's MCPAdapter and run them in a hand-built StateGraph with ToolNode. For the second half you expose a graph through the /mcp endpoint that LangChain's Agent Server adds automatically. Code was checked against the LangChain and LangGraph docs on 2 November 2026, with langgraph 1.2.19 and langchain 1.4.8 current on PyPI that day.
Key Takeaways
- Load MCP tools with
langchain.mcp.MCPAdapter(installlangchain[mcp]); the oldlangchain-mcp-adapterspackage is no longer maintained. - MCP tools come back as normal LangChain tools, so
ToolNode(tools)andmodel.bind_tools(tools)work unchanged. - Agent Server exposes every deployed graph as an MCP tool at
/mcpover Streamable HTTP, and supports protocol revisions from2024-11-05through2026-07-28. - Give exposed graphs small, typed input and output schemas; the default
MessagesStateis "too general for direct LLM exposure," per the docs. - The
/mcpendpoint uses the same auth as the rest of the LangGraph API and rejects browser origins you have not allowed.
Prerequisites
- Python 3.10 or later.
pip install "langchain[mcp]" langgraphplus the provider package for your chat model.- An MCP server to test with. The 20-line FastMCP notes server in our langchain-mcp-adapters tutorial works well and runs at
http://localhost:8000/mcp.
If graphs, nodes and edges are new, LangChain's LangGraph quickstart builds the same loop with local tools first.
Part 1: Use MCP tools inside a LangGraph graph
Why not the old adapters?
Older LangGraph tutorials import MultiServerMCPClient from langchain_mcp_adapters. That repository now says it is "no longer actively maintained" and points to langchain[mcp]. The LangChain MCP docs describe the replacement, MCPAdapter, which "discovers a server's tools and adapts them into LangChain tools." It is in beta, so pin your versions.
Minimal working example
This graph has two nodes: a model node that decides whether to call a tool, and a ToolNode that runs it. It loops until the model stops asking for tools.
import asyncio
from typing import Literal
from langchain.chat_models import init_chat_model
from langchain.mcp import MCPAdapter
from langgraph.graph import END, START, MessagesState, StateGraph
from langgraph.prebuilt import ToolNode
async def main():
async with MCPAdapter("http://localhost:8000/mcp") as adapter:
tools = await adapter.list_tools()
model = init_chat_model("claude-sonnet-5").bind_tools(tools)
async def call_model(state: MessagesState):
return {"messages": [await model.ainvoke(state["messages"])]}
def route(state: MessagesState) -> Literal["tools", END]:
return "tools" if state["messages"][-1].tool_calls else END
builder = StateGraph(MessagesState)
builder.add_node("model", call_model)
builder.add_node("tools", ToolNode(tools))
builder.add_edge(START, "model")
builder.add_conditional_edges("model", route, ["tools", END])
builder.add_edge("tools", "model")
graph = builder.compile()
result = await graph.ainvoke(
{"messages": [{"role": "user", "content": "List my notes, then read the first one."}]}
)
print(result["messages"][-1].content)
asyncio.run(main())
What each piece does:
MCPAdapter(...)connects over Streamable HTTP because the target is anhttpURL. Pass aPathfor a local stdio script, or an{"mcpServers": {...}}dict for several servers.list_tools()returns LangChain tools. The docs say "the tools hold the client," so the graph keeps working inside the context.ToolNodeis LangGraph's prebuilt tool runner. The workflows and agents page says it "handles parallel tool execution, error handling, and state injection automatically."ainvokematters. MCP calls are network calls, so run the graph asynchronously.
Swap "claude-sonnet-5" for any chat model your LangChain install supports.
If you only need a standard agent loop
You may not need a custom graph at all. The LangChain MCP quickstart passes the same tools to create_agent, which builds a tool-calling loop for you. Write your own StateGraph when you need extra nodes, such as a human approval step before a write tool runs.
Add a human check before write tools
MCP servers can attach hints to tools. LangChain keeps them on tool.metadata["mcp"]["tool"]["annotations"], with keys such as read_only_hint and destructive_hint. The MCP tools page tells you to read this metadata defensively because every field is optional.
A simple pattern: split tools into a read-only list and a write list, and route write calls to a node that waits for approval. Treat the hints as hints. Our tool annotations guide explains why a malicious server could mislabel a tool.
Errors
A server result with isError=True becomes a ToolMessage with status="error", so the model sees the message and can try again. Transport or session failures raise an exception instead. Wrap the ainvoke call if your app must survive a server going down.
Test the server first
When a tool fails, find out which side is wrong. The MCP Inspector CLI calls the server with no model involved:
npx @modelcontextprotocol/inspector --cli http://localhost:8000/mcp --transport http --method tools/list
See our MCP Inspector lesson for tool calls and CI smoke tests.
Part 2: Expose your LangGraph agent as an MCP server
How it works
LangChain's Agent Server MCP page says Agent Server "implements MCP using the Streamable HTTP transport," which lets "LangGraph agents be exposed as MCP tools." The endpoint lives at /mcp. You do not write MCP code; deployed graphs appear as tools automatically.
Version details from that page:
- Base requirement:
langgraph-api>=0.2.3andlanggraph-sdk>=0.1.61. - Protocol revisions
2024-11-05through2026-07-28, plus the tool fields, session behavior andOrigincheck described there, needlanggraph-api>=0.18.0. - If your dependencies pin an
mcpversion outside the rangelanggraph-apineeds, Agent Server starts without the endpoint and logsMCP endpoint disabled: the installed MCP SDK is not supported.
Name and describe the tool
Each graph becomes one tool. The tool name is the agent's name, cleaned to allowed characters and cut to 128 characters. The description is the agent's description, or a generic fallback. Set both in langgraph.json:
{
"graphs": {
"my_agent": {
"path": "./my_agent/agent.py:graph",
"description": "Answers questions about our product docs. Input: a question."
}
},
"env": ".env"
}
Agent Server marks these tools with readOnlyHint: false, destructiveHint: false, idempotentHint: false and openWorldHint: true. Clients that respect hints will treat your agent as something that can change state.
Keep the schema small
The docs warn that the default MessagesState "is too general for direct LLM exposure." Use explicit input and output schemas instead:
from langgraph.graph import StateGraph, START, END
from typing_extensions import TypedDict
class InputState(TypedDict):
question: str
class OutputState(TypedDict):
answer: str
class OverallState(InputState, OutputState):
pass
def answer_node(state: InputState):
return {"answer": "replace with real logic", "question": state["question"]}
builder = StateGraph(OverallState, input_schema=InputState, output_schema=OutputState)
builder.add_node(answer_node)
builder.add_edge(START, "answer_node")
builder.add_edge("answer_node", END)
graph = builder.compile()
An agent whose input schema cannot be generated is left out of the tool list, so check tools/list after each deploy.
Connect a client
The docs' local examples point at http://localhost:2024/mcp. Test it with the Inspector the same way as above, adding an Authorization or X-Api-Key header when you deploy. For a deployed LangSmith agent, the docs' Python example wraps a fastmcp StreamableHttpTransport with an X-Api-Key header and hands it to MCPAdapter, so one LangGraph agent can call another.
Sessions, auth and origins
- No sessions. "Each
/mcprequest is independent," the endpoint answers with JSON, and it accepts onlyPOST. That matches the stateless design of the2026-07-28spec. - Auth.
/mcp"uses the same authentication as the rest of the LangGraph API." Add custom auth middleware if you need per-user identity. - Origins. Requests with an
Originheader must come from an allowed origin. A wildcard*"does not allow any origin on this endpoint." Localhost origins are always allowed; others get HTTP 403. - Turn it off. Set
disable_mcptotrueinlanggraph.json.
Pitfalls
- Old imports.
from langchain_mcp_adapters.client import MultiServerMCPClientbelongs to the retired package. - Sync invoke. Calling
graph.invokewith MCP tools in an async context causes confusing errors. Useainvoke. - Too many tools. Every MCP tool you bind adds a description to every model call. Filter to what the graph needs; see too many MCP tools.
- Pinned
mcppackage. A stale pin can silently disable Agent Server's/mcpendpoint. Check/infofor"mcp": falseunderflags.
Next step
The free MCP course explains the protocol pieces these libraries hide. If you want a LangGraph agent and its MCP servers designed and secured for production, talk to our MCP development team.
More in the MCP Guide
Free tools: server.json Validator · MCP Error Code Lookup
Latest research: State of MCP: October 2026
Frequently asked questions
Do I need langchain-mcp-adapters for LangGraph?
No. Use langchain[mcp] and MCPAdapter. The adapters repository says it is no longer actively maintained, and LangChain publishes a migration guide.
Can a LangGraph agent call another LangGraph agent over MCP?
Yes. Deploy the second agent to Agent Server, then load its /mcp endpoint with MCPAdapter. It appears as one tool whose input is that agent's input schema.
Does the /mcp endpoint support SSE?
The page describes Streamable HTTP only, answered with JSON and POST requests. Clients that can only speak the deprecated SSE transport will need an update.
How do I stop a graph from being exposed?
Set disable_mcp to true in langgraph.json to turn off the whole endpoint. The docs do not describe a per-graph switch, so deploy graphs you want to keep private in a separate deployment with MCP disabled.











