Bridge the Gap: Exposing FastAPI Endpoints as AI Tools with MCP
Integrating MCP servers via SSE with langchain agents
Have you ever built a neat API with FastAPI and wished your AI assistant or LangChain agent could directly use its functionalities? What if you could expose those API endpoints as discoverable "tools" without tightly coupling them to your agent code? The Model Context Protocol (MCP) and libraries like fastapi-mcp
and langchain-mcp-adapters
make this possible!
Today, I want to walk you through a practical example project demonstrating how to:
Take a standard FastAPI application.
Use
fastapi-mcp
to automatically expose its endpoints as MCP-compliant tools.Build a LangChain agent using
langchain-mcp-adapters
that connects to the FastAPI server, discovers these tools, and uses them to answer queries.(Bonus) Configure the Cursor code editor to also use these tools.
This setup allows you to decouple your tool implementations (your FastAPI endpoints) from your agent logic, enabling cleaner architecture and tool reusability across different agents or clients.
What is MCP?
Before diving in, let's clarify MCP. Think of the Model Context Protocol as a standardized contract or language for how AI models (like LLMs in agents) and applications can interact with external capabilities, which MCP calls "tools." A tool could be anything from a simple function call to a complex API endpoint or even a database query interface.
MCP defines specifications for:
Discovery: How does a client (like an agent or an editor) ask a server what tools it offers?
Description: How does the server describe a tool's purpose, required inputs (parameters), and expected outputs? This often leverages standards like JSON Schema.
Execution: How does the client request the server to run a specific tool with certain arguments, and how does the server return the result?
(Optional) Authentication/Authorization: How can tool usage be secured?
By adhering to this protocol, different tools and clients can interoperate more easily.
Prerequisites: What You'll Need
To follow along and run the example project, ensure you have:
Python: Version 3.10 or higher is recommended.
uv
: A fast Python package manager written in Rust. While you can usepip
, the instructions useuv
. (Installation).Node.js and npm: Required only if you want to use the optional (but helpful) MCP Inspector tool (
npx @modelcontextprotocol/inspector
). Download from nodejs.org.Git: For cloning the example repository.
OpenAI API Key: The LangChain client example uses an OpenAI model (like GPT-4o or GPT-3.5 Turbo). You'll need to get an API key from OpenAI and store it securely.
Project Setup & Key Libraries
Clone the Repository: Get the code onto your local machine.
git clone https://github.com/SDCalvo/sse-mcp-and-langchain-client-example
Initialize Environment (if needed): If the project doesn't have a
pyproject.toml
, initializeuv
:
uv init
Create Virtual Environment: Isolate project dependencies:
uv venv # Creates a .venv directory
(Activate the environment in your shell as needed, though
uv run
handles it)Install Dependencies: Install all required packages:
# Installs everything needed for the server and client
uv pip install fastapi "uvicorn[standard]" fastapi-mcp langchain-mcp-adapters langgraph langchain-openai python-dotenv
Configure API Key: Create a file named
.env
in the project root and add your OpenAI key:
# .env
OPENAI_API_KEY=sk-your_openai_api_key_here
Key Libraries We Just Installed:
fastapi
: The modern, high-performance web framework for building APIs with Python.uvicorn
: An ASGI server needed to run our FastAPI application.[standard]
includes recommended extras.fastapi-mcp
: The star on the server-side! This library integrates with FastAPI to expose its routes as MCP tools.langchain-mcp-adapters
: The client-side counterpart. Provides adapters (likeMultiServerMCPClient
) for LangChain agents to connect to MCP servers (including those created byfastapi-mcp
).langgraph
: A LangChain library for building reliable, stateful agent applications. We use itscreate_react_agent
helper.langchain-openai
: Provides LangChain integrations for OpenAI models.python-dotenv
: Used by the client to load the API key from the.env
file.
The FastAPI Server (main.py
)
This file sets up our basic web server and exposes its endpoints via MCP.
# main.py
from fastapi import FastAPI
from fastapi_mcp import FastApiMCP # Import the MCP library
# 1. Initialize FastAPI App
app = FastAPI(title="MCP FastAPI Server", version="0.1.0")
# 2. Define Standard FastAPI Endpoints
@app.get("/")
async def read_root():
"""Returns a simple welcome message."""
return {"message": "Welcome to the FastAPI MCP Server!"}
@app.get("/greet/{name}")
async def greet_user(name: str):
"""Greets the user by name."""
return {"message": f"Hello, {name}!"}
# 3. Initialize and Mount fastapi-mcp
# *** This MUST come AFTER defining the endpoints you want to expose ***
mcp = FastApiMCP(app) # Pass the FastAPI app instance
mcp.mount() # This adds the /mcp routes to the app
Breakdown:
app = FastAPI(...)
: Standard FastAPI initialization.@app.get(...)
: We define two simple API routes:/
: A basic root endpoint./greet/{name}
: An endpoint taking a path parametername
.Notice the docstrings!
fastapi-mcp
uses these (along with type hints and parameter names) to generate descriptions for the MCP tools.
mcp = FastApiMCP(app)
: This is where the magic starts. We create an instance ofFastApiMCP
, passing it our existing FastAPIapp
. The library introspects theapp
object, finding the routes defined before this point.mcp.mount()
: This crucial call modifies the FastAPIapp
by adding the necessary MCP-specific routes (usually prefixed under/mcp
, e.g.,/mcp/tools
,/mcp/tools/{tool_id}/run
). These routes handle the MCP discovery and execution protocol requests from clients.fastapi-mcp
uses the information gathered during initialization (routes, parameters, docstrings, types) to generate the tool schemas and handle incoming execution requests, mapping them back to calls to our original/
and/greet/{name}
functions.
Running the Server:
uvicorn main:app --reload --port 8000
Keep this terminal running.
The LangChain Client (langchain_client.py
)
This script acts as an MCP client, connecting to our server, discovering the tools, and using them within a LangChain agent.
# langchain_client.py (Simplified)
import asyncio
from dotenv import load_dotenv
from langchain_mcp_adapters.client import MultiServerMCPClient # MCP Client
from langgraph.prebuilt import create_react_agent # Agent framework
from langchain_openai import ChatOpenAI # LLM
load_dotenv() # Load OPENAI_API_KEY from .env
# 1. Configure MCP Server Connection
mcp_config = {
"local_fastapi": { # An arbitrary name for this server connection
"url": "http://127.0.0.1:8000/mcp", # URL of our FastAPI server's MCP endpoint
"transport": "sse" # Specify HTTP+SSE transport
}
}
# 2. Configure the LLM
llm = ChatOpenAI(model="gpt-4o")
async def run_agent():
print("Connecting to MCP server(s)...")
# 3. Initialize MCP Client
async with MultiServerMCPClient(mcp_config) as client:
print("Discovering tools...")
# 4. Discover Tools
tools = client.get_tools() # Fetches tools via MCP protocol
if not tools:
# Handle error: server not running or MCP not mounted correctly
return
print(f"Discovered tools: {[tool.name for tool in tools]}") # e.g., ['read_root__get', 'greet_user_greet__name__get']
# 5. Create LangGraph Agent
# Pass the discovered MCP tools directly to the agent
agent_executor = create_react_agent(llm, tools)
print("Agent created. Ready for query.")
# 6. Run Queries
query1 = "What is the welcome message from the FastAPI server?"
print(f"Invoking agent with query: '{query1}'")
# Stream events to see agent thought process and tool calls
async for event in agent_executor.astream_events({"messages": [("user", query1)]}, version="v1"):
# Print intermediate steps (LLM thoughts, tool calls, tool outputs)
# ... (event handling logic as in original file) ...
query2 = "Greet the user 'LangChain'"
print(f"Invoking agent with query: '{query2}'")
result2 = await agent_executor.ainvoke({"messages": [("user", query2)]})
print(f"Result 2: {result2}")
if __name__ == "__main__":
asyncio.run(run_agent())
Breakdown:
mcp_config
: A dictionary defining the MCP servers to connect to. We give our local server a name (local_fastapi
) and specify its MCP endpoint URL (http://127.0.0.1:8000/mcp
). Crucially, we set"transport": "sse"
.llm = ChatOpenAI(...)
: Standard LangChain LLM setup.async with MultiServerMCPClient(mcp_config) as client:
: This initializes the MCP client using our configuration. Theasync with
ensures proper connection handling (setup and teardown).MultiServerMCPClient
can connect to multiple MCP servers if needed.tools = client.get_tools()
: This is the discovery step. The client sends a request to the server's MCP endpoint (/mcp/tools
). The server (handled byfastapi-mcp
) responds with a list of available tools and their descriptions (name, purpose, parameters).langchain-mcp-adapters
automatically converts these MCP tool descriptions into LangChain-compatibleTool
objects. The tool names (e.g.,read_root__get
) are often automatically generated by FastAPI/fastapi-mcp
based on the function name and path, though they can be customized (see Further Exploration).agent_executor = create_react_agent(llm, tools)
: We create a ReAct agent (a common agent type that reasons and acts), directly passing thetools
list obtained from the MCP client. LangGraph and LangChain handle the logic of deciding when to call a tool based on the LLM's reasoning and the tool descriptions.agent_executor.astream_events(...) / agent_executor.ainvoke(...)
: We run queries through the agent. When the agent decides to use a tool (e.g.,read_root__get
),langchain-mcp-adapters
intercepts this, makes the appropriate MCP execution request tohttp://127.0.0.1:8000/mcp/tools/read_root__get/run
(or similar), sends the parameters, receives the result from the FastAPI server, and passes it back to the agent loop.
Running the Client:
In a second terminal (while the server is running):
uv run python langchain_client.py
Understanding the SSE Communication
We specified "transport": "sse"
in the client configuration. Why?
HTTP + Server-Sent Events (SSE): SSE is a standard web technology allowing a server to push data to a client asynchronously over a single, long-lived HTTP connection, once the initial connection is established.
fastapi-mcp
&langchain-mcp-adapters
: When using the"sse"
transport,fastapi-mcp
sets up the server-side handler for SSE connections on its/mcp
endpoint. Thelangchain-mcp-adapters
client initiates this SSE connection.How it's Used Here: While our simple tools (
read_root
,greet_user
) are request/response, SSE is the chosen transport mechanism byfastapi-mcp
for the primary MCP communication channel in this setup. It handles the initial handshake, tool discovery messages, and subsequent tool execution request/response cycles over this persistent connection. For tools that could potentially stream results back, SSE provides a natural fit, although the basic adapter usage here doesn't expose that streaming capability directly to the LangChain agent in this simple example. It primarily simplifies the connection management compared to constantly opening new HTTP requests for discovery and execution.
Benefits: Why Use This MCP Approach?
This FastAPI + MCP setup offers significant advantages, especially for larger projects:
Decoupling: Your core agent logic (in
langchain_client.py
) doesn't need to contain the implementation details of the tools. It only needs to know how to connect to an MCP server and use the discovered tools based on their descriptions. The tool implementation lives entirely within the FastAPI server (main.py
).Reusability: The same
fastapi-mcp
server can serve tools to multiple different clients. Imagine:A LangChain agent.
A Haystack agent.
A custom Python script.
The Cursor editor.
All connecting to
http://127.0.0.1:8000/mcp
and using the same underlyingread_root
andgreet_user
implementations without code duplication.
"MCP Server as a Service": You can deploy your FastAPI MCP server as a standalone microservice. Your applications (agents, etc.) just need the URL to its MCP endpoint. This promotes modularity and independent scaling.
Leverage Existing Infrastructure: If you already have FastAPI APIs, adding the MCP layer with
fastapi-mcp
is often much easier than rewriting that logic specifically for different agent frameworks.Standardization: By using MCP, you're building on an emerging standard, potentially making your tools compatible with future MCP clients and platforms.
Further Exploration
This example scratches the surface. The fastapi-mcp
library and MCP itself offer more:
Authentication/Authorization: Secure who can discover or execute tools (e.g., using FastAPI dependencies, API keys via headers, or full OAuth 2 flows).
langchain-mcp-adapters
supports passing necessary headers.Custom Tool Naming: Set explicit
operation_id
s in FastAPI routes for clearer MCP tool names instead of the auto-generated ones.Dynamic Tool Updates: Mechanisms exist to refresh the tool list if routes are added to the FastAPI app after the server starts.
Deployment Strategies: Mount the MCP server on a separate FastAPI app or server instance from your main API if needed.
Filtering & Customization: Control exactly which endpoints are exposed as tools and customize their descriptions.
This pattern of exposing APIs via MCP provides a powerful way to bridge the gap between traditional web services and the world of AI agents. Give it a try!
Let me know your thoughts or questions in the comments!
GitHub Repository
Example Project Repository: SDCalvo/sse-mcp-and-langchain-client-example
Documentation Links
fastapi-mcp Documentation: tadata-org/fastapi_mcpGitHub+1apidog+1
langchain-mcp-adapters Documentation: langchain-ai/langchain-mcp-adapterslangchain-ai.github.io+3GitHub+3GitHub+3
Model Context Protocol (MCP) Specification: modelcontextprotocol.io/specification