# Bridging Frameworks: Writing Universal Adapters for MCP & LangChain

Imagine you've built a brilliant tool that speaks MCP (Model Context Protocol). It can fetch stock prices, query a database, or generate images. Now, you want it to work inside LangChain or LangGraph. Different languages, different rules. It's like having a perfect guest who only speaks Spanish at a party where everyone else speaks Japanese.

This tutorial will teach you how to build a universal adapter so your MCP tools work seamlessly with LangChain and LangGraph. We'll demystify `langchain-mcp-adapters`, `MultiServerMCPClient`, `Interceptors`, `Multimodal Tool Content`, `Stateful Client Sessions`, and `Unified Tool Routing` — one concept at a time. No jargon, no skipped steps.

Each concept gets a plain-English definition, a real-world analogy, and an annotated code snippet. You'll finish with a working adapter and a cheat-sheet to remember it all.

---

<figure class="post-hero-image">
<img class="post-hero" src="/assets/images/posts/2026-05-20-bridging-frameworks-writing-universal-adapters-for-mcp-langchain.jpg" alt="Hero image for Bridging Frameworks: Writing Universal Adapters for MCP & LangChain" loading="lazy">
<figcaption>Architecture diagram generated by [Google Gemini 3.1 Flash Image](https://ai.google.dev)</figcaption>
</figure>

## The Adapter Framework: Your Trusty Translator

**Plain-English Definition:** The `langchain-mcp-adapters` library is a set of pre-built translator functions. It sits between your MCP tools (which speak the MCP protocol) and LangChain/LangGraph (which speak a different internal protocol). It converts tool definitions, call arguments, and results back and forth so both sides understand each other.

**How it works under the hood:** When you import the adapter, it inspects the MCP tool's specification (its `description`, `inputSchema`, and so on). It then creates a LangChain `BaseTool` with matching `name`, `description`, and `args_schema`. The adapter stores a reference to the original MCP tool's call function. When LangChain calls the tool, the adapter intercepts the call, translates the arguments into the MCP-expected format, makes the real MCP call, and translates the result back into a LangChain `ToolMessage`.

**Analogy:** Think of an embassy interpreter. The MCP tool is a foreign diplomat. LangChain is your government. The adapter (interpreter) listens to the diplomat, translates their statement into your government's official language, and translates your government's reply back for the diplomat.

**Code Example:**
```python
from langchain_mcp_adapters import adapt_tool
from mcp import ClientSession

# Assume we have an MCP tool called 'fetch_weather'
async with ClientSession(mcp_transport) as session:
    mcp_tool = await session.list_tools()  # returns a list of MCPTool objects
    # The adapter translates the MCP tool into a LangChain tool
    langchain_tool = adapt_tool(mcp_tool[0])
    # Now we can use it directly in a LangChain chain
    result = await langchain_tool.ainvoke({"city": "London"})
    print(result)  # LangChain ToolMessage with temperature, humidity, etc.

MultiServerMCPClient: The Global Address Book

Plain-English Definition: MultiServerMCPClient is a helper that can connect to several different MCP servers at once. Each server might host different tools (e.g., a database tool on one server, a calculator tool on another). The client aggregates the tool lists from all connected servers into one unified list.

How it works: When you create a MultiServerMCPClient, you pass a dictionary of server names to their connection parameters (like url or transport). The client then opens an async session to each server, calls their list_tools endpoint, and merges the resulting tool specifications into a single Python list. Under the hood, it uses asyncio tasks to connect to servers in parallel for speed.

Analogy: You are the manager of a large office with several departments (servers). Each department has its own contact list of experts (tools). The MultiServerMCPClient is your assistant who calls each department, gets their contact list, and pins them all to one physical board labeled “Company Experts.” You only need to look at one board.

Code Example:

from langchain_mcp_adapters import MultiServerMCPClient

# Define our MCP servers
server_config = {
    "weather": {"url": "http://localhost:8001/mcp"},
    "finance": {"url": "http://localhost:8002/mcp"},
}

# Create the multi-server client and fetch all tools
async with MultiServerMCPClient(server_config) as client:
    all_tools = await client.get_tools()
    # all_tools is a single list of LangChain-tools (already adapted)
    # We can use it directly in a LangChain agent
    agent = create_react_agent(llm, all_tools)

Interceptors: The Customs Officers

Plain-English Definition: Interceptors are middleware functions that sit between LangChain and the MCP adapter. They can inspect, modify, or block tool calls and responses. Think of them as customs officers at a border. Every call and response passes through them for a quick check.

How it works: When you attach an interceptor to an adapter, the adapter wraps every tool call with the interceptor. The interceptor receives the tool name and arguments before the call reaches the MCP server, and it receives the response before it reaches LangChain. It can log, modify, or reject (raise an exception) either stage. Common uses: rate-limiting, auditing, or adding authentication headers.

Analogy: You’re sending a package overseas. It must go through customs. The customs officer (interceptor) can open the package, check its contents, add a new label (modify the call), or seize it (reject it) if it violates rules.

Code Example:

from langchain_mcp_adapters import Interceptor

# Define an interceptor that logs every call
class LoggingInterceptor(Interceptor):
    async def before_call(self, tool_name: str, arguments: dict):
        print(f"[LOG] Calling tool: {tool_name} with args: {arguments}")
        return arguments  # must return the (possibly modified) arguments

    async def after_call(self, tool_name: str, result: str):
        print(f"[LOG] Tool {tool_name} returned: {result[:100]}...")
        return result  # must return the result

# Attach the interceptor to our adapter
adapted_tool = adapt_tool(mcp_tool, interceptors=[LoggingInterceptor()])

Multimodal Tool Content: More Than Just Text

Plain-English Definition: Some MCP tools return content that isn’t plain text — images, audio, CSV files, or complex data structures. Multimodal Tool Content is the adapter’s way of handling these non-text responses so LangChain can still work with them. The adapter converts them into structured ToolMessage objects with a content field that LangChain’s models can interpret (often as a list of dictionaries with a type key).

How it works: When the MCP tool returns a result with a content attribute (like an image binary), the adapter checks the contentType field in the result metadata. If it’s image/png, the adapter stores the bytes and creates a content block with {"type": "image", "source": {"type": "base64", "media_type": "image/png", "data": base64_encoded_bytes}}. LangChain’s ChatOpenAI model can then process this as an image message.

Analogy: You visit a library and ask for information. Sometimes the librarian (MCP tool) hands you a book (text). Other times, they hand you a poster (image), a recording (audio), or a USB stick (binary). The librarian’s assistant (adapter) tags each item with a label: “book,” “poster,” “recording,” so you (LangChain) know how to handle it.

Code Example:

# Assume an MCP tool 'generate_chart' returns an image
result = await langchain_tool.ainvoke({"chart_type": "bar"})
# result is now a ToolMessage with multimodal content
print(result.content)
# Output: [
#   {"type": "image", "source": {
#       "type": "base64",
#       "media_type": "image/png",
#       "data": "iVBORw0KGgo..."
#   }}
# ]

Gotcha: Not all LangChain model providers support all content types. Check the model’s documentation for supported type values (e.g., "text", "image", "tool_use").


Stateful Client Sessions: Keeping Memory Alive

Plain-English Definition: MCP servers can be stateful — they remember information between calls (like a user session or a database cursor). Stateful Client Sessions allow the adapter to maintain a single, persistent connection to an MCP server across multiple tool calls. This avoids re-authenticating or re-establishing state for every single call.

How it works: Instead of creating a new session for every tool call (which is the default for simple ad-hoc calls), you pass an existing ClientSession object to the adapter. The adapter reuses that session’s context, including any state the server has built (like a logged-in user profile or a conversation history). The session is closed only when you explicitly call session.close().

Analogy: You’re at a coffee shop. Making a separate order every time you want a refill would be tedious — you’d have to queue again. A stateful session is like having a loyalty card. The barista (MCP server) remembers your order preferences and you just say “same again.” The adapter is your friend who hands you the card at the start and collects it when you leave.

Code Example:

# Create a persistent session
async with ClientSession(mcp_transport) as session:
    # Authenticate once
    await session.initialize()
    await session.send_request("auth/login", {"user": "alice", "password": "secret"})

    # Now build an adapter that uses this session
    adapted_tool = adapt_tool(mcp_tool, session=session)

    # Call the tool multiple times; each call uses the same authenticated session
    await adapted_tool.ainvoke({"query": "my recent orders"})  # remembers Alice
    await adapted_tool.ainvoke({"query": "my profile"})       # still Alice

Gotcha: If the server session times out or becomes invalid, the adapter won’t automatically re-authenticate. You should catch MCPSessionError and handle reconnection logic manually.


Unified Tool Routing: One Dispatcher to Rule Them All

Plain-English Definition: Unified Tool Routing is the final piece: a single routing function that, given a tool name and arguments, automatically finds the correct MCP server and tool across a federation of servers, calls it, and returns the result. It combines MultiServerMCPClient and the adapter pattern into a single abstraction.

How it works: You register multiple MCP servers with the router (using a MultiServerMCPClient) and then provide the router to a LangChain agent as its single tool. When the agent requests a tool call, the router intercepts the call, checks a mapping of tool names to server IDs, forwards the call to the correct server via its adapter, and returns the result. The agent never needs to know about the underlying server topology.

Analogy: You’re a CEO who needs various departments to do tasks. Instead of directly calling each department head (server) and waiting, you have an executive assistant (router). You tell the assistant: “I need a financial report.” The assistant knows the finance tool is on the “finance server,” so they call it, get the result, and hand it back to you. You only talk to one person.

Code Example:

from langchain_mcp_adapters import UnifiedToolRouter, MultiServerMCPClient

# Set up servers
client = MultiServerMCPClient({
    "weather": {"url": "http://localhost:8001/mcp"},
    "finance": {"url": "http://localhost:8002/mcp"},
})

# Build the unified router
router = UnifiedToolRouter(client)

# The router can be passed as a single callable tool to an agent
# The agent calls it with just the tool name and args
result = await router.ainvoke({"tool_name": "get_stock_price", "arguments": {"symbol": "AAPL"}})
print(result)  # '150.32 USD'

Gotcha: If two servers register a tool with the same name, the router will raise a ToolNameConflictError or pick the first one based on configuration. Always ensure unique tool names across your server federation.


Comparison Summary

Concept Problem It Solves Key Output Analogy
langchain-mcp-adapters Translates between MCP and LangChain protocols LangChain BaseTool object Embassy interpreter
MultiServerMCPClient Connects to many MCP servers at once Unified list of LangChain tools Global address book
Interceptors Add middleware (logging, auth, rate-limiting) inside the adapter pipeline Modified call/response Customs officer
Multimodal Tool Content Handles non-text responses (images, audio) from MCP tools Structured ToolMessage content list Library assistant tagging items
Stateful Client Sessions Maintains persistent MCP server connection across calls Reusable, logged-in session Coffee loyalty card
Unified Tool Routing Single dispatch point for all tools across servers Router that forwards to correct server CEO’s executive assistant

Key Takeaways

  • langchain-mcp-adapters is the translation layer that creates LangChain-compatible tools from MCP tools.
  • MultiServerMCPClient aggregates tools from multiple MCP servers into one list.
  • Interceptors are middleware functions that run before/after every tool call, enabling logging, auth, or rate-limiting.
  • Multimodal Tool Content allows tools to return images, audio, or binary data, which the adapter converts into structured LangChain message blocks.
  • Stateful Client Sessions let you reuse an authenticated MCP session across many tool calls.
  • Unified Tool Routing provides a single entry point that automatically routes tool calls to the correct MCP server.

Start with one server and one tool. Add MultiServerMCPClient when you have a second server. Add Interceptors when you need monitoring. Keep stateful sessions for login-required tools. Finally, wrap everything in Unified Tool Routing when you need a clean API for your agent. Now you’re not just bridging frameworks — you’re architecting a tool ecosystem. Go build something extraordinary.