MCP vs LangChain Tools
Picking between MCP and LangChain tools comes down to one question: are you building a tool that many different agents and apps need to reuse, or are you wiring up a tool for a single LangChain application? MCP (Model Context Protocol) is a standardized, transport-level protocol that exposes tools, resources, and prompts to any compatible client, while LangChain tools are a Python/JavaScript abstraction meant to be called from inside a LangChain or LangGraph agent. Both let a model call functions with structured arguments, but they solve different layers of the problem. This article walks through the actual code for each, where they overlap, and how teams commonly run them together.
What MCP Actually Is
MCP is an open protocol, originally released by Anthropic, that standardizes how an AI application (the "host") talks to external tools, files, and data sources (the "servers"). Instead of writing a custom function-calling integration for every app that wants access to your database, your CRM, or your file system, you write one MCP server. Any MCP-compatible client, Claude Desktop, an IDE like Cursor or Windsurf, a custom agent runtime, can connect to that server and immediately see its tools.
The protocol defines three primitives:
- Tools: functions the model can invoke, with a name, description, and JSON Schema for arguments.
- Resources: read-only data the client can pull in, like a file or a database row.
- Prompts: reusable prompt templates the server can hand to the client.
MCP servers talk over stdio (for local processes) or HTTP with server-sent events (for remote services). The client discovers tools dynamically by calling a list_tools method, so nothing about the tool set needs to be hardcoded into the client's source code.
What LangChain Tools Actually Are
LangChain tools are a much narrower concept: a Python or JavaScript object with a name, a description, an argument schema, and a function body, wrapped so that a LangChain agent or LangGraph graph can call it during its reasoning loop. There is no protocol, no client-server split, and no network boundary by default. The tool lives in the same process as your agent code (unless you explicitly wrap a remote API call inside it).
LangChain tools became the default because LangChain popularized the ReAct-style agent loop in Python: the model proposes a tool call, LangChain's executor runs the corresponding Python function, and the result gets fed back into the next model call. This is fast to prototype and works well for a single application, but the tool definitions are tightly coupled to that one codebase.
MCP vs LangChain Tools: The Core Difference
The clearest way to frame MCP vs LangChain tools is: MCP standardizes tool *distribution*, LangChain standardizes tool *execution inside an agent framework*. MCP answers "how do arbitrary clients discover and call this tool over the wire." LangChain answers "how do I write a Python function and hand it to my agent's reasoning loop."
A few concrete differences:
- Coupling. A LangChain tool is a Python object imported directly into your agent script. An MCP tool is a service you connect to; your agent doesn't import its source code at all.
- Reuse across apps. Ship an MCP server once and every MCP client (chat apps, IDEs, other agents) gets the tool for free. A LangChain tool only helps other LangChain (or LangGraph) codebases, and even then only if they import your Python package.
- Process boundary. MCP tools typically run as a separate process or remote service, which gives you isolation, independent versioning, and the ability to write the server in a different language than the client. LangChain tools run in-process by default.
- Discovery. MCP clients call
list_toolsat connection time and get schemas dynamically. LangChain tools are registered explicitly in code, usually as a Python list passed to the agent constructor. - Scope. MCP is a full protocol (tools, resources, prompts, sampling). LangChain tools are one piece of a much larger orchestration framework (chains, memory, retrievers, agents, LangGraph state machines).
They are not mutually exclusive. In practice, an MCP server's tools are frequently wrapped as LangChain tools inside a LangChain agent, letting you keep the protocol-level portability of MCP while still using LangChain/LangGraph for orchestration.
Building a Tool the LangChain Way
Here is a minimal LangChain tool, defined with the @tool decorator, and wired into an agent.
from langchain_core.tools import tool
from langchain.agents import create_agent
@tool
def get_order_status(order_id: str) -> str:
"""Look up the current shipping status for a given order id."""
orders = {
"A100": "shipped",
"A101": "processing",
"A102": "delivered",
}
return orders.get(order_id, "unknown order id")
@tool
def calculate_refund(order_total: float, restocking_fee_pct: float = 5.0) -> float:
"""Calculate the refund amount after a restocking fee percentage."""
fee = order_total * (restocking_fee_pct / 100)
return round(order_total - fee, 2)
agent = create_agent(
model="claude-sonnet",
tools=[get_order_status, calculate_refund],
system_prompt="You help customers check orders and calculate refunds.",
)
result = agent.invoke({
"messages": [{"role": "user", "content": "What's the status of order A101, and if I cancel it, what's my refund on a $80 order?"}]
})
print(result["messages"][-1].content)Everything here lives in one Python file. The tool schema is inferred from the function signature and type hints, the docstring becomes the tool description the model sees, and create_agent wires the tools into the reasoning loop. This is genuinely the fastest way to get a working agent with a couple of tools, and it is the right call when the tools only ever need to be called from this one application.
Building the Same Tools as an MCP Server
Now the same two tools, exposed as an MCP server using the Python MCP SDK's FastMCP helper. Any MCP client, not just LangChain, can use this.
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("order-tools")
@mcp.tool()
def get_order_status(order_id: str) -> str:
"""Look up the current shipping status for a given order id."""
orders = {
"A100": "shipped",
"A101": "processing",
"A102": "delivered",
}
return orders.get(order_id, "unknown order id")
@mcp.tool()
def calculate_refund(order_total: float, restocking_fee_pct: float = 5.0) -> float:
"""Calculate the refund amount after a restocking fee percentage."""
fee = order_total * (restocking_fee_pct / 100)
return round(order_total - fee, 2)
if __name__ == "__main__":
mcp.run(transport="stdio")Run this as its own process (python order_server.py). Any MCP client that knows how to spawn it and speak stdio can now call get_order_status and calculate_refund without ever seeing the Python source. Register it in a client config like this:
{
"mcpServers": {
"order-tools": {
"command": "python",
"args": ["order_server.py"]
}
}
}Notice what changed structurally, not just syntactically. The tool functions themselves are almost identical, @tool became @mcp.tool(), but the deployment model is completely different. The LangChain version only works inside the process that imported it. The MCP version is a standalone service that Claude Desktop, a custom Node.js agent, another Python agent, or a LangChain agent can all connect to independently.
Calling an MCP Server from Inside LangChain
Because MCP and LangChain solve different layers, the common real-world pattern is to keep your tools as MCP servers and pull them into LangChain using an adapter. The langchain-mcp-adapters package converts MCP tool definitions into LangChain-compatible tool objects at runtime.
import asyncio
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain.agents import create_agent
async def main():
client = MultiServerMCPClient(
{
"order_tools": {
"command": "python",
"args": ["order_server.py"],
"transport": "stdio",
},
"search": {
"url": "http://localhost:8931/mcp",
"transport": "streamable_http",
},
}
)
tools = await client.get_tools()
agent = create_agent(
model="claude-sonnet",
tools=tools,
system_prompt="You help customers with orders and general lookups.",
)
result = await agent.ainvoke({
"messages": [{"role": "user", "content": "Check order A102 for me."}]
})
print(result["messages"][-1].content)
asyncio.run(main())client.get_tools() connects to every configured MCP server, calls list_tools on each, and wraps the results as StructuredTool objects LangChain already knows how to call. Your agent code doesn't need to know or care that get_order_status originally lived in a separate MCP process, and the same order_server.py can simultaneously serve a completely different client, like Claude Desktop or an internal Slack bot, with zero duplicated tool code.
When MCP Is the Better Choice
Reach for MCP when:
- The tool needs to be usable from more than one application or agent framework, not just one LangChain codebase.
- You want a language-agnostic boundary; the server can be Python while a client is written in TypeScript.
- You're exposing internal company systems (databases, ticketing, internal APIs) as a standard interface that any future agent should be able to use, including ones your team hasn't built yet.
- You want process isolation between the tool execution and the model-calling application, useful for security, rate-limiting, or running the tool server on a different machine.
- You're building for an ecosystem where the end user picks the client (Claude Desktop, an IDE, a custom app) and you just need your tool to show up there.
When LangChain Tools Are the Better Choice
Stick with plain LangChain tools when:
- You're building a single application with a fixed set of tools that will never be shared elsewhere.
- You need the tool to have direct, fast, in-process access to application state, database connections already open in the same process, request-scoped auth tokens, or in-memory caches, without serialization overhead.
- You want the fastest possible prototyping loop; a decorated Python function is less setup than standing up a server, even a lightweight one.
- Your orchestration is already deeply built on LangGraph state, and the tool is really a thin wrapper around logic that belongs in that graph anyway.
- You don't need dynamic tool discovery; your tool list is small, known at build time, and doesn't change per deployment.
Performance and Operational Considerations
MCP tools add a serialization and IPC hop compared to a plain in-process LangChain tool call. For most tools, this overhead (typically low single-digit milliseconds for stdio, slightly more for HTTP) is irrelevant next to the model's own latency, but it matters for extremely hot-path, high-frequency tool calls inside a tight loop.
Versioning also behaves differently. An MCP server can be updated and redeployed independently of every client that uses it, which is valuable when a platform team owns the tool and application teams consume it. A LangChain tool's version is whatever's pinned in that repo's dependency file; changing behavior means shipping a new release of that specific app.
Testing an MCP server is closer to testing any backend service: you can call it directly with the MCP CLI inspector or write integration tests against the running process, independent of any particular agent. Testing a LangChain tool is closer to unit-testing a regular function, since it's just Python.
Can You Use Both Together
Yes, and this is the pattern most production teams land on for anything beyond a single-app prototype. Write your tools once as MCP servers so they're portable across every client your organization uses, then pull them into LangChain (or LangGraph, or any other framework) with an MCP adapter when you need LangChain's orchestration, memory, and graph features on top. You get MCP's distribution model and LangChain's agent-building ergonomics without duplicating tool logic in two places.
A reasonable default for a new project in 2026: prototype quickly with plain @tool functions while you're figuring out what the agent actually needs to do, then migrate any tool that turns out to be broadly useful, a database lookup, an internal API call, a search integration, into an MCP server once you know it needs to be shared across more than one client or team.
FAQ
Is MCP a replacement for LangChain? No. MCP is a protocol for exposing and discovering tools across clients. LangChain is an orchestration framework for building agents, chains, and graphs. They operate at different layers and are commonly used together via an MCP adapter.
Do I need to rewrite my LangChain tools to use MCP? Not necessarily. You can keep tools as plain LangChain @tool functions for as long as they only need to serve one application. Migrate to an MCP server only when another client or team needs the same tool.
Does using MCP make tool calls slower? There is a small IPC or network hop compared to an in-process function call, but for most tools this is negligible next to model inference latency. It only matters for extremely high-frequency, low-latency tool calls.
Can an MCP server be written in a different language than the LangChain agent that calls it? Yes. That's one of MCP's main advantages. The server just needs to implement the protocol over stdio or HTTP; the client doesn't need to know what language the server is written in.
What's the fastest way to try both side by side? Write two versions of the same simple tool, one with LangChain's @tool decorator, one with FastMCP's @mcp.tool() decorator, exactly as shown above. Run the LangChain version directly in an agent script, then connect the MCP version through langchain-mcp-adapters and compare how each is registered, discovered, and called.
Which one should a beginner start with? Start with LangChain's @tool decorator for a single prototype agent; it has less setup and gets you to a working demo fastest. Move to MCP once you have a tool you want to reuse across more than one project or client.
BootcampA 30-day guided bootcamp: build, harden and ship a production autonomous agent from scratch.
AI AgentsUnderstand how AI agents really work: the loop, the tools, the memory, and why most agent projects fail.