ahad.

We stopped writing custom tool schemas. We use MCP now.

AK
Ahad KhanAI Engineer
June 5, 2026
4 min read
MCPFastAPILangGraphTool Calling
We stopped writing custom tool schemas. We use MCP now.Click to expand

Every 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:

  1. The underlying Python function implementation.
  2. A Pydantic schema class specifying argument descriptions for OpenAI/Anthropic function calling formats.
  3. An entry in a central dictionary mapping string names to callable functions.
  4. 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.

Loading diagram...

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

bash
1pip install mcp fastmcp fastapi uvicorn pydantic

2. Implementation (mcp_server.py)

Here is a production-ready MCP server exposing database lookup and access validation tools:

python
1from fastmcp import FastMCP
2from pydantic import BaseModel, Field
3import asyncpg
4
5# Initialize the MCP server
6mcp = 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 call
19 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) transport
28 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.

python
1import asyncio
2from mcp import ClientSession
3from mcp.client.sse import sse_client
4from langchain_core.tools import Tool
5from langgraph.prebuilt import ToolNode
6from langgraph.graph import StateGraph, START, END
7
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].text
23
24 langchain_tool = Tool(
25 name=tool.name,
26 description=tool.description,
27 func=None,
28 coroutine=_call_mcp_tool
29 )
30 langchain_tools.append(langchain_tool)
31
32 return langchain_tools
33
34# Use the loaded tools inside a LangGraph ToolNode
35# 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:

  1. Network Overhead over SSE: Transporting tool calls over HTTP SSE introduces minor network latency (~5–15ms) compared to direct in-process Python function execution.
  2. 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.
  3. 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.