Click to expandEvery team building production AI agents eventually maintains an internal tool registry. You start with three custom function schemas, but six months later you have 40 tool scripts, five schema conversion adapters for different LLM SDKs, and zero consistency in error handling.
Model Context Protocol (MCP) fixes this maintenance tax. Instead of wrapping every database query, internal API call, and file parser in proprietary function-calling schemas, MCP provides a standardized protocol for discovering and executing tools over transport layers like stdio and HTTP SSE.
Here is how we migrated our agent tooling architecture from custom Pydantic schemas to an MCP server built with FastAPI and integrated with LangGraph.
The Tool Schema Maintenance Tax
Before adopting MCP, adding a new database lookup tool to our agent runtime required updating four separate files:
- The underlying Python function implementation.
- A Pydantic schema class specifying argument descriptions for OpenAI/Anthropic function calling formats.
- An entry in a central dictionary mapping string names to callable functions.
- Error handler wrappers to convert unhandled exceptions into LLM-friendly JSON strings.
When we switched or tested different orchestration frameworks, we had to rewrite the glue code. If two teams built internal tool libraries, they could not share them without copy-pasting code and resolving conflicting dependency versions.
MCP replaces this custom glue code with three standard primitives:
- Tools: Callable functions with input schemas that execute side effects or calculations.
- Resources: Read-only data sources (documents, logs, database records) exposed via URIs.
- Prompts: Reusable prompt templates exposed directly from the server.
Because the tool definition lives on the MCP server, any agent client (Claude Desktop, custom LangGraph graphs, IDE assistants) connects to the server and consumes the exact same tools without code changes.
Building an MCP Server with FastAPI and FastMCP
Using FastMCP (a high-level library for building MCP servers in Python), you can convert standard FastAPI endpoints or standalone Python functions into fully compliant MCP tools with minimal setup.
1. Installation
1pip install mcp fastmcp fastapi uvicorn pydantic2. Implementation (mcp_server.py)
Here is a production-ready MCP server exposing database lookup and access validation tools:
1from fastmcp import FastMCP2from pydantic import BaseModel, Field3import asyncpg4
5# Initialize the MCP server6mcp = FastMCP("IT-Access-Tools")7
8class TicketStatusRequest(BaseModel):9 ticket_id: str = Field(..., description="The unique IT ticket identifier, e.g. TICKET-9482")10
11class ProvisionAccessRequest(BaseModel):12 user_id: str = Field(..., description="The corporate employee ID")13 system_role: str = Field(..., description="The requested system access role, e.g. 'prod-db-read'")14
15@mcp.tool()16async def check_ticket_status(request: TicketStatusRequest) -> str:17 """Retrieves current processing state and authorization level for an IT access ticket."""18 # Simulated DB call19 return f"Ticket {request.ticket_id}: Status='PENDING_APPROVAL', RequestedRole='prod-db-read', RiskScore=0.12"20
21@mcp.tool()22async def provision_user_access(request: ProvisionAccessRequest) -> str:23 """Executes provision granted for a validated employee ID and role."""24 return f"SUCCESS: Role '{request.system_role}' granted to User '{request.user_id}'. Audit log record #88392 created."25
26if __name__ == "__main__":27 # Runs the server using HTTP Server-Sent Events (SSE) transport28 mcp.run(transport="sse", port=8000)Running python mcp_server.py starts an HTTP server on port 8000 exposing the /sse endpoint for tool discovery and execution.
Wiring MCP Tools into a LangGraph StateGraph
To consume these tools inside a LangGraph agent, we use the MCP client to inspect available tools dynamically and convert them into standard LangChain StructuredTool instances.
1import asyncio2from mcp import ClientSession3from mcp.client.sse import sse_client4from langchain_core.tools import Tool5from langgraph.prebuilt import ToolNode6from langgraph.graph import StateGraph, START, END7
8async def load_mcp_tools(server_url: str):9 """10 Connects to an external MCP server over SSE, discovers tools,11 and returns them as executable LangChain tools.12 """13 async with sse_client(server_url) as (read, write):14 async with ClientSession(read, write) as session:15 await session.initialize()16 mcp_tools = await session.list_tools()17
18 langchain_tools = []19 for tool in mcp_tools.tools:20 async def _call_mcp_tool(tool_name=tool.name, **kwargs):21 result = await session.call_tool(tool_name, arguments=kwargs)22 return result.content[0].text23
24 langchain_tool = Tool(25 name=tool.name,26 description=tool.description,27 func=None,28 coroutine=_call_mcp_tool29 )30 langchain_tools.append(langchain_tool)31
32 return langchain_tools33
34# Use the loaded tools inside a LangGraph ToolNode35# tool_node = ToolNode(load_mcp_tools("http://localhost:8000/sse"))Known Trade-Offs & Limitations
MCP solves schema duplication, but it is not a silver bullet. When evaluating MCP for your architecture, consider these operational trade-offs:
- Network Overhead over SSE: Transporting tool calls over HTTP SSE introduces minor network latency (~5–15ms) compared to direct in-process Python function execution.
- Authentication Scoping: The initial MCP specification does not dictate standard OAuth2 or token propagation patterns per tool call. You must handle authentication headers at the transport layer.
- Streaming Tool Output: Large tool outputs (e.g., retrieving a 10MB log file) stream as discrete text blocks rather than chunked byte streams.
Moving to MCP decoupled our tool development from our agent runtime. Our engineering team builds and tests tools once in Python, and any agent graph across our systems consumes them immediately without boilerplate schema maintenance.
Read Next

Streaming LangGraph state updates to the browser in real time
WebSockets add complexity you don't need for agent telemetry. An async FastAPI generator and the browser's native EventSource API is 30 lines of code that actually works.

I Run an AI Agent on a VPS. Here's My Actual Setup
A walkthrough of my real OpenClaw deployment: 13 Telegram topics, GPT-5.2 on Azure free tier, Playwright browser automation with anti-bot bypass, and a massive ecosystem of over 5,700 ClawHub skills powering my Second Brain. Pulled directly from my live droplet.