Contents · 20 sections↓
- 01What is the difference between LangChain and MCP?
- 02What is MCP?
- 03What is LangChain?
- 04How do LangChain and MCP work together?
- 05LangChain tools vs MCP tools
- 06LangChain vs MCP: when to use LangChain tools, MCP, or both
- 07LangChain vs MCP vs Promptise: where does Promptise fit?
- 08What you need
- 09Step 1: Write the tools once as an MCP server
- 10Step 2: Load the MCP tools into a LangChain agent
- 11Step 3: Mix LangChain tools and MCP tools in one agent
- 12Step 4: Require approval before destructive MCP tools
- 13Step 5: Read MCP resources and prompts
- 14Step 6: Use the same server from Promptise Foundry
- 15The complete example
- 16Running it in production
- 17Common problems and fixes
- 18Limitations
- 19Frequently asked questions
- 20Next steps
LangChain vs MCP is a confusing comparison because the two show up in the same places: wherever an AI agent calls tools. They are different kinds of things. This guide is for developers deciding where their tools should live. You will learn what each one is, how a LangChain agent uses tools from an MCP server, how LangChain tools compare with MCP tools, and when to use which. At the end you will have a runnable LangChain agent that mixes MCP tools with a local tool and asks for approval before destructive actions.
What is the difference between LangChain and MCP?
MCP (the Model Context Protocol) is an open protocol. It defines how a separate program, an MCP server, offers tools, data (resources) and prompt templates to any AI application, over stdio or HTTP. LangChain is a framework for building the AI application itself: the agent loop, model calls, memory and integrations. They are not alternatives. A LangChain agent can act as an MCP client and use tools from any MCP server through LangChain's MCP adapter.
MCP decides where tools live and how they are called. A tool on an MCP server runs in its own process and works with any MCP client: LangChain, other frameworks, Claude, Cursor or VS Code.
LangChain decides what the agent does with tools. The loop, the prompt, memory, approvals and tracing are framework features. MCP has none of them.
A LangChain tool is a Python object in your process. Only LangChain agents in that codebase can use it.
They connect in one line. await MCPAdapter(url).list_tools() returns LangChain tools that call the MCP server. In LangChain 1.4 this API is in beta.
The model sees the same thing either way: a tool name, a description and a JSON schema.
[02]
What is MCP?
MCP is a client-server protocol for giving AI applications context. An application, called the host, runs one MCP client per server it uses. Each server exposes up to three kinds of things:
Tools: functions the model can ask to call, such as get_order or create_issue.
Resources: data the application can read, such as a policy document or a database schema.
Prompts: reusable prompt templates with arguments.
Client and server exchange JSON-RPC messages over stdio, for a local child process, or Streamable HTTP, for a network service. The server can be written in any language, and it doesn't know or care which framework the agent uses. Anthropic released MCP in November 2024 and later donated it to the Linux Foundation's Agentic AI Foundation. What is the Model Context Protocol explains the protocol in depth, and MCP tools vs resources vs prompts explains the three primitives.
What MCP doesn't do matters as much. It has no agent loop, no memory and no model. An MCP server waits for calls; something else decides which tool to call and what to do with the result.
[03]
What is LangChain?
LangChain is an open-source framework, in Python and JavaScript, for building applications on language models. In version 1.x its center is create_agent, which runs the tool-calling loop on LangGraph, plus middleware for approvals, call limits, summarization and PII redaction. Around that it has integrations for dozens of model providers, document loaders and vector stores. LangSmith, its hosted tracing and deployment product, is optional.
In LangChain, a tool is a Python object, usually a function decorated with @tool. It runs inside your agent's process, it can use anything in your codebase, and only LangChain agents can call it.
So "LangChain vs MCP" is really two narrower questions. Should a given tool be a LangChain tool or live on an MCP server? And does your agent need a framework like LangChain at all? This guide answers the first. For the second, see LangChain alternatives.
[04]
How do LangChain and MCP work together?
LangChain has an MCP client, usually called the LangChain MCP adapter. It connects to MCP servers, lists their tools, and turns each one into a LangChain tool. When the model asks for that tool, the LangChain tool sends a tools/call request to the server and returns the result as a ToolMessage.
┌──────────────────────── your process ─────────────────────────┐
│ create_agent loop ──▶ model (sees name, description, schema)│
│ │ │
│ ├── @tool refund_amount (LangChain tool, local) │
│ └── get_order, cancel_order (converted MCP tools) │
│ │ │
│ MCPAdapter = MCP client │
└────────────────────┼───────────────────────────────────────────┘
│ JSON-RPC over Streamable HTTP or stdio
┌────────────────────▼───────────────────┐
│ MCP server (any language, any owner) │
│ tools · resources · prompts │──▶ databases, APIs
└────────────────────────────────────────┘There are two packages for this, and search results mix them up:
`langchain.mcp` with `MCPAdapter` is the current API, added in LangChain 1.4 and marked beta. Install it with pip install "langchain[mcp]". It is built on FastMCP 4 and the MCP Python SDK 2.x. The LangChain MCP docs use it.
`langchain-mcp-adapters` with `MultiServerMCPClient` is the older standalone package, still on PyPI at 0.3.2. LangChain's migration guide says langchain.mcp replaces it. Version 0.3.2 pins mcp<2, so it can't share an environment with langchain[mcp].
Most tutorials still show the older package. This is what it looks like, and it works against the same server you build below:
"""The older route: langchain-mcp-adapters 0.3.x (needs mcp<2). pip install langchain-mcp-adapters"""
import asyncio
from langchain.agents import create_agent
from langchain_mcp_adapters.client import MultiServerMCPClient
client = MultiServerMCPClient({"store": {"transport": "streamable_http", "url": "http://127.0.0.1:8080/mcp"}})
async def main():
tools = await client.get_tools() # opens a new MCP session for each tool call
agent = create_agent("openai:gpt-5-mini", tools=tools, system_prompt="You are a store support agent. Be brief.")
result = await agent.ainvoke({"messages": [{"role": "user", "content": "What's in order O-1001?"}]})
print(result["messages"][-1].content)
asyncio.run(main())Order O-1001 contains: Desk lamp — $49.00. Status: delivered.The migration guide maps the older API to the new one:
langchain-mcp-adapters | langchain.mcp |
|---|---|
| MCPAdapter(target), an async context manager |
await client.get_tools() / load_mcp_tools() | await adapter.list_tools() |
StdioConnection, StreamableHttpConnection | A Path target, an http(s) URL, or a FastMCP transport |
tool_interceptors | @wrap_tool_call middleware on the agent |
load_mcp_prompt, load_mcp_resources | No wrapper yet: call the FastMCP client directly |
[05]
LangChain tools vs MCP tools
LangChain tool | MCP tool | |
|---|---|---|
What it is | A Python object (BaseTool), often a @tool function | A function a server exposes over the MCP protocol |
Where it runs | Inside the agent's process | In the server's process, locally or across the network |
Language | Python (or JS for LangChain.js) | Any language with an MCP SDK |
Who can use it | LangChain agents in the same codebase | Any MCP client: other frameworks, IDEs, desktop apps |
How the agent finds it | You pass it in tools= | The client lists the server's tools at runtime |
Isolation | Shares memory, secrets and dependencies with the agent | Own process, own dependencies, own credentials |
Auth | Whatever your code does | Transport-level, such as OAuth or bearer tokens over HTTP |
Extra metadata | Name, description, schema | The same, plus annotations such as destructive_hint |
Overhead per call | A function call | An IPC or HTTP round trip |
What the model sees | Name, description, JSON schema | Name, description, JSON schema |
The last row is the one people miss. The adapter turns an MCP tool into a LangChain tool before the model sees it, so token cost and tool selection are the same for both. Ten MCP tools cost as much context as ten @tool functions. If an agent has too many tools, filter them; too many MCP tools covers how.
[06]
LangChain vs MCP: when to use LangChain tools, MCP, or both
Use a LangChain tool when the logic belongs to this agent: a calculation, a formatting step, a call that needs objects already in memory, or a prototype with one team and one codebase.
Use an MCP server when:
Several agents, frameworks or apps need the same tool, including IDEs and desktop assistants.
Another team owns the system behind the tool and should own its access rules.
The tool needs its own credentials, dependencies or deployment, separate from the agent.
You might change agent frameworks later. Tools on MCP move without changes.
Use both in most real agents: shared systems on MCP servers, small app-specific helpers as local tools. The steps below build exactly that.
And when not to use MCP: for a single script with one or two tools, a local function is simpler, faster and easier to debug. MCP vs CLI for AI agents compares MCP with another lightweight option, shell commands.
[07]
LangChain vs MCP vs Promptise: where does Promptise fit?
Promptise Foundry, which we make, sits on LangChain's side of this comparison: it is an agent framework, not a protocol. The difference is that it is MCP-native. An agent gets its tools from MCP servers through build_agent(servers=...), with no adapter to install, and the same package has a framework for building production MCP servers with authentication, middleware and approval gates. It is also built on LangChain's packages: it uses LangChain chat models, turns MCP tools into LangChain tool objects, and accepts your existing @tool functions through extra_tools=.
LangChain | MCP | Promptise Foundry | |
|---|---|---|---|
What it is | Agent framework (Python and JS) | Open protocol between AI apps and tool servers | Agent framework plus MCP server framework (Python) |
Tools defined where | @tool objects in your process; MCP tools through an adapter | On MCP servers, in any language | On MCP servers (built with Promptise or anything else); LangChain tools via extra_tools= |
How it connects to MCP | langchain.mcp.MCPAdapter (beta) or langchain-mcp-adapters | It is the protocol | Built in: servers={"name": HTTPServerSpec(...)} or StdioServerSpec(...) |
Server-side features | None; it is a client | Defined by the spec: tools, resources, prompts, transports, auth for HTTP | MCPServer with auth, middleware, rate limits, caching and server-side approval gates |
Resources and prompts | Not wrapped yet; use the FastMCP client | Part of the protocol | expose_resources=True and expose_prompts=True give them to the agent as tools |
Approvals | HumanInTheLoopMiddleware; the paused run is saved by a checkpointer and can survive a restart | The spec says a person should be able to deny tool calls; the host implements it | ApprovalPolicy with a handler that waits in-process, up to 24 hours; lost on restart |
Observability | LangSmith (opt-in), callbacks | Left to clients and servers | Opt-in observe=True: HTML report, JSON, OTLP or Prometheus |
When to choose it | Many integrations, LangGraph's durable runs, LangSmith | Tools that many clients and frameworks share | MCP-first agents and servers with approvals, budgets and deployment in one package |
Step 6 runs a Promptise agent against the same server, with an approval rule and the server's resources. Sources: the Promptise docs for the MCP client, server auth, middleware, approval gates, approval and observability, and the MCP tools specification.
LangChain is the better choice in several cases:
Integration breadth. LangChain has provider packages for dozens of model vendors, plus ready-made document loaders and vector stores. Promptise has RAG base classes, but you write the loader and vector store adapters.
The LangGraph ecosystem. Checkpointed interrupts let an approval wait for days and survive a restart, and LangGraph gives you explicit graphs, Studio and LangSmith evaluation and deployment. A pending Promptise approval doesn't survive a restart.
Community and languages. LangChain has far more users, tutorials and answers (about 148,000 GitHub stars against under 1,000 for Promptise Foundry in October 2026) and a JavaScript version.
MCP SDK version. LangChain's MCP client runs on MCP SDK 2.x. Promptise 1.4 still pins mcp<2, which is why this guide uses two environments.
Promptise fits better when MCP is the center of the design: you build the servers as well as the agents, or you want approvals, budgets and resources as agent settings.
[08]
What you need
Python 3.10 or later and an OpenAI API key in OPENAI_API_KEY. The agent uses gpt-5-mini.
Two virtual environments, one per process. The server here is built with Promptise Foundry 1.4, which pins mcp<2. LangChain's MCP client needs mcp 2.x. The two can't share an environment, and with MCP they don't have to: they only talk over HTTP.
# environment 1: the MCP server
pip install "promptise==1.4.3"
# environment 2: the LangChain agent
pip install "langchain[mcp]==1.4.6" "langchain-openai==1.7.2"Any MCP server works in place of the one below, including servers written with FastMCP or the official MCP SDKs, or in another language.
[09]
Step 1: Write the tools once as an MCP server
Start with the shared system: an order store. The server exposes two tools, a resource and a prompt, so you can see how LangChain handles each.
"""An MCP server with two tools, a resource and a prompt. Run it: python store_server.py"""
from promptise.mcp.server import MCPServer
ORDERS = {
"O-1001": {"item": "Desk lamp", "amount_usd": 49.00, "status": "delivered"},
"O-1002": {"item": "Office chair", "amount_usd": 219.00, "status": "processing"},
}
server = MCPServer(name="store")
@server.tool(read_only_hint=True)
async def get_order(order_id: str) -> dict:
"""Look up an order by ID: item, amount in USD and status."""
return ORDERS.get(order_id.strip().upper(), {"error": f"No order {order_id}"})
@server.tool(destructive_hint=True)
async def cancel_order(order_id: str) -> dict:
"""Cancel an order that has not shipped yet."""
order = ORDERS.get(order_id.strip().upper())
if order is None or order["status"] != "processing":
return {"cancelled": False, "reason": "Only orders that are still processing can be cancelled."}
order["status"] = "cancelled"
return {"cancelled": True, "order_id": order_id}
@server.resource("policy://returns", mime_type="text/plain")
async def return_policy() -> str:
"""The store's return and refund policy."""
return "Cancelled orders are refunded in full. Returns of delivered items cost $15 return shipping."
@server.prompt()
async def support_reply(customer: str) -> str:
"""Instructions for a short, friendly support reply."""
return f"Write a two-sentence reply to {customer}. Be friendly and state the next step."
if __name__ == "__main__":
server.run(transport="http", port=8080) # Streamable HTTP at http://127.0.0.1:8080/mcpRun it in the first environment with python store_server.py. It listens on http://127.0.0.1:8080/mcp.
The annotations matter later. read_only_hint=True and destructive_hint=True travel with each tool's definition, so a client can treat cancel_order differently from get_order without hardcoding names. They are hints from the server, not guarantees.
[10]
Step 2: Load the MCP tools into a LangChain agent
In the second environment, connect with MCPAdapter, list the tools and hand them to create_agent:
import asyncio
from langchain.agents import create_agent
from langchain.mcp import MCPAdapter # beta in langchain 1.4: pip install "langchain[mcp]"
URL = "http://127.0.0.1:8080/mcp"
async def main():
async with MCPAdapter(URL) as store:
tools = await store.list_tools() # MCP tools, converted to LangChain tools
for t in tools:
print(f"{t.name}: {t.metadata['mcp']['tool']['annotations']}")
agent = create_agent("openai:gpt-5-mini", tools=tools, system_prompt="You are a store support agent. Be brief.")
result = await agent.ainvoke({"messages": [{"role": "user", "content": "What's in order O-1002, and has it shipped?"}]})
print(result["messages"][-1].content)
asyncio.run(main())get_order: {'read_only_hint': True}
cancel_order: {'destructive_hint': True}
Order O-1002 contains: Office chair — $219.00. Status: processing (not shipped).What happened:
MCPAdapter(URL) opened an MCP session and list_tools() converted each server tool into a LangChain StructuredTool. The server's annotations are on tool.metadata["mcp"].
The agent didn't know the tools came from MCP. It called get_order, the call went over HTTP, and the result came back as a normal tool message.
Python prints a LangChainBetaWarning on import, because langchain.mcp is beta in 1.4.
A string target must be an http or https URL. To start a local server over stdio, pass a Path or a FastMCP StdioTransport. A Path runs the script with the agent's own Python, so use StdioTransport(command=..., args=[...]) when the server needs packages the agent's environment doesn't have.
[11]
Step 3: Mix LangChain tools and MCP tools in one agent
Refund math is specific to this app, so it stays a local LangChain tool. It sits in the same tools list as the MCP tools:
@tool
def refund_amount(amount_usd: float, delivered: bool) -> float:
"""Work out the refund for an order: full price, minus $15 return shipping if it was delivered."""
return round(amount_usd - (15.0 if delivered else 0.0), 2)
...
tools=[*mcp_tools, refund_amount], # MCP tools and a LangChain tool, side by sideNothing else changes: LangChain runs the local tool in-process and the MCP tools over the connection.
[12]
Step 4: Require approval before destructive MCP tools
cancel_order changes data, so a person should confirm it. LangChain's HumanInTheLoopMiddleware pauses the run before named tools. Instead of hardcoding names, read the server's destructive_hint:
def is_destructive(t) -> bool: # read the MCP annotation the server declared
return t.metadata.get("mcp", {}).get("tool", {}).get("annotations", {}).get("destructive_hint", False)
gated = {t.name: {"allowed_decisions": ["approve", "reject"]} for t in mcp_tools if is_destructive(t)}
...
middleware=[HumanInTheLoopMiddleware(interrupt_on=gated)],
checkpointer=InMemorySaver(), # an interrupt needs a checkpointerWhen the model calls cancel_order, the run stops and returns an interrupt. Your code shows the pending action to a person, then resumes with Command(resume={"decisions": [...]}). Here a stand-in function approves it. This is the full run of the complete example below:
APPROVAL NEEDED: cancel_order({'order_id': 'O-1002'}) -> approved
tools called: ['get_order', 'cancel_order', 'refund_amount']
Order O-1002 has been canceled. You will receive a refund of $219.00.The agent looked up the order over MCP, paused before the destructive MCP tool, cancelled after approval, and used the local tool for the refund. Because the paused state is in the checkpointer, a database checkpointer would let the approval wait for hours. Only trust annotations from servers you control. A third-party server can label a destructive tool as read-only, so keep an explicit list for those. Human-in-the-loop approval for AI agents covers approval design.
[13]
Step 5: Read MCP resources and prompts
MCPAdapter wraps tools only. Resources and prompts are still on the server, and you reach them through the underlying FastMCP client:
import asyncio
from langchain.mcp import MCPAdapter
URL = "http://127.0.0.1:8080/mcp"
async def main():
async with MCPAdapter(URL) as store:
client = store.client # the underlying FastMCP client
policy = await client.read_resource("policy://returns")
print("resource:", policy[0].text)
prompt = await client.get_prompt("support_reply", {"customer": "Dana"})
print("prompt:", prompt.messages[0].content.text)
asyncio.run(main())resource: Cancelled orders are refunded in full. Returns of delivered items cost $15 return shipping.
prompt: Write a two-sentence reply to Dana. Be friendly and state the next step.Use resources for context your code decides to load, such as a policy added to the system prompt, and prompts for templates you want to share across clients. The model never calls these directly.
[14]
Step 6: Use the same server from Promptise Foundry
The point of MCP is that the server doesn't change when the client does. Here the same server and the same request go to a Promptise Foundry agent, running in the server's environment:
"""The same MCP server, used by Promptise Foundry. pip install "promptise>=1.4.3" """
import asyncio
from promptise import build_agent
from promptise.approval import ApprovalDecision, ApprovalPolicy
from promptise.config import HTTPServerSpec
async def ask_a_person(request) -> ApprovalDecision: # stand-in for Slack, a webhook or a UI
print(f"APPROVAL NEEDED: {request.tool_name}({request.arguments}) {request.tool_annotations} -> approved")
return ApprovalDecision(approved=True)
async def main():
agent = await build_agent(
model="openai:gpt-5-mini",
servers={"store": HTTPServerSpec(url="http://127.0.0.1:8080/mcp")}, # tools come from MCP
instructions="You are a store support agent. Use the tools. Answer in two short sentences.",
approval=ApprovalPolicy(tools=["cancel_*"], handler=ask_a_person), # waits in this process
expose_resources=True, # also lets the agent read the server's resources
)
try:
message = {"role": "user", "content": "Please cancel order O-1002. How much will I get back?"}
result = await agent.ainvoke({"messages": [message]})
messages = result["messages"]
print("tools called:", [c["name"] for m in messages for c in getattr(m, "tool_calls", [])])
print(messages[-1].content)
finally:
await agent.shutdown()
asyncio.run(main())APPROVAL NEEDED: cancel_order({'order_id': 'O-1002'}) {'destructiveHint': True} -> approved
tools called: ['get_order', 'read_resource', 'cancel_order']
I've cancelled order O-1002 and you will receive a full refund of $219.00.The server needed no changes. Three things differ from the LangChain version. The connection is a servers= entry, not an adapter. The approval is a handler that runs before every tool matching cancel_* and receives its MCP annotations; the run waits for the answer, so there is no checkpointer or resume loop, and no survival across a restart. And expose_resources=True gave the agent a read_resource tool, which it used to read the refund policy.
The same works the other way. If you have LangChain tools that other clients need, langchain-mcp-adapters can serve them from an MCP SDK FastMCP server:
"""The other direction: serve an existing LangChain tool over MCP (langchain-mcp-adapters, mcp<2)."""
import asyncio
from langchain.tools import tool
from langchain_mcp_adapters.tools import to_fastmcp
from mcp.server.fastmcp import FastMCP
@tool
def refund_amount(amount_usd: float, delivered: bool) -> float:
"""Work out the refund for an order: full price, minus $15 return shipping if it was delivered."""
return round(amount_usd - (15.0 if delivered else 0.0), 2)
server = FastMCP("refunds", tools=[to_fastmcp(refund_amount)])
async def check(): # what an MCP client would see; use server.run() to serve it for real
print([t.name for t in await server.list_tools()])
print(await server.call_tool("refund_amount", {"amount_usd": 49.0, "delivered": True}))
asyncio.run(check())['refund_amount']
[TextContent(type='text', text='34.0', annotations=None, meta=None)][15]
The complete example
The server is store_server.py from step 1. This is the agent, with MCP tools, a local tool and approval for destructive tools:
"""A LangChain agent that mixes MCP tools with a local LangChain tool and gates destructive MCP tools."""
import asyncio
from langchain.agents import create_agent
from langchain.agents.middleware import HumanInTheLoopMiddleware
from langchain.mcp import MCPAdapter
from langchain.tools import tool
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import Command
URL = "http://127.0.0.1:8080/mcp"
PROMPT = "You are a store support agent. Use the tools. Answer in two short sentences."
@tool
def refund_amount(amount_usd: float, delivered: bool) -> float:
"""Work out the refund for an order: full price, minus $15 return shipping if it was delivered."""
return round(amount_usd - (15.0 if delivered else 0.0), 2)
def is_destructive(t) -> bool: # read the MCP annotation the server declared
return t.metadata.get("mcp", {}).get("tool", {}).get("annotations", {}).get("destructive_hint", False)
def ask_a_person(action: dict) -> dict: # stand-in for a Slack message or an approval UI
print(f"APPROVAL NEEDED: {action['name']}({action['args']}) -> approved")
return {"type": "approve"}
async def main():
async with MCPAdapter(URL) as store:
mcp_tools = await store.list_tools()
gated = {t.name: {"allowed_decisions": ["approve", "reject"]} for t in mcp_tools if is_destructive(t)}
agent = create_agent(
"openai:gpt-5-mini",
tools=[*mcp_tools, refund_amount], # MCP tools and a LangChain tool, side by side
system_prompt=PROMPT,
middleware=[HumanInTheLoopMiddleware(interrupt_on=gated)],
checkpointer=InMemorySaver(), # an interrupt needs a checkpointer
)
config = {"configurable": {"thread_id": "ticket-7"}}
message = {"role": "user", "content": "Please cancel order O-1002. How much will I get back?"}
result = await agent.ainvoke({"messages": [message]}, config, version="v2")
while result.interrupts: # the run paused before a destructive MCP tool
decisions = [ask_a_person(a) for i in result.interrupts for a in i.value["action_requests"]]
result = await agent.ainvoke(Command(resume={"decisions": decisions}), config, version="v2")
messages = result.value["messages"]
print("tools called:", [c["name"] for m in messages for c in getattr(m, "tool_calls", [])])
print(messages[-1].content)
asyncio.run(main())[16]
Running it in production
Transport and authentication
Use Streamable HTTP for servers shared across services, and stdio for a server that belongs to one agent on one machine. An HTTP server that anyone can reach can be called by anyone, so put authentication in front of it, such as bearer tokens or OAuth. LangChain's MCP authentication docs show the client side, and MCP server authentication with JWT and OAuth shows the server side.
Trust and approvals
Tool results from an MCP server are untrusted input. A compromised or careless server can return text that tries to steer the model. Gate tools that change data, as in step 4, and connect only servers you trust. MCP security best practices lists the main risks.
Sessions and performance
By default, each tool call from an MCPAdapter tool opens the client, makes the call and releases it. To reuse one connection across a run, keep the async with MCPAdapter(...) block open while the agent runs, as the examples do. For services, LangChain's docs suggest per-run discovery, a shared connection pool and cached tool lists.
Versions
langchain.mcp is beta, and the MCP Python SDK changed its imports in 2.x. Pin langchain, fastmcp and mcp together, and run your agent tests after every upgrade.
[17]
Common problems and fixes
Symptom | Cause | Fix |
|---|---|---|
| MCPAdapter reads a string as a URL | Pass Path("server.py") or a StdioTransport |
| pip installed langchain-mcp-adapters 0.3.1 next to mcp 2.x | Use langchain.mcp, or pin langchain-mcp-adapters==0.3.2 in an environment with mcp<2 |
A stdio server fails with ModuleNotFoundError | A Path target runs the server with the agent's Python | Use StdioTransport(command="/path/to/server-env/bin/python", args=[...]) |
LangChainBetaWarning on every start | langchain.mcp is beta in 1.4 | Expected; filter the warning if it clutters logs |
The agent can't see a resource or prompt | MCPAdapter converts tools only | Call adapter.client.read_resource() or get_prompt() |
Two servers both have a search tool | Names collide | With an MCPConfig, tools get the server key as a prefix; in the older package, set tool_name_prefix=True |
The approval never pauses the run | No checkpointer, or the name in interrupt_on doesn't match | Pass checkpointer= and use the MCP tool's exact name |
promptise and langchain[mcp] won't install together | Promptise 1.4 pins mcp<2; LangChain's client needs mcp 2.x | Keep server and agent in separate environments and connect over HTTP |
[18]
Limitations
`langchain.mcp` is beta. The API in this guide may change in a minor release.
Partial protocol coverage. LangChain doesn't wrap resources or prompts yet, and its docs say a server request for sampling or roots raises NotImplementedError. Elicitation, where a server asks the user a question mid-call, is supported as an interrupt.
Python only. LangChain.js has its own MCP adapters.
We make Promptise. It is used for the server, in step 6 and in the three-way comparison. We sourced the LangChain and MCP cells from their docs and listed where LangChain is the better choice. Any MCP server would work with the LangChain examples.
[19]
Frequently asked questions
LangChain vs MCP: is MCP a replacement for LangChain?
No. MCP standardizes how tools, resources and prompts are offered to an AI application. LangChain is a framework for building that application: the agent loop, memory, approvals and integrations. An MCP server can't run an agent by itself. You can replace LangChain tools with MCP tools, but you still need LangChain or another framework, or your own loop, to use them.
Does LangChain support MCP?
Yes. LangChain 1.4 has a built-in MCP client, langchain.mcp.MCPAdapter, installed with pip install "langchain[mcp]" and marked beta. It connects to servers over Streamable HTTP or stdio and converts their tools into LangChain tools for create_agent. The older langchain-mcp-adapters package with MultiServerMCPClient also still works.
What is langchain-mcp-adapters, and is it deprecated?
It is the original package that connects LangChain and LangGraph agents to MCP servers through MultiServerMCPClient. LangChain's docs now say the langchain.mcp namespace replaces it and provide a migration guide, but they don't give an end-of-support date. Version 0.3.2 pins mcp<2, so use it in an environment without langchain[mcp].
MCP vs LangChain tools: which should I use?
Use LangChain tools for logic that belongs to one agent and one codebase. Use MCP tools for systems several agents, apps or teams share, or when the tool needs its own credentials and deployment. The model sees both the same way, so you can mix them in one agent and move a tool to MCP later without changing the prompt.
Should I use LangChain or Promptise with MCP?
Both can use the same MCP servers, so the choice is about the agent. Choose LangChain if you need its integrations, LangGraph's checkpointed runs that survive restarts, or LangSmith. Choose Promptise if MCP is central: it connects servers without an adapter, gives the agent resources and prompts, puts approvals and budgets on the agent, and builds the servers too. Its approvals wait in-process, and 1.4 still pins mcp<2.
Can I expose LangChain tools as an MCP server?
Yes. langchain-mcp-adapters has to_fastmcp(), which turns a LangChain tool into a tool for the MCP SDK's FastMCP server, as step 6 shows. Tools with injected arguments are not supported. You can also rewrite the function as an MCP tool in any server framework.
Is LangGraph the same as MCP?
No. LangGraph is LangChain's runtime for stateful agents: graphs of steps, checkpoints and interrupts. MCP is a protocol for tools and context. A LangGraph agent uses MCP tools through the same adapter as a LangChain agent.
[20]
Next steps
Learn the protocol in depth: what is the Model Context Protocol.
Compare MCP with shell tools: MCP vs CLI for AI agents.
Connect several servers to one agent: connect MCP servers to an AI agent.
Build a server for production: build a production MCP server in Python.
Considering a different framework: LangChain alternatives.
Reference: the Promptise docs for MCP servers and the MCP client.