Fastmcp Openapi Template
No description available
Ask AI about Fastmcp Openapi Template
Powered by Claude Β· Grounded in docs
I know everything about Fastmcp Openapi Template. Ask me about installation, configuration, usage, or troubleshooting.
0/500
Reviews
Documentation
FastMCP OpenAPI MCP Server
Production-ready MCP server generated from an OpenAPI specification, built with FastMCP (v3.2+).
Give an LLM access to any REST API by dropping in an OpenAPI spec.
Table of Contents
- Prerequisites
- Quick Start
- Project Structure
- Configuration Reference
- Authentication
- OpenAPI Spec
- Route Maps
- Running the Server
- MCP Client Setup
- Development
- Docker
- Production Checklist
- Troubleshooting
- License
Prerequisites
- Python 3.12+
- uv (recommended) or pip β install uv:
pip install uvor see astral.sh/uv - An OpenAPI 3.0+ specification file (JSON or YAML)
Quick Start
# 1. Clone or copy this template
cd fastmcp-openapi-template
# 2. Install dependencies
uv sync
# 3. Create your config
cp .env.example .env
# 4. Edit .env β at minimum set UPSTREAM_BASE_URL
# and point OPENAPI_SPEC_PATH to your spec
# 5. Drop your OpenAPI spec
cp /path/to/your-api.yaml openapi/spec.yaml
# 6. Run (stdio mode β for Claude Code, Cursor, etc.)
uv run python -m src
If you don't have an OpenAPI spec handy, the included openapi/spec.yaml (a Petstore API) works for testing.
Project Structure
βββ src/
β βββ __init__.py
β βββ __main__.py # Entry point: python -m src
β βββ server.py # Core: FastMCP.from_openapi() + route maps
β βββ config.py # Pydantic-settings β all config from env vars
β βββ auth.py # MCP server JWT auth provider
β βββ client.py # Upstream HTTP client factory (Bearer / API-key)
β βββ lifespan.py # Startup/shutdown lifecycle hooks
β βββ middleware.py # Structured logging + error handling middleware
βββ openapi/
β βββ spec.yaml # Drop your OpenAPI spec here (or anywhere)
βββ tests/
β βββ conftest.py # Shared fixtures (sample spec, mocks)
β βββ test_config.py # Config validation tests
β βββ test_server.py # Server creation, auth, client tests
βββ scripts/
β βββ dev.sh # Dev launcher helper
βββ pyproject.toml # Dependencies + project metadata
βββ Dockerfile # Multi-stage production build
βββ docker-compose.yml # Local Docker dev setup
βββ .env.example # Full config template with docs
βββ README.md # This file
You don't need to touch most of these files. For basic use, you only need:
.envβ your configurationopenapi/spec.yaml(or equivalent) β your OpenAPI spec
Configuration Reference
All settings are loaded from environment variables or a .env file. Copy .env.example to .env and fill in your values.
OpenAPI Spec
| Variable | Default | Description |
|---|---|---|
OPENAPI_SPEC_PATH | openapi/spec.yaml | Path to your OpenAPI spec (.json, .yaml, .yml) or a URL (https://...) |
Upstream API
| Variable | Default | Description |
|---|---|---|
UPSTREAM_BASE_URL | (empty) | Base URL of the API described by your spec (e.g. https://api.example.com/v1) |
UPSTREAM_AUTH_TYPE | none | bearer, api-key, or none |
UPSTREAM_AUTH_TOKEN | β | Bearer token (required when UPSTREAM_AUTH_TYPE=bearer) |
UPSTREAM_API_KEY_HEADER | X-API-Key | Header name for API key auth |
UPSTREAM_API_KEY_VALUE | β | API key value (required when UPSTREAM_AUTH_TYPE=api-key) |
UPSTREAM_TIMEOUT | 30.0 | Request timeout in seconds |
MCP Server Identity
| Variable | Default | Description |
|---|---|---|
MCP_NAME | OpenAPI MCP Server | Name shown to MCP clients |
MCP_INSTRUCTIONS | (empty) | Instructions shown to LLMs about how to use this server |
MCP Transport
| Variable | Default | Description |
|---|---|---|
MCP_TRANSPORT | stdio | stdio (local) or http (remote) |
MCP_HOST | 127.0.0.1 | Bind address (HTTP mode only) |
MCP_PORT | 8000 | Bind port (HTTP mode only) |
MCP Server Auth (HTTP transport only)
| Variable | Default | Description |
|---|---|---|
MCP_AUTH_ENABLED | false | Enable JWT auth for the MCP server |
MCP_JWT_JWKS_URI | β | JWKS endpoint URL (required if auth enabled) |
MCP_JWT_ISSUER | β | Expected JWT issuer (required if auth enabled) |
MCP_JWT_AUDIENCE | β | Expected JWT audience (required if auth enabled) |
Error Handling & Logging
| Variable | Default | Description |
|---|---|---|
MASK_ERROR_DETAILS | true | Hide internal errors from clients (set true in production) |
LOG_LEVEL | INFO | DEBUG, INFO, WARNING, or ERROR |
LOG_FORMAT | json | json (structured) or text (human-readable) |
Authentication
This template handles auth at two independent levels.
Upstream API Auth
Auth used when calling the REST API described by your OpenAPI spec. Configured on the httpx.AsyncClient that FastMCP.from_openapi() uses internally.
Bearer token β most common for APIs using OAuth 2.0 / JWT:
UPSTREAM_AUTH_TYPE=bearer
UPSTREAM_AUTH_TOKEN=eyJhbGciOi...
UPSTREAM_BASE_URL=https://api.example.com
API key β common for SaaS APIs (e.g. Stripe, SendGrid):
UPSTREAM_AUTH_TYPE=api-key
UPSTREAM_API_KEY_HEADER=X-API-Key
UPSTREAM_API_KEY_VALUE=sk_live_abc123...
UPSTREAM_BASE_URL=https://api.example.com
No auth β for public APIs or local development:
UPSTREAM_AUTH_TYPE=none
UPSTREAM_BASE_URL=https://api.example.com
The system validates your config at startup β if you set UPSTREAM_AUTH_TYPE=bearer without a token, or api-key without a key value, the server will refuse to start with a clear error message.
MCP Server Auth
Protects the MCP server itself when running over HTTP transport. This is separate from upstream API auth β it controls who can connect to your MCP server, not who can call the upstream API.
Currently supports JWT validation via JWTVerifier:
MCP_TRANSPORT=http
MCP_AUTH_ENABLED=true
MCP_JWT_JWKS_URI=https://auth.example.com/.well-known/jwks.json
MCP_JWT_ISSUER=https://auth.example.com
MCP_JWT_AUDIENCE=my-mcp-server
Clients must present a valid JWT in the Authorization: Bearer <token> header. FastMCP validates the signature against the JWKS endpoint, checks expiration, and verifies the issuer and audience claims.
Auth is not applicable to stdio transport β stdio inherits security from the local process environment.
To extend auth (OAuth2, multi-provider), edit src/auth.py. FastMCP also supports OAuthProxy (GitHub, Google, Azure), RemoteAuthProvider (WorkOS, Descope), MultiAuth (hybrid JWT + OAuth), and custom providers.
OpenAPI Spec
Supported formats
- YAML:
.yaml,.ymlfiles - JSON:
.jsonfiles - URL: Any
https://URL serving a spec (content-type header or extension determines parser)
Where to put it
The default path is openapi/spec.yaml, but you can point OPENAPI_SPEC_PATH anywhere:
OPENAPI_SPEC_PATH=./my-api/openapi.json # local file
OPENAPI_SPEC_PATH=https://petstore3.swagger.io/api/v3/openapi.json # remote URL
Only one spec is loaded β this template does not scan directories or merge multiple specs.
Spec requirements
- OpenAPI 3.0+ (3.0.x or 3.1.x)
- Each endpoint should have an
operationIdβ FastMCP uses it as the tool/resource name - A
serversblock orUPSTREAM_BASE_URLmust be set so generated tools know where to send requests
What happens at startup
- The spec is loaded and validated (malformed YAML/JSON raises a clear error)
- Each path + method combination is mapped to an MCP component based on your route maps
- Request parameters, bodies, and response schemas are auto-generated from the spec
- The server is ready β tools and resources appear in your MCP client
Route Maps
Route maps control which OpenAPI endpoints become Tools (invocable), Resources (browsable data), or are excluded entirely.
Default behavior
| HTTP Method + Path Pattern | MCP Component |
|---|---|
GET /collection (single segment, no params) | Resource β browsable, cachable |
GET /collection/{id} (contains path param) | Resource Template β parameterized |
POST, PUT, PATCH, DELETE (any path) | Tool β invocable by the LLM |
Example with the Petstore spec:
GET /petsβresource://listPetsGET /pets/{petId}β resource templategetPetPOST /petsβ toolcreatePetDELETE /pets/{petId}β tooldeletePet
Customizing route maps
Edit build_route_maps() in src/server.py:
from fastmcp.server.providers.openapi import RouteMap, MCPType
def build_route_maps():
return [
# Everything under /admin/ is excluded from MCP
RouteMap(
pattern=r"^/admin/.*",
mcp_type=MCPType.EXCLUDE,
),
# GET with path params β resource templates
RouteMap(
methods=["GET"],
pattern=r".*\{.*\}.*",
mcp_type=MCPType.RESOURCE_TEMPLATE,
),
# Everything else β tool (default)
RouteMap(
methods=["POST", "PUT", "PATCH", "DELETE", "GET"],
pattern=r".*",
mcp_type=MCPType.TOOL,
),
]
Route maps are evaluated in order β the first match wins. Each RouteMap can filter by:
methods: list of HTTP methods (e.g.["GET", "POST"])pattern: regex against the URL pathtags: set of OpenAPI tags that must all be present on the operation
Running the Server
STDIO mode (local MCP clients)
This is the default. MCP clients like Claude Code, Cursor, and VS Code launch the server as a subprocess and communicate via stdin/stdout.
# Using the dev script
./scripts/dev.sh
# Or directly
uv run python -m src
No network port is exposed. The server inherits the security context of the user running it.
Set in .env:
MCP_TRANSPORT=stdio
HTTP mode (remote / network)
Run as a standalone HTTP server with the Streamable HTTP transport:
# Using the dev script
./scripts/dev.sh --http
# Or directly
MCP_TRANSPORT=http uv run python -m src
The server listens on the configured host:port (default 127.0.0.1:8000). Enable MCP_AUTH_ENABLED=true to require JWT authentication.
Set in .env:
MCP_TRANSPORT=http
MCP_HOST=0.0.0.0 # Listen on all interfaces
MCP_PORT=8000
MCP Client Setup
Claude Code
Add to your Claude Code config (~/.claude/claude.json or project .mcp.json):
{
"mcpServers": {
"my-api": {
"command": "uv",
"args": ["run", "python", "-m", "src"],
"cwd": "/absolute/path/to/this/project"
}
}
}
Cursor / VS Code
Add to .cursor/mcp.json or the VS Code MCP settings:
{
"mcpServers": {
"my-api": {
"command": "uv",
"args": ["run", "python", "-m", "src"],
"cwd": "/absolute/path/to/this/project"
}
}
}
Generic HTTP Client
{
"mcpServers": {
"my-api": {
"url": "http://localhost:8000/mcp",
"headers": {
"Authorization": "Bearer YOUR_JWT_TOKEN"
}
}
}
}
Verifying it works
After connecting your MCP client, you should see your API's tools and resources listed. Ask the LLM: "What tools are available?" β it should list all the endpoints from your OpenAPI spec.
Development
Installing dependencies
# Install main dependencies
uv sync
# Install with dev dependencies (pytest, etc.)
uv sync --group dev
Running tests
# Run all tests
uv run pytest -v
# Run specific test file
uv run pytest tests/test_config.py -v
# Run with coverage
uv pip install pytest-cov
uv run pytest --cov=src --cov-report=term-missing
17 tests cover config validation, auth setup, client creation, route map building, and server creation. The test suite uses the sample Petstore spec in openapi/spec.yaml.
Adding custom tools
You can add hand-written tools alongside the auto-generated ones:
# In src/server.py, after create_server()
@mcp.tool
async def custom_business_logic(param: str, ctx: Context) -> str:
"""A custom tool not in the OpenAPI spec."""
return f"Processed: {param}"
Custom tools appear alongside the OpenAPI-generated ones.
Customizing route maps
Edit build_route_maps() in src/server.py. See the Route Maps section for patterns. Common customizations:
- Exclude admin/internal endpoints
- Make read-only GETs into resources for better caching
- Tag specific endpoints for filtering in clients
- Use
route_map_fnfor dynamic per-endpoint decisions
Adding custom lifespan logic
Edit src/lifespan.py to add startup/shutdown logic:
@lifespan
async def app_lifespan(server):
# Startup: warm caches, check upstream health, load config
logger.info("warming_caches")
...
try:
yield {"my_resource": expensive_object}
finally:
# Shutdown: close connections, flush metrics
logger.info("cleaning_up")
...
Tools access lifespan objects via ctx.lifespan_context["my_resource"].
Docker
Building the image
docker build -t fastmcp-openapi-mcp .
The Dockerfile is a multi-stage build:
- Builder stage: Installs dependencies via
uvinto a virtual environment - Runtime stage: Copies only the venv and source, runs as non-root
appuser
Running with Docker Compose
# 1. Configure .env with MCP_TRANSPORT=http
# 2. Start
docker-compose up -d
# 3. Check logs
docker-compose logs -f
# 4. Stop
docker-compose down
The compose file:
- Mounts
./openapi/as read-only (update specs without rebuilding) - Loads env vars from
.env - Sets
restart: unless-stopped - Includes a healthcheck on
GET /health
Production Docker
For production, don't rely on the .env file β pass env vars explicitly:
docker run -d \
--name mcp-server \
-p 8000:8000 \
-v "$(pwd)/openapi:/app/openapi:ro" \
-e MCP_TRANSPORT=http \
-e MCP_HOST=0.0.0.0 \
-e MCP_PORT=8000 \
-e UPSTREAM_BASE_URL=https://api.example.com \
-e UPSTREAM_AUTH_TYPE=bearer \
-e UPSTREAM_AUTH_TOKEN="$API_TOKEN" \
-e MCP_AUTH_ENABLED=true \
-e MCP_JWT_JWKS_URI="$JWKS_URI" \
-e MCP_JWT_ISSUER="$JWT_ISSUER" \
-e MCP_JWT_AUDIENCE="$JWT_AUDIENCE" \
-e MASK_ERROR_DETAILS=true \
fastmcp-openapi-mcp
Never bake secrets into the image. Always inject them at runtime via env vars or a secrets manager.
Production Checklist
Before deploying to production:
- Set
MASK_ERROR_DETAILS=true(hides stack traces from clients) - Set
LOG_FORMAT=json(structured logs for aggregation) - Set
LOG_LEVEL=INFOorWARNING(DEBUG leaks sensitive data) - Enable
MCP_AUTH_ENABLED=truewith valid JWKS config (HTTP mode) - Pin FastMCP:
fastmcp>=3.0.0,<4.0.0inpyproject.toml(breaking changes happen in minors) - Don't hardcode secrets in
.envβ inject via environment at deploy time - Run as non-root user (Dockerfile already does this)
- Configure healthchecks in your orchestrator against
GET /health - Set resource limits on your container (CPU/memory)
- Use a reverse proxy (nginx, Caddy) with TLS if exposing HTTP publicly
- Review your route maps β exclude sensitive/internal endpoints
- Set
UPSTREAM_TIMEOUTappropriate to your API's SLA - Run
uv run pytest -vand verify all tests pass - Verify the server starts and tools appear in your MCP client
Troubleshooting
"OpenAPI spec not found"
The file at OPENAPI_SPEC_PATH doesn't exist. Either:
- Drop your spec at the default location (
openapi/spec.yaml) - Set
OPENAPI_SPEC_PATHto the correct path - Use an absolute path
"UPSTREAM_AUTH_TYPE is 'bearer' but UPSTREAM_AUTH_TOKEN is not set"
You enabled bearer auth but didn't provide a token. Set UPSTREAM_AUTH_TOKEN in .env.
"UPSTREAM_BASE_URL is required when UPSTREAM_AUTH_TYPE is..."
You're using auth but didn't set a base URL. Add UPSTREAM_BASE_URL=https://your-api.com.
"MCP_AUTH_ENABLED is true but required fields are missing"
All three JWT fields (MCP_JWT_JWKS_URI, MCP_JWT_ISSUER, MCP_JWT_AUDIENCE) are required when auth is enabled.
Tools don't appear in my MCP client
Check:
- The spec loaded successfully (check startup logs for
openapi_spec_loaded) - Your route maps aren't excluding everything (set
LOG_LEVEL=DEBUGto see route mapping decisions) - The spec has valid
operationIdvalues on each endpoint - For STDIO mode: the client is correctly configured with the absolute path to this project
"route_map_import_failed_using_defaults" warning
The FastMCP version installed doesn't match the expected RouteMap import path. The server falls back to default behavior (all endpoints β tools). Update FastMCP: uv sync --reinstall.
Upstream API calls fail
- Verify
UPSTREAM_BASE_URLis correct (include the path prefix, e.g./api/v1) - Verify auth credentials are valid
- Check network connectivity from the server's environment
- Set
LOG_LEVEL=DEBUGto see request/response details
Port already in use (HTTP mode)
Change MCP_PORT to a different port, or find and stop the process:
lsof -i :8000 # macOS/Linux
netstat -ano | findstr :8000 # Windows
License
MIT
