Build MCP Servers: Using FastAPI-MCP

Table of Contents

Previous posts looked at MCP Python SDK and FastMCP v2 for building MCP servers. Let’s now look at FastAPI-MCP. Built on top of the official MCP Python SDK, it allows you to define MCP servers by defining FastAPI endpoints. Let’s see how this works.

You might find it helpful to be familiar with the MCP Inspector and the general idea behind what and why MCP is in my MCP Introduction.

Install

pip install fastapi-mcp

Simple MCP Server

With FastAPI-MCP you would first define FastAPI endpoints, which are then processed and made available as MCP tools:

# server.py
from fastapi import FastAPI
from fastapi_mcp import FastApiMCP  # type: ignore[import-untyped]


app = FastAPI(title="MCP Server API", version="0.0.1")


@app.get(
    "/add_numbers",
    operation_id="add_numbers",
    description="Add two numbers"
)
async def add_numbers(a: int, b: int) -> int:
    return a + b


mcp = FastApiMCP(app)
mcp.mount(mount_path="/sse", transport="sse")

We can then use the FastAPI tools to run the application:

fastapi dev server.py

The SSE endpoint is available under: http://localhost:8000/sse

Unfortunately Streamable HTTP isn’t supported yet (as of June 2025).

List and Run Tools

Under Tools click on List Tools and you should see the following tool listed:

  • add_numbers

When you click on it, you can run it:

MCP Inspector show result of add_numbers tool

Tools schema generated by FastAPI-MCP

The tools schema is important as it describes the available tools - to users as well as AI. In this case the schema includes the correct input type integer and other fields you would expect:

{
  "tools": [
    {
      "name": "add_numbers",
      "description": "Add Numbers\n\nAdd two numbers\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json\n\n**Example Response:**\n```json\n1\n```",
      "inputSchema": {
        "type": "object",
        "properties": {
          "a": {
            "type": "integer",
            "title": "a"
          },
          "b": {
            "type": "integer",
            "title": "b"
          }
        },
        "required": [
          "a",
          "b"
        ],
        "title": "add_numbersArguments"
      }
    }
  ]
}

You can get the schema by looking at the tools/list response in the MCP Inspector.

While the tools schema is similar to the one generated by the MCP Python SDK, there are some differences:

  • the property titles are lower case (a and b)
  • the description is more verbose (see below)

The title and description of fields we can easily configure by annotating the parameters:

@app.get(
    "/add_numbers",
    operation_id="add_numbers",
    description="Add two numbers"
)
async def add_numbers(
    a: Annotated[
        int,
        Query(title="A", description="First number to add")
    ],
    b: Annotated[
        int,
        Query(title="B", description="Second number to add")
    ]
) -> int:
    return a + b

The description will then be included in the schema of properties:

{
    "a": {
        "type": "integer",
        "title": "a",
        "description": "First number to add"
    },
    "b": {
        "type": "integer",
        "title": "b",
        "description": "Second number to add"
    }
}

For some reasons, the title hasn’t been updated.

Let’s have a closer look at the generated tool description:

Add Numbers

Add two numbers

Responses:

200: Successful Response (Success Response) Content-Type: application/json

Example Response:

1

We can get an even more verbose description by passing describe_all_responses to FastApiMCP:

mcp = FastApiMCP(app, describe_all_responses=True)

Add Numbers

Add two numbers

Responses:

200: Successful Response (Success Response) Content-Type: application/json

Example Response:

1

422: Validation Error Content-Type: application/json

Example Response:

{
  "detail": [
    {
      "loc": [],
      "msg": "Message",
      "type": "Error Type"
    }
  ]
}

If you want to get even more overboard, try adding describe_full_response_schema as well:

mcp = FastApiMCP(app, describe_full_response_schema=True)

It will then add the schema at the end of the description:

Output Schema:

{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {},
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

This will likely be of limited use because it currently only describes the error response. Describing the response in too much detail isn’t as useful because the AI will figure it out. It’s more important to describe the inputs.

Unfortunately there doesn’t seem to be an option to only use the description of the endpoint.

Code

You can find self contained examples code in my python-examples repo, under python_examples/ai/mcp/fastapi_mcp.

Conclusion

Being able to define your MCP server using FastAPI is a nice feature, if you are already familiar with FastAPI or want to provide regular REST endpoints as well. FastAPI-MCP is built around that. However, it’s lack of Streamable HTTP support is pulling it down. Additionally I’d wish for more control over the tool description.

FastMCP v2 has similar features and seems to be much more actively maintained.

Subscribe