Mastering Model Context Protocol (MCP): Build AI Agents with LlamaIndex & LangGraph
This guide explains the Model Context Protocol (MCP), its architecture, and how to create and debug MCP servers and clients in Python, then shows how to integrate third‑party MCP servers with LlamaIndex or LangGraph to quickly build powerful LLM agents.
What is MCP
MCP (Model Context Protocol) is a unified protocol that enables large‑language‑model (LLM) applications—especially agents—to access external resources such as browsers, files, databases, or APIs without writing a custom adapter for each service. By providing a single, standardized interface, MCP simplifies integration, accelerates tool plug‑in, and isolates agents from changes in external interfaces.
Architecture
An MCP system consists of three layers:
MCP Server : a plug‑in service that implements tool, resource, and prompt endpoints. It runs as an independent process and handles all external calls.
MCP Client SDK : a library used by the LLM application to communicate with the server via a standard protocol (typically stdio/stdout streams in local mode).
LLM Application : the user‑facing agent or chatbot that talks only to the MCP Server, delegating all external interactions to the server.
In local mode the client spawns the server as a subprocess and exchanges JSON messages over standard input/output.
Creating a simple MCP Server
Install the SDK: pip install mcp Define a server that offers a single calculator tool:
# server_demo.py
from mcp.server.fastmcp import FastMCP
# Create a FastMCP instance named "demo"
mcp = FastMCP("demo")
@mcp.tool()
def calculate(expression: str) -> float:
"""Evaluate a basic arithmetic expression such as "1 + 2 * 3".
The implementation can use Python's <code>eval</code> with safety checks or a dedicated parser.
"""
# Example safe implementation (placeholder)
# result = safe_eval(expression)
# return float(result)
... # implementation omitted for brevity
if __name__ == "__main__":
# Run the server using the stdio transport so the client can launch it as a subprocess
mcp.run(transport='stdio')The if __name__ == "__main__" block ensures the server starts when the client launches the script.
Creating an MCP Client
Use the stdio client and ClientSession to start the server and call its tools:
# client_demo.py
from mcp.client.stdio import stdio_client
from mcp import ClientSession, StdioServerParameters
import asyncio
# Configuration tells the client how to start the server process
server_params = StdioServerParameters(
command="python",
args=["./server_demo.py"],
env=None,
)
async def main():
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
print('
Calling tool...')
# Call the "calculate" tool defined in the server
result = await session.call_tool("calculate", {"expression": "188*23-34"})
print(result.content)
asyncio.run(main())Running the client automatically launches server_demo.py as a child process and invokes the calculator tool.
Debugging the MCP Server
For pure server development you can start the server in inspector mode: mcp dev server_demo.py Then open http://localhost:5173 in a browser. The visual UI lets you invoke the server’s tools, view request/response payloads, and debug the implementation interactively.
Using third‑party MCP servers with LlamaIndex / LangGraph
Third‑party MCP servers (e.g., an ArXiv search server) can be installed and used without writing new adapters.
Installation (example for an ArXiv MCP server): uv tool install arxiv-mcp-server Configure the client to launch the external server:
import os
from mcp import StdioServerParameters
server_params_arxiv = StdioServerParameters(
command="uv",
args=["tool", "run", "arxiv-mcp-server", "--storage-path", "./storage"],
env={**os.environ},
)Convert the server’s tools into LLM‑compatible tools using McpToolSpec from LlamaIndex, then build a FunctionCallingAgent (or a LangGraph React agent) that can query ArXiv, download papers, or invoke any other tool provided by the MCP server.
import asyncio
from mcp.client.stdio import stdio_client
from mcp import ClientSession
from llama_index.agent import FunctionCallingAgent
from llama_index.tools.mcp import McpToolSpec
async def main():
async with stdio_client(server_params_arxiv) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
# List available tools for debugging
tools = await session.list_tools()
print("Available tools:", [t.name for t in tools.tools])
# Convert MCP tools to LlamaIndex tool objects
mcp_tool_spec = McpToolSpec(session)
tool_list = await mcp_tool_spec.to_tool_list_async()
# Create a simple function‑calling agent
agent = FunctionCallingAgent.from_tools(
tool_list,
llm=llm, # replace with your LLM instance
verbose=True,
system_prompt="You are an expert answering questions using tools."
)
while True:
q = input("
Enter question (q to quit): ")
if q.lower() == "q":
break
response = await agent.achat(q)
print(response)
asyncio.run(main())This agent can now answer queries that require external knowledge, such as searching ArXiv or downloading a paper.
Key Resources
Official MCP documentation: https://modelcontextprotocol.io/introduction
Curated list of MCP servers: https://github.com/punkpeye/awesome-mcp-servers
Convert stdio‑based MCP servers to SSE remote servers: https://github.com/supercorp-ai/supergateway
LangChain MCP adapters: https://github.com/langchain-ai/langchain-mcp-adapters
AI Large Model Application Practice
Focused on deep research and development of large-model applications. Authors of "RAG Application Development and Optimization Based on Large Models" and "MCP Principles Unveiled and Development Guide". Primarily B2B, with B2C as a supplement.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
