Skip to content

Repository files navigation

mcp-template

A uv + FastMCP server template — a clean skeleton with the patterns, scripts, and glue you actually need, optimized for use with agentic coding tools (Claude Code, Cursor, Codex, Antigravity).

The demo domain is cake/baking (recipes, a simulated long-running bake, and a fake "Bakery Cloud" API for the auth demo). Replace the cake tools with your own; keep the structure.

Agents: start with AGENTS.md.

Highlights

  • uv packaging with the uv_build backend.
  • Hybrid tool registration with modes (optional) — an essentials surface that's always on, plus an extended tier a session opts into at runtime via the set_mode tool (no restart, and scoped to that one client). Keeps the default tool list small to avoid polluting an agent's context. One demo tool ships in the extended tier; delete the axis entirely if your server has one surface.
  • Boot-safe runtime auth — the server never requires a key to start. Key-gated tools resolve credentials per request / per session (multi-user safe), via an authenticate tool, an HTTP header, or env. Session keys live in FastMCP's own session state (ctx.set_state), not in a hand-rolled dict.
  • Real background tasks out of the box — @mcp.tool(task=True) with FastMCP's in-memory backend (no Redis); FASTMCP_DOCKET_URL switches to Redis for scale.
  • On-demand tool discovery (optional) — --tool-search regex|bm25 replaces the whole listing with search_tools + call_tool, for catalogs too big to put in an agent's context.
  • Structured I/O via Pydantic models + tool annotations.
  • In-memory test harness (Client(transport=server)) — fast, deterministic, no network.
  • Pre-wired client configs for Claude Code, Cursor, and VS Code.
  • Optional Docker deployment.

Quickstart

uv sync
uv run pytest                      # 28 tests, all in-memory
uv run mcp-template stdio          # run over stdio
uv run mcp-template http           # run over HTTP (default :3011)
uv run mcp-template stdio --mode extended   # add the opt-in tools
uv run mcp-template stdio --tool-search bm25  # discover tools by search
uv run fastmcp dev fastmcp.json    # MCP Inspector

The server boots with no environment configured.

Tools (cake demo)

Tool Tier Key? Notes
list_recipes essentials no read-only
get_recipe essentials no read-only
bake_cake essentials no real background task (task=True), streams progress
authenticate always unlocks gated tools for this session
set_mode always switches this session between the two surfaces
scale_recipe extended no --mode extended
order_custom_cake gated yes needs an API key (demo: cake_*)
delivery_status gated yes needs an API key

Gated tools are listed by default and refuse per call; CAKE_HIDE_GATED_UNTIL_AUTH=true hides them from a session until it authenticates.

Plus a resource (resource://cakes/pantry) and a prompt (bake_a_cake).

Modes (optional pattern)

CAKE_MODE (env) or --mode (CLI), default essentials, picks the starting surface:

  • essentials — minimal, casual surface (low context cost).
  • extended — adds the opt-in tools in tools/extended.py.

Startup is not the last word. Any session can switch its own surface mid- connection with the set_mode tool:

set_mode(mode="extended")    # extended tools appear for THIS client
set_mode(mode="essentials")  # ...and can be shed again to shrink the context

That matters most locally, where a stdio server's mode was fixed by whatever the client's launch config said and changing it meant editing .mcp.json and restarting. The extended tools are always registered; CAKE_MODE just decides whether they start hidden. Switching calls the session-scoped ctx.enable_components/disable_components, so other clients are unaffected and the caller gets a tools/list_changed notification.

Exactly one tool (scale_recipe) sits in the extended tier — it is there to show the shape, not to be a bucket to fill. Keep the split only if some of your tools are genuinely too niche for the default surface; otherwise delete tools/extended.py, its register_extended/register_mode_switch calls in server.py, the mode setting, and the --mode CLI option.

Tool search (optional pattern)

CAKE_TOOL_SEARCH (env) or --tool-search (CLI), default off:

  • off — every registered tool is listed.
  • regex — listing collapses to search_tools + call_tool; case-insensitive substring match over name, description and parameter docs. Deterministic and blunt: searching bake also matches authenticate, whose description mentions the Bakery Cloud.
  • bm25 — same shape, Okapi BM25 relevance ranking. Better for natural language. CAKE_TOOL_SEARCH_MAX_RESULTS caps the hits (default 5).

authenticate and set_mode stay pinned into the listing (ALWAYS_VISIBLE in tool_search.py): a client that cannot see authenticate cannot unlock anything, and set_mode is the only hint that an extended surface exists — tools the mode axis hides are invisible to search as well.

Three things to know before enabling it:

  • It is a FastMCP transform, not an MCP protocol feature — clients need to know nothing about it, they just see two tools.
  • Unlisted tools stay callable by name, but a client cannot validate structured output against a schema it never received: FastMCP's client degrades result.data from a typed model to a plain dict. Use call_tool for typed results.
  • Only tools are replaced; resources and prompts list as usual. Search respects auth and visibility, so it composes with CAKE_HIDE_GATED_UNTIL_AUTH.

At this template's size it costs more than it saves — it earns its keep at dozens of tools.

Auth model (read this)

The server never raises at startup for a missing key. Key-gated tools resolve a key per request, in this order:

  1. X-Cake-Api-Key HTTP header (multi-user safe)
  2. per-session key set via the authenticate tool
  3. CAKE_API_KEY env (single-tenant / local default)

If none resolve, gated tools return a friendly message (no exception). A key set via authenticate goes into FastMCP session state (ctx.set_state), which namespaces it by ctx.session_id — so it never leaks between HTTP clients.

Two properties of that store to plan around:

  • Entries expire after 24h; the session is then asked for a key again.
  • The default backend is an in-process MemoryStore. A multi-process HTTP deployment must share one — FastMCP(session_state_store=...) (e.g. Redis via py-key-value) — or a worker will not see a key another worker stored.

Visibility vs. authorization

By default the gated tools are listed to everyone and refuse politely per call, so an agent can discover them before it has a key. Set CAKE_HIDE_GATED_UNTIL_AUTH=true to hide them from a session until it authenticates: startup disables them by tag, and authenticate calls the session-scoped ctx.enable_components(tags={"bakery_cloud"}), which reveals them to that client only (other sessions stay in the dark). The tradeoff — and why it is off by default — is that an unauthenticated session cannot discover the tools at all, and calling one by name fails with "Unknown tool" instead of the friendly refusal.

Note the asymmetry: mcp.enable()/disable() are server-global (fine at startup, never per user), while ctx.enable_components() is session-scoped and is the only one safe to drive from a client's request.

Using with coding agents

Pre-wired configs are included:

  • Claude Code → .mcp.json
  • Cursor → .cursor/mcp.json
  • VS Code → .vscode/mcp.json

They launch uv run mcp-template stdio. For Codex (~/.codex/config.toml):

[mcp_servers.cake]
command = "uv"
args = ["run", "mcp-template", "stdio"]

Configuration

All CAKE_* env vars are optional (see .env.example and settings.py): CAKE_API_KEY, CAKE_API_KEY_HEADER, CAKE_HIDE_GATED_UNTIL_AUTH, CAKE_MODE, CAKE_TOOL_SEARCH, CAKE_TOOL_SEARCH_MAX_RESULTS, CAKE_TRANSPORT, CAKE_HOST, CAKE_PORT, CAKE_LOG_LEVEL, CAKE_OVEN_MAX_TEMP_C.

Deployment

  • Docker: docker build -t cake-mcp . && docker run -p 3011:3011 cake-mcp (defaults to HTTP). For a publicly reachable HTTP deployment, opt into FastMCP's DNS-rebinding guard: FASTMCP_HTTP_HOST_ORIGIN_PROTECTION=true plus FASTMCP_HTTP_ALLOWED_HOSTS / FASTMCP_HTTP_ALLOWED_ORIGINS (off by default so local runs and reverse proxies keep working).
  • Declarative: fastmcp.json drives fastmcp run / fastmcp dev.

Project layout

src/mcp_template/
  server.py        build_server(), CLI, graceful shutdown
  settings.py      pydantic-settings (CAKE_*), safe defaults
  auth.py          per-request key resolution (session state) + authenticate
  tool_search.py   optional: collapse the listing into search_tools/call_tool
  models.py        Pydantic tool I/O models
  logging_setup.py stdlib logging -> stderr
  tools/
    recipes.py       essentials (always)
    extended.py      extended tier + set_mode switch — optional pattern
    bakery_cloud.py  key-gated (tag: bakery_cloud)
    data.py          in-memory recipe fixtures
tests/             in-memory client tests

Make it yours

See the "Renaming the template" section in AGENTS.md.

License

MIT — see LICENSE.

About

A fastmcp template mcp server for uniform format

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages