Skip to main content
Glama
ayushneo

fast_mcp_template

by ayushneo
README.md
# fast_mcp_template

> **This is a GitHub template repository.** Click **Use this template → Create a new repository**
> on [github.com/ayushneo/fast_mcp_template](https://github.com/ayushneo/fast_mcp_template), or:
>
> ```bash
> gh repo create my-service --template ayushneo/fast_mcp_template --private --clone
> ```

Same layered CRUD/Docker/Poetry base as [`fastapi_template`](https://github.com/ayushneo/fastapi_template),
plus one thing: the app's own REST endpoints are exposed as MCP tools via
[`fastapi_mcp`](https://github.com/tadata-org/fastapi_mcp), so an MCP client
(Claude Desktop, Claude Code, any agent framework) can call them directly.
Generate a new repository from this template; delete `items` once you've read
how the wiring works.

Built by reading the actual `fastapi_mcp` source (its README's own code
sample is thinner than what's needed to get filtering/auth/transport right),
not from the surveyed blog posts — see "What was actually useful" below for
why most of those added nothing beyond `fastapi_mcp`'s own docs.

## File structure

Identical to `fastapi_template/` (see that README for the full layout and its
own trade-offs table — those still apply here) plus:

```
fast_mcp_template/
├── app/
│   ├── core/
│   │   └── mcp.py          -- NEW: builds + mounts the MCP server over this app's own routes
│   ├── api/v1/
│   │   └── items.py         -- CHANGED: explicit operation_id on every route (see why below)
│   └── schemas/
│       └── item.py           -- CHANGED: Field(..., description=...) on every field
└── tests/
    └── test_mcp.py             -- NEW: asserts the exposed tool set, no live MCP session needed
```

## The one architectural choice that matters here

There are two different things "add MCP to a FastAPI app" can mean:

**(a) Expose the individual REST endpoints as MCP tools** and let an external
MCP client do the reasoning about which to call, in what order — this is
what `fastapi_mcp` is actually built for (turn API endpoints into tools,
not wrap an agent), and what this template does.

**(b) Wrap an internal agent loop as one meta-tool** (`ask_agent(question)`
that reasons internally and returns a final answer) — simpler to wire, but
hides all tool-selection structure from the client; from the outside it's
one opaque tool.

This template builds (a). Notably, this repo already made and recorded the
exact same call independently, for the hand-rolled (non-`fastapi_mcp`)
version of this same decision, in `react_mcp_server/react-mcp-server-spec.md`
— same reasoning, arrived at twice without cross-referencing.

## Trade-offs specific to the MCP layer

(General FastAPI trade-offs — models vs. schemas, error handling, pagination,
logging — are the same as `fastapi_template` and not repeated here.)

| Decision | Options | Chosen, and why |
|---|---|---|
| `operation_id` | Auto-generated (function name + path hash) vs. explicit on every route | Explicit, always — `fastapi_mcp` uses `operation_id` as the MCP tool's *name*. Leave it auto and an unrelated refactor (renaming the function, moving the router) silently renames or breaks the tool from an agent's perspective. The single most-repeated warning across every `fastapi_mcp` write-up surveyed. |
| Tool descriptions | Minimal vs. `Field(..., description=...)` on every schema field | Detailed — this is the *entire* quality signal an MCP client (an LLM) has to decide whether/how to call a tool. There's no separate "tool doc" to write; the Pydantic field description *is* the doc. |
| Transport | `mount_sse()` (legacy Server-Sent Events) vs. `mount_http()` (Streamable HTTP) | HTTP — the current, non-deprecated transport (verified against `fastapi_mcp`'s own source, not just its README). Use SSE only if a specific client you must support hasn't moved off it. |
| Exposure surface | Expose every endpoint vs. filter | Filtered — `delete_item` is excluded via `exclude_operations` in `app/core/mcp.py` by default. A destructive endpoint has a bigger blast radius when the caller is an LLM deciding on its own when to invoke it than when it's a human confirming a dialog. Still a normal REST endpoint either way; re-include it explicitly once you actually want that. |
| Auth | Build a custom auth bridge vs. `fastapi_mcp`'s `AuthConfig(dependencies=[Depends(...)])` | Not wired by default (no auth in this template at all, same as `fastapi_template`) — but when you add it, pass your existing `Depends()` into `AuthConfig` rather than writing a separate MCP-specific auth path. `fastapi_mcp`'s whole pitch over a generic OpenAPI→MCP converter is reusing FastAPI's own dependencies for this. |
| Where to build the MCP object | Inline in `main.py` vs. a `build_mcp()` function | A function (`app/core/mcp.py`) — lets `tests/test_mcp.py` inspect `.tools` directly without booting a live MCP session/handshake. |

## What was actually useful from the surveyed sources

| Source | Verdict |
|---|---|
| [tadata-org/fastapi_mcp](https://github.com/tadata-org/fastapi_mcp) (source, not just README) | The only one worth reading closely — this template's `mount_http()`/`exclude_operations`/`AuthConfig` usage all came from reading `fastapi_mcp/server.py` directly, because **the README's own basic example (`mcp.mount()`) already calls a soft-deprecated path** — worth knowing before copying it verbatim from the README. |
| `react_mcp_server/react-mcp-server-spec.md` (this repo, already existed) | Same "(a) not (b)" call, same `operation_id`/`Field(description=...)` warnings — written independently before this template, confirms the pattern rather than introducing it. |
| modelcontextprotocol/servers | Reference *server* implementations (stdio-based tool servers), not FastAPI-specific — no additional guidance for this template's actual problem (exposing an existing HTTP API), so nothing was taken from it directly. |
| The Medium/blog posts (Ruchi Awasthi, uselessai.in, Jayant Nehra) and mintmcp's blog | General MCP-concept explainers, largely restating what `fastapi_mcp`'s own README already says, at a lower technical resolution. mcpservers.org is a directory listing, not a guide. None added anything beyond what's already reflected above — surveyed, not skipped out of laziness. |

## A live upstream bug, verified 2026-09-13

`fastapi-mcp==0.4.0` (latest on PyPI) calls the `mcp` SDK's `Server(name, description)`
positionally; `mcp>=2.0.0` made `description` keyword-only, so the combination
raises `TypeError` at import time. `pyproject.toml` pins `mcp = "<2.0"` to work
around it. Re-check this pin when bumping `fastapi-mcp` — the fix is on the
`fastapi_mcp` side, not something to route around with more code here.

## Quick start

```bash
cp .env.example .env
poetry install
make migrate
make dev        # fastapi dev app/main.py -- REST at /v1/items, docs at /docs, MCP at /mcp
make test       # includes tests/test_mcp.py
```

Point an MCP client (e.g. Claude Desktop's config, or `mcp-remote`) at
`http://127.0.0.1:8000/mcp` and confirm it lists `create_item`, `get_item`,
`list_items`, and `health` — but not `delete_item`.

## Extending this template

Same as `fastapi_template` for adding a new resource (model → schema →
repository → service → route → one line in `router.py`) — the only addition
is: give the route an explicit `operation_id`, and write its schema field
descriptions for an LLM reader. It becomes an MCP tool automatically the
next time `build_mcp()` runs; no separate registration step.

---

## Change Log

| Date | Change | Author |
|------|--------|--------|
| 2026-09-13 | Forked from `fastapi_template`; added `fastapi_mcp` wiring, explicit `operation_id`s, tool-facing field descriptions, and the exposure-filtering example | Ayush Sood |

## Contributing, security, license

See [CONTRIBUTING.md](CONTRIBUTING.md), [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md), [SECURITY.md](SECURITY.md). Released under the [MIT License](LICENSE).