Engineering
March 19, 2025

Building a Server-Sent Events (SSE) MCP Server with FastAPI

Bob Remeika
,
Co-founder & CEO

The Model Context Protocol (MCP) has gained significant traction, with developers creating numerous MCP servers as standalone packages that communicate via stdio transport. Server-sent events (SSE) is the recommended approach for integrating an MCP server directly into your existing FastAPI application. However, due to limited documentation and examples, implementing SSE can be challenging.  This guide will walk you through creating an MCP server with SSE support in your FastAPI application.

Initialize project

First, we'll create a new project directory using uv init and install our dependencies. We need FastAPI for our web server and MCP for the model context protocol implementation. Then we'll set up the basic app structure with an app directory containing our Python modules.

# Create project
uv init fastapi_sse --bare

# Change to project directory
cd fastapi_sse

# Install FastAPI
uv add fastapi --extra standard

# Install MCP
uv add mcp

# Create fastapi app
mkdir app
touch app/__init__.py
touch app/main.py

Create the FastAPI Application

Let's start by creating our FastAPI application in app/main.py. We'll create a basic route to verify everything is working correctly. This route will serve as a health check endpoint that we can use to test our server:

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
def read_root():
    return {"Hello": "World"}

Test your server

To run the FastAPI development server:

uvicorn app.main:app --reload

Or using uv:

uv run uvicorn app.main:app --reload


The server will start at http://127.0.0.1:8000. Open this URL in your browser to see the JSON response {"Hello": "World"}.

Building the SSE Transport Layer with Starlette

Create a helper to initialize a Starlette app that we will mount in our FastAPI application by creating an app/sse.py file:

from mcp.server.fastmcp import FastMCP
from mcp.server.sse import SseServerTransport
from starlette.applications import Starlette
from starlette.routing import Mount, Route

def create_sse_server(mcp: FastMCP):
    """Create a Starlette app that handles SSE connections and message handling"""
    transport = SseServerTransport("/messages/")

    # Define handler functions
    async def handle_sse(request):
        async with transport.connect_sse(
            request.scope, request.receive, request._send
        ) as streams:
            await mcp._mcp_server.run(
                streams[0], streams[1], mcp._mcp_server.create_initialization_options()
            )

    # Create Starlette routes for SSE and message handling
    routes = [
        Route("/sse/", endpoint=handle_sse),
        Mount("/messages/", app=transport.handle_post_message),
    ]

    # Create a Starlette app
    return Starlette(routes=routes)

In this helper function, we're creating a Starlette application that will handle the Server-Sent Events (SSE) protocol for our MCP server. Let's break down what's happening:

  1. Transport Creation: We initialize an SseServerTransport with a path prefix (/messages/). This transport handles the SSE protocol details, including establishing connections and managing event streams.
  1. SSE Connection Handler: The handle_sse function manages SSE connections:
    - It uses the transport's connect_sse method to establish an SSE connection
    - The connection provides bidirectional streams for communication
    - We pass these streams to the MCP server's run method, which handles the MCP protocol communication
  1. Route Configuration: We set up two essential routes:
    - /sse/
    endpoint: Handles incoming SSE connections
    - /messages/
    mount: Processes messages sent from clients after the SSE connection is established
  1. Starlette Application: Finally, we create and return a Starlette application with our configured routes, which can be mounted in our FastAPI application

This design separates the SSE transport concerns from our FastAPI application logic, making it easier to maintain and extend. The Starlette app acts as a bridge between HTTP and the MCP protocol, converting HTTP requests and responses into the streams that MCP expects.

Mount the Starlette application in FastAPI

Now, let's update our FastAPI application to include the MCP server with SSE support. We'll modify our existing app/main.py file to import our SSE helper and mount it in our application:

from fastapi import FastAPI
from app.sse import create_sse_server
from mcp.server.fastmcp import FastMCP

app = FastAPI()
mcp = FastMCP("Echo")

# Mount the Starlette SSE server onto the FastAPI app
app.mount("/", create_sse_server(mcp))


@app.get("/")
def read_root():
    return {"Hello": "World"}

Here's what's happening:

  1. Initialize FastMCP: We create a FastMCP instance named "Echo" that will handle our MCP protocol functionality.
  1. Mount the Starlette App: We use FastAPI's mount() method to integrate our SSE server at the root path. This allows the SSE server to handle requests to /sse/ and /messages/ paths while our FastAPI routes handle everything else.
  1. Keep Original Endpoints: We maintain our original health check endpoint at the root path.

You should be able to get a successful response from your /sse route now. Open http://localhost:8000/sse in your browser. You should see a response that looks similar to this:

event: endpoint
data: /messages/?session_id=9bb7cf474d1e4e24832ee7cce54993f3

: ping - 2025-03-18 13:20:50.694707+00:00

Connecting to Your Server with an MCP Client

Now that your FastAPI MCP server is running, you can connect to it using an MCP client like Cursor. This requires configuring the client to recognize your server.

Locate or create your MCP configuration file (in Cursor, this is typically found in Settings > Cursor Settings > MCP > "+ Add new global MCP server"). Add the following configuration:

{
  "mcpServers": {
    "example-sse": {
      "url": "http://localhost:8000/sse",
      "env": {}
    }
  }
}

After saving this configuration:

  1. Restart your MCP client if necessary
  2. Look for "example-sse" in your available MCP servers list
  3. You should see a successful connection message in your client
  4. You might also notice additional log entries in your FastAPI server console indicating a new connection

At this point you should see a successful connection to your FastAPI MCP server. This is great, but our MCP server isn't very interesting yet. We need to add some tools.

Adding Tools, Resources, and Prompts

Now that we have a working MCP server with SSE support, let's enhance it by adding some functionality. The MCP Python SDK provides decorators that make it easy to expose tools, resources, and prompts to connected clients.

Let's modify our app/main.py file to add these capabilities:

from fastapi import FastAPI
from app.sse import create_sse_server
from mcp.server.fastmcp import FastMCP

app = FastAPI()
mcp = FastMCP("Echo")

# Mount the Starlette SSE server onto the FastAPI app
app.mount("/", create_sse_server(mcp))


@app.get("/")
def read_root():
    return {"Hello": "World"}


# Add MCP functionality with decorators
@mcp.resource("echo://{message}")
def echo_resource(message: str) -> str:
    """Echo a message as a resource"""
    return f"Resource echo: {message}"


@mcp.tool()
def echo_tool(message: str) -> str:
    """Echo a message as a tool"""
    return f"Tool echo: {message}"


@mcp.prompt()
def echo_prompt(message: str) -> str:
    """Create an echo prompt"""
    return f"Please process this message: {message}"

Let's break down what we're adding:

  1. Resource (@mcp.resource): Resources are identified by a URI pattern and allow clients to access structured data. Our example creates a resource with the URI template echo://{message} that simply echoes back the message.
  1. Tool (@mcp.tool): Tools are functions that clients can invoke directly. They appear in the client's tool list and can be called with parameters. Our echo_tool simply returns the provided message with a prefix.
  1. Prompt (@mcp.prompt): Prompts help clients generate better queries or instructions. Our echo_prompt formats a message into a standard prompt template.

After adding these features and restarting your server, refresh your MCP client. You should now see the new "echo_tool" available in your client's tool list. When you use it, the client will send a request to your server, and your server will respond with the echo message.

Here's an example of what interacting with the MCP server might look like in Cursor after we've added our echo tool:

Handling Authentication

At the moment, the MCP specification does not define a standard authentication mechanism for SSE servers. This means that while you could implement authentication in your FastAPI application using standard methods (like API keys, OAuth, or JWT tokens), there is no standardized way for MCP clients like Cursor to provide these credentials when connecting to your SSE endpoint.

You could implement basic authentication for your FastAPI routes outside of the MCP endpoints, but the /sse endpoint itself would need to remain unauthenticated for compatibility with current MCP clients. This limitation should be considered when deploying MCP servers in production environments.

If your use case requires authentication, you might consider:

  1. Network-level security (VPNs, private networks, or IP whitelisting)
  2. Deploying behind a reverse proxy that handles authentication
  3. Using environment-specific configurations that limit access

As the MCP specification evolves, standardized authentication mechanisms may be introduced. For now, it's important to be aware of this limitation when designing systems that use MCP over SSE. The code from this repo was used to create this guide: https://github.com/ragieai/fastapi-sse-mcp. You can take a look at it to get a better understanding of how to build a Server-Sent Events (SSE) MCP Server with FastAPI.

Conclusion

In this guide, we've walked through creating a FastAPI application with an integrated MCP server using Server-Sent Events. This approach allows you to add powerful AI assistant capabilities directly into your existing applications without relying on separate processes or complex communication mechanisms.

While this example demonstrates a simple echo-based MCP server, the same principles can be applied to create sophisticated AI tools that leverage your application's data and functionality. By exposing your application's capabilities through MCP tools, resources, and prompts, you can create rich, interactive AI experiences that are seamlessly integrated with your software.