1.217 MCP Server Implementation#
Building an MCP server in Python, 2026: standalone fastmcp 3.x vs the official mcp SDK 2.0 — where FastMCP became MCPServer — plus when to hand-roll it.
At a glance#
Findings checked against this survey’s current text on 2026-08-24.
| Library | Recommendation | License |
|---|---|---|
| FastMCP | primary | Apache-2.0 |
| MCP Python SDK | alternative | MIT |
| FastAPI-MCP | brownfield-only | MIT |
| Raw JSON-RPC | educational | N/A |
What the research found
- TWO different things were called FastMCP. The official SDK renamed ITS copy to MCPServer in mcp 2.0.0; the standalone fastmcp package is still FastMCP. Answering which one you mean is now the first question.
- No single default any more: standalone fastmcp 3.4.7 for ergonomics, official mcp 2.0.0 for the reference implementation
- Superseded: the December 2025 recommendation of “FastMCP 2.0” names a line that no longer exists
- mcp 1.x is maintenance mode, security fixes only (pin mcp>=1.28,<2 to stay deliberately)
- PIN THE MAJOR. Both leading options moved a major inside twelve months; an unbounded >= is a live hazard
Explainer
MCP Server Implementation - Domain Explainer#
Audience: Developers new to MCP who need to build tool servers for LLMs Reading Time: 10 minutes
What is MCP?#
Model Context Protocol (MCP) is an open standard for connecting LLMs (like Claude) to external tools and data sources. Think of it as “USB-C for AI” - a universal connector that lets AI assistants interact with your systems.
Launched by Anthropic in November 2024, MCP has been adopted by:
- OpenAI (ChatGPT, Agents SDK) - March 2025
- Microsoft (Windows integration) - May 2025
- Linux Foundation (Agentic AI Foundation) - December 2025
Why Does MCP Exist?#
Before MCP, every AI integration was custom:
- Each LLM had different tool-calling conventions
- Developers built separate integrations for Claude, GPT, etc.
- No standard way to expose data/functionality to AI
MCP solves this with a single protocol:
- Write one server, connect to any MCP-compatible client
- Standardized tool schemas, resource access, prompt templates
- Protocol-level security and capability negotiation
Core Concepts#
1. MCP Server#
A program that exposes functionality to LLMs. You build this.
2. MCP Client#
An LLM application that connects to servers. Claude Desktop, Claude.ai, ChatGPT desktop are clients.
3. Tools#
Functions the LLM can call. Like API endpoints but for AI:
@mcp.tool()
def search_database(query: str) -> list[dict]:
"""Search the database for matching records."""
return db.search(query)The LLM sees the function name, description, and parameter schema. When it decides to use the tool, MCP handles the invocation.
4. Resources#
Read-only data the LLM can access. Like GET endpoints:
@mcp.resource("config://{section}")
def get_config(section: str) -> str:
"""Get configuration for a section."""
return config[section]5. Prompts#
Reusable message templates:
@mcp.prompt()
def summarize(text: str) -> str:
"""Create a summarization prompt."""
return f"Please summarize:\n\n{text}"How It Works#
┌─────────────┐ JSON-RPC 2.0 ┌─────────────┐
│ Claude │ ◄──────────────────────────► │ Your MCP │
│ Desktop │ (stdio or HTTP) │ Server │
│ (Client) │ │ │
└─────────────┘ └─────────────┘
1. Client discovers server capabilities
2. Client lists available tools/resources
3. When user asks something requiring a tool:
- LLM decides which tool to call
- Client sends tool call to server
- Server executes and returns result
- LLM incorporates result into responseTransport Options#
| Transport | When to Use |
|---|---|
| stdio | Local development, CLI tools. Server runs as subprocess. |
| Streamable HTTP | Production, remote servers. Standard HTTP with optional streaming. |
| SSE | Legacy. Deprecated since late 2024. |
Default: stdio for local, Streamable HTTP for deployed.
Python Library Options#
For Most Projects: FastMCP#
pip install fastmcpfrom fastmcp import FastMCP
mcp = FastMCP("my-server")
@mcp.tool()
def hello(name: str) -> str:
"""Say hello to someone."""
return f"Hello, {name}!"
if __name__ == "__main__":
mcp.run()Why FastMCP:
- Minimal boilerplate
- Decorator-based (Pythonic)
- Built-in auth, deployment tools
- 21k+ GitHub stars, actively maintained
For Protocol Control: MCP SDK#
pip install "mcp[cli]"Use when you need low-level access or are building MCP tooling.
For Existing FastAPI Apps: FastAPI-MCP#
pip install fastapi-mcpAutomatically exposes your REST endpoints as MCP tools.
A Complete Example#
from fastmcp import FastMCP
from datetime import datetime
mcp = FastMCP("task-server")
# In-memory storage
tasks = {}
next_id = 1
@mcp.tool()
def create_task(title: str, priority: str = "medium") -> dict:
"""Create a new task.
Args:
title: The task title
priority: low, medium, or high
"""
global next_id
task = {
"id": next_id,
"title": title,
"priority": priority,
"status": "pending",
"created_at": datetime.now().isoformat()
}
tasks[next_id] = task
next_id += 1
return task
@mcp.tool()
def list_tasks(status: str = "all") -> list[dict]:
"""List all tasks, optionally filtered by status.
Args:
status: Filter by 'pending', 'completed', or 'all'
"""
if status == "all":
return list(tasks.values())
return [t for t in tasks.values() if t["status"] == status]
@mcp.tool()
def complete_task(task_id: int) -> dict:
"""Mark a task as completed.
Args:
task_id: The ID of the task to complete
"""
if task_id not in tasks:
raise ValueError(f"Task {task_id} not found")
tasks[task_id]["status"] = "completed"
tasks[task_id]["completed_at"] = datetime.now().isoformat()
return tasks[task_id]
if __name__ == "__main__":
mcp.run()Running Your Server#
Local Development (stdio)#
# Run directly
python my_server.py
# Or with fastmcp CLI
fastmcp run my_server.pyConnect to Claude Desktop#
Add to Claude Desktop config (~/.config/claude/claude_desktop_config.json):
{
"mcpServers": {
"my-server": {
"command": "python",
"args": ["/path/to/my_server.py"]
}
}
}Restart Claude Desktop. Your tools appear in the interface.
Production (HTTP)#
if __name__ == "__main__":
mcp.run(transport="http", host="0.0.0.0", port=8000)Common Patterns#
Error Handling#
Raise exceptions - FastMCP converts them to MCP errors:
@mcp.tool()
def get_user(user_id: int) -> dict:
if user_id not in users:
raise ValueError(f"User {user_id} not found")
return users[user_id]Type Hints#
FastMCP uses type hints for schema generation:
from pydantic import BaseModel
class Task(BaseModel):
id: int
title: str
priority: str
@mcp.tool()
def get_task(task_id: int) -> Task:
"""Get a task by ID."""
return Task(**tasks[task_id])Async Support#
Both sync and async work:
@mcp.tool()
async def fetch_data(url: str) -> dict:
async with httpx.AsyncClient() as client:
response = await client.get(url)
return response.json()Testing#
FastMCP provides in-memory testing:
from fastmcp import Client
async def test_create_task():
async with Client(mcp) as client:
result = await client.call_tool("create_task", {
"title": "Test task",
"priority": "high"
})
assert result["title"] == "Test task"Security Considerations#
- Input validation: Validate all tool parameters
- Authentication: Use FastMCP’s built-in auth or implement your own
- Least privilege: Only expose what’s necessary
- Rate limiting: Protect against abuse
Next Steps#
- Start simple: Build a server with 2-3 tools
- Test locally: Connect to Claude Desktop
- Add complexity: Resources, prompts, auth
- Deploy: Streamable HTTP for production
Resources#
Glossary#
| Term | Definition |
|---|---|
| MCP | Model Context Protocol - the standard |
| Tool | Function an LLM can invoke |
| Resource | Read-only data an LLM can access |
| Prompt | Reusable message template |
| Transport | Communication channel (stdio, HTTP) |
| FastMCP | High-level Python framework |
| JSON-RPC 2.0 | Underlying message protocol |
S1: Rapid Discovery
S1 Approach — MCP Server Implementation#
Rapid survey of what exists for building an MCP server in Python: what each option is, who maintains it, and what state it is in. No benchmarks and no architecture — those are S2’s.
The boundary#
In scope: libraries you build a server with, in Python. The official SDK, the standalone FastMCP, the FastAPI adapter, and writing JSON-RPC by hand — which is included because it is the option that tells you what the others are doing for you.
Out of scope: the protocol itself, which is a standard rather than a library and is surveyed in 2.074 MCP Protocol; MCP clients, which are a different problem; the gateways and federation layer, which is 1.214; and hosted MCP products, which are 3.xxx.
Read the naming file first#
naming-the-two-fastmcps.md exists because since 2026-07-28 there are two different things
in this comparison and only one is still called FastMCP. A survey that used the name without
saying which would be unreadable a year from now, and unusable today.
Method#
Every version, release date and status was verified against PyPI on the day of the pass — 2026-08-17 for the refresh, extended 2026-08-29 for S2-S4 with download counts, star counts and dependency graphs read from the PyPI JSON API, pypistats.org and the GitHub REST API.
FastAPI-MCP - Library Profile#
🔄 REFRESH NOTE — 2026-08-17
>#
fastapi-mcp0.4.0, last released 2025-07-28 — thirteen months ago. Verified against PyPI. The December 2025 pass recorded the version as “Latest (July 2025)”, which was accurate then and is the same release now.>The caveat has changed in kind. It used to be about fit — brownfield FastAPI apps only. It is now also about maintenance: this is a protocol adapter that has shipped nothing across the largest revision MCP has had, including the move to a stateless core and the official SDK’s own 2.0.>That is not proof of abandonment; a stable library can go quiet. But the risk should be priced deliberately rather than inherited. Check the repository’s recent activity before adopting, and prefer mounting afastmcpormcpserver alongside the FastAPI app over adapting it in place.
Package: fastapi-mcp
Version: 0.4.0 (released 2025-07-28; still current as of 2026-08-17)
License: MIT
Maintainer: Tadata Inc.
Repository: https://github.com/tadata-org/fastapi_mcp
PyPI: https://pypi.org/project/fastapi-mcp/
Overview#
FastAPI-MCP automatically exposes existing FastAPI endpoints as MCP tools with zero configuration. It’s designed for brownfield projects where you want to add MCP capability to an existing REST API without rewriting code.
Installation#
pip install fastapi-mcp
# or
uv add fastapi-mcpRequirements: Python >= 3.10, FastAPI
Key Features#
Zero Configuration#
Automatically introspects FastAPI routes and creates MCP tools:
from fastapi import FastAPI
from fastapi_mcp import FastApiMCP
app = FastAPI()
@app.get("/users/{user_id}")
def get_user(user_id: int):
return {"id": user_id, "name": "John"}
@app.post("/tasks")
def create_task(title: str, priority: str = "medium"):
return {"title": title, "priority": priority}
# Add MCP with one line
mcp = FastApiMCP(app)
mcp.mount() # Available at /mcpSchema Preservation#
- Preserves Pydantic request/response models
- Maintains endpoint documentation
- Swagger-equivalent descriptions in MCP tools
Authentication Reuse#
Uses existing FastAPI Depends() for authorization:
from fastapi import Depends, HTTPException
def verify_token(token: str = Header(...)):
if token != "valid":
raise HTTPException(401)
return token
@app.get("/protected")
def protected_endpoint(user=Depends(verify_token)):
return {"data": "secret"}
# MCP tools inherit the same authASGI Native#
Direct ASGI interface - no HTTP hop:
- Efficient communication
- No network overhead for local calls
- Preserves request context
Deployment Options#
Same Application#
app = FastAPI()
mcp = FastApiMCP(app)
mcp.mount() # /mcp endpoint added to appSeparate Application#
app = FastAPI()
mcp_app = FastApiMCP(app).create_app()
# Deploy mcp_app separatelyComparison to FastMCP#
| Aspect | FastAPI-MCP | FastMCP 2.0 |
|---|---|---|
| Use Case | Brownfield (existing API) | Greenfield (new project) |
| Configuration | Zero | Minimal |
| Auth | Reuses FastAPI Depends | Built-in OAuth providers |
| Learning Curve | Very low (if you know FastAPI) | Low |
| Features | Endpoint exposure only | Full MCP primitives |
Strengths#
- Zero configuration: Automatic endpoint introspection
- Preserves existing code: No rewrite needed
- Auth reuse: Same auth as REST endpoints
- ASGI native: Efficient, no HTTP hop
- Dual interface: Maintain REST + MCP simultaneously
Limitations#
- FastAPI-only: Requires FastAPI application
- Endpoint-focused: Doesn’t support MCP resources/prompts natively
- Brownfield-only: Not suitable for MCP-first projects
- Less control: Auto-generation may not match desired MCP schema
When to Choose FastAPI-MCP#
- Existing FastAPI application
- Want to expose REST endpoints to LLMs
- Need to maintain both REST and MCP interfaces
- Rapid MCP addition without code changes
When to Avoid#
- New MCP-first projects (use FastMCP)
- Need full MCP primitives (resources, prompts)
- Non-FastAPI applications
- Want custom tool schemas
Related: FastMCP’s FastAPI Integration#
FastMCP 2.0 also offers FastAPI integration via FastMCP.from_fastapi():
from fastmcp import FastMCP
mcp = FastMCP.from_fastapi(app)This is similar to FastAPI-MCP but with access to FastMCP’s full feature set.
Example: Complete Integration#
from fastapi import FastAPI, Depends, HTTPException, Header
from fastapi_mcp import FastApiMCP
from pydantic import BaseModel
app = FastAPI(title="Task API")
class Task(BaseModel):
id: int
title: str
priority: str
tasks_db = {}
def api_key_auth(x_api_key: str = Header(...)):
if x_api_key != "secret":
raise HTTPException(401, "Invalid API key")
return x_api_key
@app.get("/tasks", response_model=list[Task])
def list_tasks(auth=Depends(api_key_auth)):
"""List all tasks."""
return list(tasks_db.values())
@app.post("/tasks", response_model=Task)
def create_task(title: str, priority: str = "medium", auth=Depends(api_key_auth)):
"""Create a new task."""
task_id = len(tasks_db) + 1
task = Task(id=task_id, title=title, priority=priority)
tasks_db[task_id] = task
return task
# Add MCP - tools automatically created for list_tasks and create_task
# Auth is enforced via the same Depends()
mcp = FastApiMCP(app)
mcp.mount()
# MCP available at: http://localhost:8000/mcp
# REST available at: http://localhost:8000/tasksSources#
- https://github.com/tadata-org/fastapi_mcp
- https://pypi.org/project/fastapi-mcp/
- https://www.infoq.com/news/2025/04/fastapi-mcp/
- https://www.analyticsvidhya.com/blog/2025/05/fastapi-mcp/
FastMCP - Library Profile#
🔄 REFRESH NOTE — 2026-08-17
>#Now
fastmcp3.4.7 (released 2026-08-10), verified against PyPI. The profile below describes the 2.x line and its judgements still read true; the version numbers do not.>The class name did NOT change. It is stillfrom fastmcp import FastMCP. What changed is that the official SDK renamed its copy of FastMCP toMCPServerinmcp2.0.0 — a different package. This is the single most confusable fact in the category right now; seenaming-the-two-fastmcps.md.>Pin the major. This project has moved a major roughly annually (2.x → 3.x), sofastmcp>=3,<4. An unbounded>=is a live hazard here, not untidiness.
Package: fastmcp
Version: 2.14.0 (December 2025) — superseded, see refresh note
License: Apache-2.0
Maintainer: Jeremiah Lowin (@jlowin), Prefect
Repository: https://github.com/jlowin/fastmcp
Documentation: https://gofastmcp.com
Overview#
FastMCP is a high-level Python framework for building MCP servers and clients. Originally created independently, FastMCP 1.0 was incorporated into the official MCP SDK in 2024. FastMCP 2.0 is the actively maintained standalone version with enterprise features.
Installation#
pip install fastmcp
# or
uv add fastmcpRequirements: Python >= 3.10
Key Statistics#
| Metric | Value |
|---|---|
| GitHub Stars | 21.2k |
| GitHub Forks | 1.6k |
| Open Issues | 201 |
| PyPI Downloads | High (exact count varies) |
| Last Release | December 11, 2025 |
Core Features#
Tools#
Decorated Python functions exposing functionality to LLMs:
from fastmcp import FastMCP
mcp = FastMCP("my-server")
@mcp.tool()
def search_database(query: str, limit: int = 10) -> list[dict]:
"""Search the database for matching records."""
return db.search(query, limit=limit)Resources#
Read-only data sources with templated URIs:
@mcp.resource("config://{section}")
def get_config(section: str) -> str:
"""Get configuration for a section."""
return config.get(section)Prompts#
Reusable message templates:
@mcp.prompt()
def summarize_prompt(text: str) -> str:
"""Create a summarization prompt."""
return f"Please summarize the following text:\n\n{text}"Context#
Access to session features within decorated functions:
from fastmcp import Context
@mcp.tool()
async def long_task(ctx: Context) -> str:
await ctx.report_progress(0.5, "Halfway done")
return "Complete"Enterprise Features (FastMCP 2.0)#
Authentication#
Built-in OAuth providers with zero-configuration setup:
- GitHub
- Azure
- Auth0
- WorkOS
- Descope
- Discord
- JWT (custom)
- API Keys
Deployment Options#
- Local development via CLI
- FastMCP Cloud (hosted)
- Self-hosted HTTP/SSE
Advanced Patterns#
- Server composition and mounting
- Proxy servers for transport bridging
- OpenAPI integration (
FastMCP.from_openapi()) - FastAPI integration (
FastMCP.from_fastapi()) - Tool transformation
Testing#
In-memory client for unit testing without network:
from fastmcp import Client
async with Client(mcp) as client:
result = await client.call_tool("my_tool", {"param": "value"})Transport Support#
| Transport | Support | Notes |
|---|---|---|
| stdio | Full | Default for local |
| Streamable HTTP | Full | Production recommended |
| SSE | Full | Legacy support |
| WebSocket | Via extension | mcp[ws] |
Dependency Note#
FastMCP depends on Cyclopts for CLI functionality. Cyclopts v4 includes docutils as a transitive dependency with complex licensing. Organizations with strict compliance may need to use Cyclopts v5 alpha or wait for stable release.
Relationship to Official SDK#
| Aspect | mcp.server.fastmcp | fastmcp (standalone) |
|---|---|---|
| Version | 1.x (in SDK) | 2.x |
| Features | Basic | Full enterprise |
| Auth | Limited | Comprehensive |
| Maintained | By Anthropic | By Prefect |
| Import | from mcp.server.fastmcp | from fastmcp |
Recommendation: Use standalone fastmcp for new projects (more features, actively developed).
Strengths#
- Simplicity: Minimal boilerplate, decorator-based
- Production-ready: Enterprise auth, deployment tools
- Well-documented: Comprehensive docs at gofastmcp.com
- Active community: 21.2k stars, responsive maintainers
- Proven: Validated in spawn-experiments 1.618
Limitations#
- Apache-2.0 license: May require attribution (not MIT like SDK)
- Cyclopts dependency: Licensing complexity for some orgs
- Abstraction layer: Less control than raw SDK
- Prefect backing: Tied to Prefect’s business interests
When to Choose FastMCP#
- New MCP server projects
- Rapid prototyping
- Production deployments
- Teams wanting Pythonic API
- Enterprise auth requirements
When to Avoid#
- Need protocol-level control
- Minimal dependency requirements
- Building MCP tooling/frameworks
- License compliance concerns with Apache-2.0
Example: Complete Server#
from fastmcp import FastMCP
mcp = FastMCP("task-manager")
tasks = {}
task_id = 0
@mcp.tool()
def create_task(title: str, priority: str = "medium") -> dict:
"""Create a new task."""
global task_id
task_id += 1
tasks[task_id] = {"id": task_id, "title": title, "priority": priority}
return tasks[task_id]
@mcp.tool()
def list_tasks(status: str = "all") -> list[dict]:
"""List all tasks."""
return list(tasks.values())
if __name__ == "__main__":
mcp.run()Sources#
- https://github.com/jlowin/fastmcp
- https://pypi.org/project/fastmcp/
- https://gofastmcp.com
- https://www.prefect.io/fastmcp
MCP Python SDK - Library Profile#
🔄 REFRESH NOTE — 2026-08-17
>#Now
mcp2.0.0, released 2026-07-28 — the same day as the protocol revision. Verified against PyPI. This is a larger change than a version bump:>
FastMCPis nowMCPServer, and there is a first-classClient. The import moves frommcp.server.fastmcptomcp.server.mcpserver.- Protocol types split into their own package —
mcp-types(imported asmcp_types), also 2.0.0 on 2026-07-28. You can now depend on the wire types without the server framework, which is a real reason to prefer the official SDK that did not exist before.- Backward compatible in the direction that matters: a v2 server supports the
2026-07-28revision and still serves 2025-era clients.- v1.x is maintenance mode — security fixes only. Pin
mcp>=1.28,<2to stay there deliberately.- Numerous other breaking changes, each with before-and-after code in the published migration guide.
>The profile below describes 1.x. Its account of why you would choose the official SDK still holds and is arguably stronger now, thanks tomcp-types.
Package: mcp
Version: 1.24.0 (December 2025) — superseded, see refresh note
License: MIT
Maintainer: Anthropic (dsp, jspahrsummers)
Repository: https://github.com/modelcontextprotocol/python-sdk
Documentation: https://modelcontextprotocol.github.io/python-sdk/
Overview#
The official Python SDK for Model Context Protocol, maintained by Anthropic. It implements the full MCP specification including clients, servers, transports, and protocol messages. This is the reference implementation that other libraries build upon.
Installation#
pip install "mcp[cli]"
# or
uv add "mcp[cli]"Requirements: Python >= 3.10
Optional Extensions#
| Extra | Purpose |
|---|---|
cli | Command-line interface tools |
rich | Enhanced terminal output |
ws | WebSocket transport support |
Key Statistics#
| Metric | Value |
|---|---|
| GitHub Stars | 20.6k |
| GitHub Forks | 2.9k |
| Open Issues | 241 |
| License | MIT |
| Last Release | December 12, 2025 |
Core Capabilities#
Server Building#
from mcp.server import Server
from mcp.server.stdio import stdio_server
server = Server("my-server")
@server.list_tools()
async def list_tools():
return [
{"name": "my_tool", "description": "Does something", "inputSchema": {...}}
]
@server.call_tool()
async def call_tool(name: str, arguments: dict):
if name == "my_tool":
return {"content": [{"type": "text", "text": "Result"}]}
async with stdio_server() as (read, write):
await server.run(read, write, server.create_initialization_options())Client Building#
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
server_params = StdioServerParameters(
command="python",
args=["my_server.py"]
)
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools()FastMCP Integration (Built-in)#
The SDK includes mcp.server.fastmcp (FastMCP 1.x):
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("server")
@mcp.tool()
def my_tool(param: str) -> str:
"""Tool description."""
return f"Result: {param}"Transport Support#
| Transport | Class | Use Case |
|---|---|---|
| stdio | stdio_server() | Local subprocess |
| Streamable HTTP | streamable_http_server() | Remote/networked |
| SSE | sse_server() | Legacy (deprecated) |
| WebSocket | websocket_server() | Real-time bidirectional |
Protocol Features#
Resources#
Read-only data exposure:
@server.list_resources()
async def list_resources():
return [{"uri": "file:///path", "name": "My File"}]
@server.read_resource()
async def read_resource(uri: str):
return {"contents": [{"uri": uri, "text": "Content"}]}Tools#
Executable functions:
@server.list_tools()
async def list_tools():
return [{
"name": "search",
"description": "Search database",
"inputSchema": {
"type": "object",
"properties": {"query": {"type": "string"}},
"required": ["query"]
}
}]Prompts#
Reusable templates:
@server.list_prompts()
async def list_prompts():
return [{"name": "summarize", "description": "Summarization prompt"}]
@server.get_prompt()
async def get_prompt(name: str, arguments: dict):
return {"messages": [{"role": "user", "content": {"type": "text", "text": "..."}}]}Structured Output#
Type-safe responses (spec revision 2025-06-18):
from pydantic import BaseModel
class SearchResult(BaseModel):
items: list[str]
total: int
@mcp.tool()
def search(query: str) -> SearchResult:
return SearchResult(items=["a", "b"], total=2)Authentication#
OAuth 2.1 resource server implementation (mcp.server.auth):
- RFC 9728 compliance (Protected Resource Metadata)
- AS discovery
- Token validation
- Scope enforcement
Relationship to FastMCP#
| Component | Source | Notes |
|---|---|---|
mcp.server.fastmcp | FastMCP 1.x | Incorporated into SDK |
fastmcp (pip) | FastMCP 2.x | Standalone, more features |
The SDK’s mcp.server.fastmcp is FastMCP 1.0, suitable for basic use. For production with enterprise features, use standalone fastmcp (2.x).
Strengths#
- Official: Reference implementation by Anthropic
- MIT License: Maximum permissiveness
- Complete: Full protocol implementation
- Low-level control: Direct access to all primitives
- Minimal: Fewer abstractions, smaller footprint
Limitations#
- More verbose: Requires more boilerplate than FastMCP 2.0
- Basic auth: OAuth support less comprehensive than FastMCP 2.0
- Less documentation: Compared to FastMCP’s gofastmcp.com
- Steeper learning curve: Protocol concepts exposed directly
When to Choose MCP SDK#
- Need protocol-level control
- Building MCP tooling/frameworks
- Compliance requires official implementation
- Minimal dependency footprint
- MIT license requirement
When to Avoid#
- Rapid prototyping (use FastMCP 2.0)
- Enterprise auth needs (FastMCP 2.0 is better)
- Teams unfamiliar with MCP protocol details
- Production deployment with auth requirements
Industry Adoption#
MCP has been adopted by major players:
- OpenAI: ChatGPT desktop, Agents SDK, Responses API (March 2025)
- Microsoft: Native Windows integration (May 2025)
- Linux Foundation: MCP donated to Agentic AI Foundation (December 2025)
Sources#
- https://github.com/modelcontextprotocol/python-sdk
- https://pypi.org/project/mcp/
- https://modelcontextprotocol.io
- https://modelcontextprotocol.github.io/python-sdk/
Which FastMCP do you mean? — read this first#
Added 2026-08-17. This is now the first question a reader of this survey has to answer, and until 2026-07-28 it did not exist.
Two different things were both called FastMCP#
| Package | Class | Current | Provenance | |
|---|---|---|---|---|
| The standalone project | fastmcp | FastMCP | 3.4.7 (2026-08-10) | jlowin / PrefectHQ; the original |
| The official SDK’s copy | mcp | FastMCPMCPServer | 2.0.0 (2026-07-28) | modelcontextprotocol/python-sdk |
The official Python SDK had absorbed a version of FastMCP as mcp.server.fastmcp.FastMCP.
Two libraries, two release cadences, one name — and code samples on the internet that did
not say which one they meant.
The 2.0.0 release fixed it by renaming its own copy. From the release notes:
"
FastMCPis nowMCPServer, and there is a first-classClient"
So:
# Official SDK, 1.x — deprecated import path
from mcp.server.fastmcp import FastMCP
# Official SDK, 2.x — the rename
from mcp.server.mcpserver import MCPServer
# Standalone project, 3.x — UNCHANGED, still FastMCP
from fastmcp import FastMCPWhat this means in practice#
If you use the standalone fastmcp package, nothing about your code changed. It is
still FastMCP, it is on 3.x, and the rename happened somewhere else. Reading the August
2026 coverage and concluding “FastMCP is now MCPServer, I must rename everything” is the
predictable mistake, and it is wrong for the majority of existing code.
If you use the official SDK, the rename is one of many breaking changes in 2.0.0, and there is a published migration guide with before-and-after code for each.
The rest of what shipped in mcp 2.0.0 (2026-07-28)#
Shipped the same day as the 2026-07-28 protocol revision, which is not a coincidence —
this is the SDK catching up to a stateless protocol.
FastMCP→MCPServer, plus a first-classClient- Protocol types split out into their own package:
mcp-types(imported asmcp_types), also 2.0.0 on 2026-07-28 - Backward compatibility retained: v2 supports the
2026-07-28revision while still serving earlier 2025-era clients from the same server - v1.x is maintenance mode — security fixes only. Pin
mcp>=1.28,<2to stay put deliberately rather than by accident
How to choose between them#
The two are now different products rather than two names for one thing, so this is a real decision:
| Choose | When |
|---|---|
Standalone fastmcp (3.x) | You want the batteries-included ergonomics, and you are content to track a project that moves faster than the spec |
Official SDK mcp (2.x) | You want the reference implementation, the closest tracking of the spec, and mcp-types as a dependency you can depend on independently |
Do not choose on the name. Whichever you pick, pin the major version — both lines moved
a major inside twelve months, and an unbounded >= will eventually pull the other one’s
idea of what your code should look like.
Raw MCP Implementation - Approach Profile#
Package: None (stdlib only)
Dependencies: Python stdlib, optionally json, sys
License: N/A
Use Case: Educational, embedded, minimal footprint
Overview#
MCP can be implemented without any SDK by directly handling JSON-RPC 2.0 messages over stdio or HTTP. This approach is educational and useful for understanding protocol internals or when external dependencies are unacceptable.
Key Insight#
“At its core, MCP is just JSON-RPC 2.0 over newline-delimited streams. No magic, no hidden complexity—just structured messages over STDIO.”
Protocol Basics#
Transport: STDIO#
Messages are newline-delimited JSON over stdin/stdout:
{"jsonrpc":"2.0","method":"initialize","params":{...},"id":1}\n
{"jsonrpc":"2.0","result":{...},"id":1}\nMessage Types#
- Request: Has
method,params,id - Response: Has
resultorerror,id - Notification: Has
method,params, noid
Minimal Implementation#
import sys
import json
from datetime import datetime
def send_response(id, result):
response = {"jsonrpc": "2.0", "result": result, "id": id}
sys.stdout.write(json.dumps(response) + "\n")
sys.stdout.flush()
def send_error(id, code, message):
response = {
"jsonrpc": "2.0",
"error": {"code": code, "message": message},
"id": id
}
sys.stdout.write(json.dumps(response) + "\n")
sys.stdout.flush()
def handle_initialize(id, params):
send_response(id, {
"protocolVersion": "2024-11-05",
"capabilities": {"tools": {}},
"serverInfo": {"name": "raw-server", "version": "1.0.0"}
})
def handle_tools_list(id, params):
send_response(id, {
"tools": [{
"name": "get_time",
"description": "Get current time",
"inputSchema": {"type": "object", "properties": {}}
}]
})
def handle_tools_call(id, params):
tool_name = params.get("name")
if tool_name == "get_time":
send_response(id, {
"content": [{"type": "text", "text": datetime.now().isoformat()}]
})
else:
send_error(id, -32601, f"Unknown tool: {tool_name}")
def main():
for line in sys.stdin:
try:
msg = json.loads(line.strip())
method = msg.get("method")
id = msg.get("id")
params = msg.get("params", {})
if method == "initialize":
handle_initialize(id, params)
elif method == "tools/list":
handle_tools_list(id, params)
elif method == "tools/call":
handle_tools_call(id, params)
elif method == "notifications/initialized":
pass # Notification, no response
else:
if id: # Only respond to requests, not notifications
send_error(id, -32601, f"Unknown method: {method}")
except json.JSONDecodeError:
send_error(None, -32700, "Parse error")
if __name__ == "__main__":
main()Protocol Lifecycle#
- Initialize: Client sends
initialize, server responds with capabilities - Initialized: Client sends
notifications/initialized(no response) - Operation: Client calls
tools/list,tools/call, etc. - Shutdown: Client closes connection
Capabilities Declaration#
Server declares what it supports:
{
"capabilities": {
"tools": {},
"resources": {},
"prompts": {},
"logging": {}
}
}Only declare capabilities you implement.
Error Codes#
| Code | Meaning |
|---|---|
| -32700 | Parse error |
| -32600 | Invalid request |
| -32601 | Method not found |
| -32602 | Invalid params |
| -32603 | Internal error |
Strengths#
- Zero dependencies: Pure Python stdlib
- Full understanding: Learn protocol internals
- Minimal footprint: ~50 lines for basic server
- Debuggable: Easy to log and inspect messages
- Portable: Works anywhere Python runs
Limitations#
- No schema validation: Must implement manually
- No transport abstraction: Locked to one transport
- No error handling: Must build robust error handling
- No testing utilities: No mock clients
- Maintenance burden: Must track protocol changes
When to Choose Raw Implementation#
- Learning MCP protocol internals
- Extremely resource-constrained environments
- Cannot add external dependencies
- Building custom transports
- Protocol debugging and analysis
When to Avoid#
- Production applications (use FastMCP or SDK)
- Time-constrained projects
- Teams unfamiliar with JSON-RPC
- Need resources, prompts, or complex features
Learning Resources#
- Understanding MCP Through Raw STDIO Communication
- MCP Protocol Specification
- JSON-RPC 2.0 Specification
Advanced: HTTP Transport#
For HTTP, implement POST endpoint accepting JSON-RPC:
from http.server import HTTPServer, BaseHTTPRequestHandler
import json
class MCPHandler(BaseHTTPRequestHandler):
def do_POST(self):
content_length = int(self.headers['Content-Length'])
body = self.rfile.read(content_length)
request = json.loads(body)
# Route to handlers...
response = handle_request(request)
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps(response).encode())Recommendation#
Use raw implementation only for:
- Educational purposes
- Understanding protocol before using SDK
- Environments where no deps are allowed
For all other cases, use FastMCP (simplest) or MCP SDK (official).
1.217 MCP Server Implementation — S1 Recommendations#
Refreshed: 2026-08-17 — all versions verified against PyPI on the day Previous pass: December 2025 Category: 1.2XX AI & LLM Application Frameworks Status: S1 Complete
Read
naming-the-two-fastmcps.mdfirst. Since 2026-07-28 there are two different things in this comparison and only one of them is still called FastMCP.
Executive Summary#
There is no longer a single default, and the December 2025 recommendation of “FastMCP 2.0” now names a version that has been superseded.
Both leading options shipped a major version in the last twelve months, in response to the largest protocol revision since MCP launched. The choice is now between two different products rather than between a convenience wrapper and the thing it wraps.
Library landscape — verified 2026-08-17#
| Library | Package | Version | Released | Status |
|---|---|---|---|---|
| Standalone FastMCP | fastmcp | 3.4.7 | 2026-08-10 | Active; class still FastMCP |
| Official MCP SDK | mcp | 2.0.0 | 2026-07-28 | Active; FastMCP → MCPServer |
| — protocol types | mcp-types | 2.0.0 | 2026-07-28 | New — split out of the SDK |
| Official SDK, previous | mcp 1.x | 1.28+ | — | Maintenance: security fixes only |
| FastAPI-MCP | fastapi-mcp | 0.4.0 | 2025-07-28 | ⚠️ No release in 13 months |
| Raw JSON-RPC | — | n/a | n/a | Timeless; see raw-implementation.md |
What changed since December 2025: FastMCP went 2.14 → 3.x. The official SDK went 1.24 →
2.0 and renamed its core class. mcp-types did not exist. FastAPI-MCP has not shipped.
Recommendations#
For most new servers: standalone fastmcp 3.x#
Still the ergonomic choice, still the fastest way from nothing to a working server, and
still called FastMCP — so existing code and existing tutorials remain valid.
Pin it: fastmcp>=3,<4. This project has moved a major roughly annually.
When you want the reference implementation: official mcp 2.x#
Choose it when tracking the specification closely matters more than ergonomics — and note
that 2.0 makes this more attractive than it was, because mcp-types is now a package you
can depend on for wire types without taking the whole server framework.
The migration cost is real: 2.0.0 has numerous breaking changes beyond the rename, with a
published migration guide giving before-and-after code for each. Backward compatibility is
better than the version number suggests — a v2 server supports the 2026-07-28 revision
while still serving 2025-era clients.
Pin it: mcp>=2,<3. Or mcp>=1.28,<2 to stay on 1.x deliberately, knowing it now receives
security fixes only.
For existing FastAPI apps: fastapi-mcp — with a caveat that has grown teeth#
The December 2025 pass said “brownfield projects only”. That still holds, but the reason to hesitate has changed from scope to maintenance: the last release was 2025-07-28, thirteen months ago, spanning the largest protocol revision MCP has had.
That is not proof of abandonment — a stable library can go quiet — but a protocol adapter
that has not shipped across a stateless-core rewrite is carrying risk that its users should
price deliberately. Check the repository’s recent activity before adopting, and prefer
mounting a fastmcp/mcp server alongside the FastAPI app over adapting it in place.
For learning or minimal dependencies: raw JSON-RPC#
Unchanged and still valid. The protocol is JSON-RPC 2.0, and the 2026-07-28 revision made
the base protocol simpler — stateless, self-contained requests with per-request capability
negotiation — so hand-rolling is, if anything, more tractable than it was. See
raw-implementation.md.
What the refresh changes about the advice#
- “Use FastMCP 2.0” is retired. The number now points at a superseded line.
- The first question is which FastMCP, not which library. See
naming-the-two-fastmcps.md. - Pin your major. Both leading options moved a major within twelve months. An
unbounded
>=is now a live hazard rather than untidiness — it was exactly the state this rig’s own survey-mcp was in (fastmcp>=0.1.0, held at 2.x only by a lockfile). - FastAPI-MCP’s caveat is now about maintenance, not just fit.
Related#
- 2.074 MCP Protocol — the specification itself, current at
2026-07-28. Note that a current protocol survey does not make an implementation survey current; that is why this one needed a refresh and 2.074 did not. - 1.200 LLM Orchestration Frameworks — what runs above an MCP server
- 1.201 LLM Agent Frameworks — when the model, not your code, decides the next step
S2: Comprehensive
S2 Approach — MCP Server Implementation#
S1 asked what exists. S2 asks what you are actually adopting, because in this category the surface a library gives you and the thing underneath it have come apart.
The three questions#
1. Is it a wrapper or an implementation? The assumption most people carry — that FastMCP is a convenience layer over the official SDK — was true once and is now false. What a library depends on decides what you inherit when the protocol changes, and it is checkable rather than a matter of reputation.
2. What does the protocol actually require, and who does it for you? MCP is JSON-RPC 2.0 over a transport with a defined handshake. Everything above that is ergonomics, and knowing where the line falls is what makes the raw option legible rather than heroic.
3. What happens at the next protocol revision? This category has had two major versions in twelve months. A survey that ranks today’s APIs without asking who ships the next migration is answering a question that expires.
What this pass does not do#
No benchmarks. Server frameworks here are thin over Starlette and the work is I/O and
model latency, not framework overhead. A wall-clock comparison would measure the transport
and mislead. That is a considered exclusion, and measurement-plan.md says so rather than
leaving it implied.
No verdict on the protocol. Whether MCP is the right standard is 2.074’s question.
No naming of private work. This survey is category-first. Applications that happen to motivate it appear, if at all, as an anonymous persona in S3.
What you are actually adopting#
The finding that changes the choice: FastMCP is not a wrapper#
The common understanding — and this survey’s own December 2025 pass — is that FastMCP is a convenience layer over the official SDK. That was true and is now false.
Checked against PyPI on 2026-08-29: fastmcp 3.4.7 does not depend on mcp at all. Its
requires_dist contains no reference to the official SDK. It is an independent
implementation of the protocol that happens to have started as a wrapper and to have kept
the name.
The official SDK’s own dependencies say what it is made of: pydantic>=2.12,
sse-starlette>=3.0, starlette, uvicorn. Both projects sit on the same ASGI substrate;
neither sits on the other.
Why this decides things rather than being trivia. A wrapper inherits protocol correctness from the thing it wraps — when the spec moves, the layer underneath moves and you get it. Two independent implementations means two separately-maintained readings of the same specification, and the question “does this server actually conform” has two answers that can differ. That is a different risk from picking the less popular API.
What the protocol requires, so the raw option is legible#
MCP is JSON-RPC 2.0 over a transport, with a defined handshake: initialize, a
capability exchange, then request/response and notifications. The methods a server
implements are a small set — list tools, call a tool, list resources, read a resource, and
the prompt equivalents.
A library gives you four things over writing that yourself:
Schema generation. A tool has to advertise a JSON Schema for its arguments. Both libraries derive it from Python type hints via Pydantic, which is the single largest saving and the one most people never think about.
Transport plumbing. stdio framing, or HTTP with Server-Sent Events for streaming. The SSE half is where hand-rolled servers get subtly wrong.
The handshake and capability negotiation, which is version-sensitive and dull.
Error mapping from Python exceptions to JSON-RPC error objects.
raw-implementation.md in S1 covers doing it by hand. It is not a recommendation for most
readers, and it is the reason the others are worth their dependency.
The two deployment shapes, which the survey’s advice depends on#
stdio — the client spawns your server as a subprocess and talks over pipes. One process per session, started and killed constantly. Cold start is the cost that matters, and authentication is whatever the parent process already had.
HTTP + SSE — a long-lived server, many clients, streaming responses. Now you have all the questions a web service has: auth, TLS, concurrency, deployment. 1.242 covers the server that runs it, 1.241 the framework shape around it.
Most guidance in this category assumes one shape without saying which, which is why advice that reads as contradictory often is not — it is answering a different deployment.
The naming collision, restated because it is load-bearing#
Since 2026-07-28 the official SDK renamed its high-level class FastMCP → MCPServer, and
the standalone project kept FastMCP. So from mcp.server.fastmcp import FastMCP and
from fastmcp import FastMCP were once nearly the same thing and are now two different
codebases with one class name.
Any tutorial, answer or model output written before that date is ambiguous, and the ambiguity
is invisible — the code imports, runs, and is a different library than the reader thinks. See
naming-the-two-fastmcps.md.
Feature comparison — MCP server implementation#
Structural properties and adoption, not benchmarks (measurement-plan.md says why there are
none). Every figure read from PyPI, pypistats.org and the GitHub REST API on 2026-08-29.
The landscape, measured#
| Package | Version (date) | Weekly | Stars | Open issues | Last push | License | |
|---|---|---|---|---|---|---|---|
| Official SDK | mcp | 2.1.1 (2026-08-25) | 53,319,102 | 24,155 | 391 | 2026-08-28 | MIT |
| FastMCP | fastmcp | 3.4.7 (2026-08-10) | 11,403,337 | 27,435 | 293 | 2026-08-29 | Apache-2.0 |
| FastAPI-MCP | fastapi-mcp | 0.4.0 (2025-07-28) | 415,881 | 11,991 | 178 | 2025-11-24 | MIT |
| Raw JSON-RPC | — | n/a | — | — | — | — | — |
Substrate both leading options sit on: pydantic 197.9M, starlette 112.6M,
uvicorn 111.9M, httpx-sse 39.9M weekly.
The inversion, and how to read it#
FastMCP has more GitHub stars than the official SDK — 27,435 against 24,155 — and about a fifth of the downloads.
Stars accumulate over a project’s whole life and never decay; weekly downloads measure what
CI installed this week. When they disagree, they are answering different questions, and here
a third factor decides which is misleading: mcp is a transitive dependency. Anything
that speaks MCP pulls it in, so its 53.3M counts installs by things that never chose it. The
star count is closer to a measure of who went looking, and the download count is closer to a
measure of what is deployed.
Neither number says FastMCP is better or worse. What they say together is that the official SDK’s lead is smaller than 5:1 among people actually making this choice, and larger than zero.
The property that decides it#
Depends on mcp? | So when the protocol moves… | |
|---|---|---|
| Official SDK | it is mcp | you get the reference reading by definition |
| FastMCP | no — independent implementation | a second team must read the spec and ship |
| FastAPI-MCP | yes | inherits, and has not shipped in 13 months |
| Raw JSON-RPC | no | you are the second team |
This is the table to look at before the API-taste one. Two independent implementations means two readings of one specification, and “does my server conform” stops having a single answer.
Ergonomics#
| Official SDK | FastMCP | FastAPI-MCP | |
|---|---|---|---|
| Tool from a typed function | decorator | decorator | derived from existing routes |
| Schema from type hints | yes (Pydantic) | yes (Pydantic) | yes (FastAPI’s) |
| stdio transport | yes | yes | via the app |
| HTTP + SSE | yes | yes | via the app |
| Client library too | yes | yes | no |
| Auth helpers | minimal | more | inherits FastAPI’s |
| High-level class | MCPServer (was FastMCP) | FastMCP | — |
FastMCP’s larger surface is its argument: server and client, more auth scaffolding, more opinions. The official SDK’s smaller surface is its argument: less to be wrong about, and it is what the specification’s own authors ship.
Maintenance, in three different states#
Both leaders are current — pushed within a day of this reading, majors shipped inside twelve months in response to the largest protocol revision since MCP launched.
FastAPI-MCP is not. No release since 2025-07-28 — thirteen months — and no repository push since 2025-11-24, nine months. 11,991 stars and 178 open issues. That is the profile 1.250 learned to distinguish from Flake8’s quiet-but-answered: here the tracker is not being emptied and nothing has shipped, in a category that revised its protocol twice meanwhile. A protocol library that misses a protocol revision is not merely stale.
Licensing#
MIT for the official SDK and FastAPI-MCP; Apache-2.0 for FastMCP. Both permissive; the difference is Apache’s explicit patent grant, which some legal reviews prefer and none of the personas in S3 turn on.
Measurement plan (Step 3.5) — 1.217#
Written before S2. The conclusion is that this subject does not get a benchmark, and the reasoning matters more than the result.
Why not#
The frameworks are thin over the same substrate. The official SDK requires
starlette, uvicorn, sse-starlette and pydantic; FastMCP is an independent
implementation but sits on the same kind of ASGI stack. Timing “which MCP server framework
is faster” would mostly measure Starlette, which 1.242 ASGI & WSGI Application Servers
already measures properly.
The cost is not in the framework. An MCP server’s wall time is dominated by the tool call it dispatches — a database query, an HTTP request, a model round-trip. Framework overhead is a rounding error against that, and a benchmark that ignored it would produce a ratio with no bearing on anything a reader experiences.
A ratio with no workload is not publishable, which is this rig’s house rule. There is no representative MCP workload: a server exposing three read-only tools and one exposing a filesystem behave nothing alike.
What replaces it#
Structural comparison, which is what actually decides this choice:
- Dependency graphs, read from PyPI on 2026-08-29 — the fact that answers “wrapper or implementation”, checkable and dated.
- Adoption and maintenance signals — downloads, stars, last push, open issues.
- Protocol-version exposure — which library shipped the last revision, and how fast.
What WOULD be worth measuring, if anyone needed it#
Cold-start time, for a server spawned per session over stdio rather than run long-lived over HTTP. That is a real cost in a real deployment shape, it is not dominated by tool latency, and it would distinguish an independent implementation from one carrying the official SDK’s dependency tree. Not run here because no persona in S3 turned on it; recorded so the next pass does not have to rediscover the idea.
S2 Recommendation — what the structure says#
The choice is not the one the guidance describes#
Most published guidance poses this as “official SDK or the nicer API”. As of 2026 it is “one implementation of the protocol, or two”.
fastmcp 3.4.7 does not depend on mcp. It is an independent implementation, and adopting
it means a second team’s reading of the specification stands between your server and
conformance. That is not an argument against it — the project is active, well-starred and
shipping — but it is a different kind of decision from picking a convenience wrapper, and
the wrapper framing is what most guidance still uses.
Default: the official SDK#
Because when the protocol moves, the reference implementation is the thing that moves first by definition, and this protocol has moved twice in twelve months. 53.3M weekly downloads, MIT, pushed the day before this reading.
Take it unless something below applies.
Take FastMCP when its surface is the point#
It ships a client as well as a server, and more authentication scaffolding. If you are building both halves, or want the auth work done, that is real and the official SDK does not match it. 27,435 stars — more than the SDK — says a lot of people made this call deliberately.
Accept the trade knowingly: an independent implementation, and a name that now means something different than it did in tutorials written before 2026-07-28.
Do not start on FastAPI-MCP#
No release in thirteen months, no repository push in nine, in a category whose protocol revised twice in that window. The adapter shape — derive MCP tools from existing FastAPI routes — is attractive if you already have the app, and it is not currently being maintained into a moving protocol.
Write it by hand only to learn what the libraries do#
MCP is JSON-RPC 2.0 with a small method set and a defined handshake. What a library actually saves you is schema generation from type hints, the SSE plumbing, and the capability negotiation. Doing it once by hand makes the rest of this survey legible. Shipping it that way means owning the next protocol revision yourself.
What decides it, in order#
- Do you need a client too? → FastMCP.
- Is conformance-by-definition worth more than surface? → official SDK.
- Which deployment shape — stdio or HTTP? It changes the surrounding questions more
than it changes the library choice. See
architecture.md. - Everything else — API taste — is the smallest input and the one most discussion is about.
Two things S2 did not do#
No benchmark. Both leaders are thin over the same ASGI substrate and an MCP server’s time
goes to the tool call it dispatches, so a framework comparison would measure Starlette and
mislead. measurement-plan.md records what would be worth measuring — stdio cold start —
and why no persona here needed it.
No verdict on MCP itself. Whether to speak this protocol at all is 2.074’s question.
S3: Need-Driven
S3 Approach — who is building an MCP server, and why#
Five positions. The question that sorts them is what the server is for, because that decides the deployment shape, and the deployment shape decides more than the library choice does.
Two things recur across all five:
stdio and HTTP are different products. A server the client spawns per session has cold start and no auth story of its own. A long-lived HTTP server has every question a web service has. Advice that reads as contradictory usually is not — it is answering the other shape.
The library choice is rarely the hard part. Deciding what to expose, and what happens when a model calls it wrong, is the work. The personas say so where it is true.
S3 Recommendation — by position#
| If you are… | Use | The deciding reason |
|---|---|---|
| Building a personal tool | Official SDK, stdio | No port, no auth, no deployment — and cold start is the only real cost |
| Exposing an existing service | Official SDK, HTTP over your service layer | A good REST endpoint is rarely a good tool; do not auto-derive |
| Shipping MCP as a product | Official SDK, HTTP + your own auth | The protocol carries a session, not an identity |
| Building server and client | FastMCP | It ships both halves and more auth scaffolding |
| Migrating from 2025 | Check your imports first | FastMCP now names two different codebases |
| Not sure it is needed | A CLI first | Same discipline, no protocol; MCP layers over it later |
The two questions that sort this category#
What is the server for? It decides the deployment shape — stdio or HTTP — and that decides auth, isolation, cold start and concurrency. It changes more than the library choice does, and most published guidance assumes a shape without saying which.
One implementation of the protocol, or two? fastmcp no longer depends on mcp
(verified 2026-08-29). Choosing it is choosing a second team’s reading of the specification,
which is a defensible decision and a different one from preferring an API.
What every persona agrees on#
- The tool descriptions are the interface. They are all the model has. Time spent there beats time spent choosing between decorators.
- Fewer tools, better named. Six beats forty derived ones, every time.
- Do not start on FastAPI-MCP — thirteen months without a release in a category that revised its protocol twice.
- Pin the library and test the handshake against a current client. A version number is not conformance.
- Ask what happens when the model calls it wrong, before choosing anything.
Persona: should we build an MCP server at all?#
Who: someone asked to “add MCP support”, who has not yet been asked what for.
What they need: the question before the library question.
What MCP is for, and what it is not#
MCP gives a model a list of tools it can call, described in natural language, and a protocol for calling them. That is the whole proposition. It is worth building when a model needs to act on something you own, and when which action to take is a judgment the model should make.
It is not an API gateway. If a caller knows which endpoint it wants, it should call the endpoint. MCP’s cost is the model deciding, and that cost only buys something when the deciding is the point.
It is not a retrieval system. If the need is “the model should know about our documents”, that is retrieval, and a resource list is a worse version of it.
It is not free. A tool list is an interface described in prose to a caller that improvises, and the descriptions are the interface. That is real design work, and it is the work — not the library — that decides whether the result is any good.
The honest test#
Ask what happens when the model calls the wrong tool, or the right tool with wrong arguments. If the answer is “it gets an error and tries again”, you are ready. If the answer is “something irreversible”, the tool needs a confirmation step, a dry-run mode, or narrower scope — and that is a design decision no library makes for you.
If the answer is yes#
Read use-case-local-tooling.md for a personal tool, use-case-expose-existing-api.md if you
already have a service, use-case-multi-tenant-service.md if other people’s agents will
connect. The library choice is in ../S2-comprehensive/recommendation.md and it is the
smallest decision of the set.
If the answer is not yet#
A CLI is often the better first step, and not as a consolation. An agent can run a command and read its output; that requires no protocol, no server and no deployment, and it forces the same discipline MCP rewards — clear verbs, legible errors, narrow scope. If the CLI is good, the MCP server is a thin layer over it later. If the CLI is bad, MCP will not rescue it.
1.244 Python CLI Frameworks is where that path goes.
Persona: we have an API and want an agent to use it#
Who: a team with a working HTTP service — REST, probably FastAPI or Flask — and a request to “make it available to Claude / an agent”.
What they need: the shortest correct path from an existing surface to an MCP one.
The tempting answer is the unmaintained one#
FastAPI-MCP derives MCP tools from routes you already have, which is exactly this persona’s shape, and the reason it has 11,991 stars.
It has not released since 2025-07-28 and its repository has not been pushed since 2025-11-24 — thirteen and nine months, in a category whose protocol shipped two major revisions in that window. Do not start here. If you already depend on it, the question is whether your MCP version still negotiates with current clients, and that is testable today.
What to do instead#
Write the MCP server as a separate surface over the same service layer, using the official SDK. Not over the HTTP API — over the functions the HTTP API calls.
That sounds like more work and is usually less, because a good REST endpoint is rarely a
good tool. REST resources are nouns shaped for CRUD; tools are verbs shaped for a caller
that cannot see your documentation. GET /orders?status=x&page=n is three tools or one, and
which is a design decision no adapter can make for you.
The hard part is not the library. It is deciding what to expose. A model given forty auto-derived tools uses them badly; the same model given six well-named ones does not. An adapter that mechanically converts every route optimizes for the wrong thing, and that is the deeper reason to skip it even when maintained.
The deployment follows from what you have#
You already run a web service, so HTTP + SSE, alongside the existing app or as a sibling. 1.242 covers the server that runs it. Authentication is the question the adapter would not have answered either: an agent calling your tools is a client with credentials, and MCP does not decide that for you.
What not to assume#
Do not assume tool descriptions are documentation. They are the entire interface the model has. Time spent on descriptions beats time spent choosing between two libraries whose decorators differ cosmetically.
Persona: a personal tool for my own editor#
Who: a developer who wants an agent to reach something local — a database, a scratch directory, an internal script, a note store.
What they need: the least ceremony that works.
Recommendation: the official SDK, stdio, one file#
stdio is the right transport and it removes most of the questions. The client spawns your server as a subprocess; there is no port, no TLS, no auth layer, because the process runs as you and inherits what you already had. A tool exposed this way is a decorated function and little else.
The official SDK is the default here for the reason it is the default everywhere: it is the reference reading of a protocol that has revised twice in a year. Nothing about this persona argues for a second implementation.
What actually goes wrong#
Cold start, which is the one cost this shape has and the one nobody measures. Your server is spawned and killed constantly, so import time is user-visible latency in a way it never is for a long-lived service. Heavy imports at module scope are the usual cause; move them inside the tool function.
This survey did not measure it (measurement-plan.md explains why not, and records it as the
thing that would be worth measuring). For a personal tool, the fix is cheaper than the
measurement.
Tools that fail confusingly. A model handed an exception traceback will try again wrongly. Return an error string that says what to do differently.
Too many tools. The temptation is to expose everything reachable. Six good ones beat forty derived ones, and this is where a personal server is won or lost.
When to reach for FastMCP instead#
If you find yourself also writing the client — driving your own server from a script or a test — FastMCP ships both halves and that is a real saving. For a server alone, it is an independent implementation of the protocol taken on for an API preference, which is a poor trade at this size.
Persona: we built this a year ago and it needs to move#
Who: anyone with a working MCP server written against the pre-2026 landscape — the official SDK 1.x, or FastMCP 2.x.
What they need: to know what actually broke, and whether the name in their imports still means what it did.
Check what you are importing before anything else#
Since 2026-07-28 the official SDK renamed its high-level class FastMCP → MCPServer,
and the standalone project kept FastMCP. So these two lines were once nearly the same
thing and now are not:
from mcp.server.fastmcp import FastMCP # official SDK, pre-2.0 spelling
from fastmcp import FastMCP # the standalone project, unrelated codebaseThis is the migration’s real hazard, because the failure is silent. Both import, both
run, and a reader following a tutorial written before that date cannot tell which library
they are on. naming-the-two-fastmcps.md exists for this and should be read first.
The two migrations are different in kind#
Official SDK 1.x → 2.x is a version upgrade of the same library: a class rename, protocol
types split into mcp-types, and the protocol revision underneath. 1.x is on security fixes
only.
FastMCP 2.x → 3.x is a version upgrade of a project that, in the same period, stopped
depending on the official SDK. Verified 2026-08-29: fastmcp 3.4.7 has no mcp dependency.
So a codebase that thought it was using a convenience wrapper over the reference
implementation is now on an independent one. Nothing broke; what changed is what you are
relying on, and that is worth a deliberate decision rather than a version bump.
What to do#
Pin both the library and the protocol version you negotiate, and test the handshake against a current client rather than trusting the library’s version number.
Re-read your tool descriptions. They were written for a model a year older. This is the part of the migration nobody schedules and the part that changes behavior most.
Decide which library you are on, once, on purpose. The SDK’s 2.x rename is a good moment for it, because the diff touches those imports anyway.
Persona: an MCP server other people’s agents connect to#
Who: a team shipping MCP as a product surface — customers point their agents at it.
What they need: everything a public web service needs, plus a protocol that assumes less about auth than they would like.
The library is the smallest decision here#
Official SDK, HTTP + SSE. Then spend the effort on the parts MCP does not settle.
Authentication is yours. The protocol carries a session; it does not tell you who the caller is or what they may reach. In practice that means bearer tokens or OAuth at the transport, and a per-caller authorisation check inside every tool — because a tool list is a capability list, and the model will call anything it can see.
Multi-tenancy is yours. stdio gets isolation free by being one process per session. HTTP does not: one process serves everyone, and tenant identity has to be threaded from the request through to the tool. That is the single largest difference between the two shapes and it is invisible in every getting-started example.
Rate limiting is yours, and it matters more than usual: an agent that misreads a tool description will retry in a loop faster than a human ever would.
Why not FastMCP, given it has more auth scaffolding#
It is the strongest case for it in this survey, and the trade is still close. Its auth helpers are real work you would otherwise do. Against that: it is an independent implementation of a protocol you are exposing publicly, so conformance questions from a customer’s client have two possible answers rather than one, and you are the one who has to resolve them.
Weigh the scaffolding against owning that. Neither answer is wrong; the framing that is wrong is treating it as an API-style preference.
What this persona should read next#
1.242 for the ASGI server, 1.241 for the surrounding app, 2.074 for what the protocol does and does not promise. And a security review that treats the tool list as the attack surface — because it is, and it is a surface described in natural language to a caller that improvises.
S4: Strategic
S4 Approach — what survives the next protocol revision#
The usual S4 question is whether a library will still be maintained. Here that question is secondary, because the protocol moves faster than the libraries do — two major revisions in twelve months — and a library that is maintained but a revision behind is not usable.
So the questions are:
- Who ships the protocol revision first, and who follows? That is a structural fact about where a library sits, not a prediction about a team.
- What does adopting an independent implementation commit you to?
- What happens to a server written today when the spec moves again?
- Where is the category going — does the client-side consolidate, does the protocol stabilise, does the adapter shape come back?
Company health stays in its lane: it answers will this survive, never is this good. Figures are from S2, read 2026-08-29.
S4 Recommendation — long-term view#
Safe to depend on#
The official SDK. Not because it is the most popular, though it is — 53.3M weekly — but because it is the reference reading of a specification that revised twice in twelve months. When the protocol moves, this is the library that moves with it by definition. In a category where a version behind is not stale but wrong, that property outranks API taste.
Its risk is a small surface, not survival. That is the gap FastMCP fills.
Adopt with your eyes open, not by default#
FastMCP. Active, well-starred — more stars than the official SDK — Apache-2.0, and
shipping. What you are taking on is an independent implementation: verified 2026-08-29,
it no longer depends on mcp, so every protocol revision is work its maintainers redo rather
than inherit.
It has shipped through one revision as an independent implementation, which is evidence the obligation is sustainable. It is not proof. Take it for the surface — a client as well as a server, more auth scaffolding — and take it knowingly.
Exit cost if it stops: a rewrite of the server’s wiring, not a config change. The tools survive; both use decorators over typed functions.
Do not start#
FastAPI-MCP. Thirteen months without a release, nine without a push, two protocol revisions missed, tracker not being emptied. The adapter shape is attractive and this is not a maintained home for it.
Raw JSON-RPC, except to learn. Timeless in the sense that nothing can deprecate it, and it makes you the implementation that must track the specification — the same obligation FastMCP carries, with a team of one.
The forecast, in one line each#
- The wrapper era is over and the documentation has not caught up. Two independent implementations now share one class name. This resolves through naming, not through one project winning, and the lag is measured in the half-life of blog posts.
- The adapter shape returns with a narrower claim than “convert every route”, because mechanical conversion optimizes for the wrong thing.
- If the protocol stabilises, this becomes an ordinary choice. Two majors in a year is a young standard; when revisions slow, “who ships first” stops deciding and API taste starts to. Most people already believe that is where we are.
- The real difficulty is not selection. Every persona landed on the tool list — fewer, better named, better described — which is interface design and no library helps.
Risk assessment#
1. Following guidance written before 2026-07-28 — near certain#
from mcp.server.fastmcp import FastMCP and from fastmcp import FastMCP were nearly the
same thing and are now two different codebases. Every tutorial, forum answer and model
completion written earlier is ambiguous, and the failure is silent — the code imports and
runs.
Mitigation: read naming-the-two-fastmcps.md, and check which package your import
resolves to before debugging anything else.
2. Assuming FastMCP wraps the official SDK — common, and it changes the decision#
It did once. fastmcp 3.4.7 has no mcp dependency (verified 2026-08-29). A team that
believes it is on a convenience layer over the reference implementation is actually on a
second implementation of the specification.
Mitigation: pip show fastmcp and read Requires:. Then choose on purpose — it is a
reasonable choice, badly made by accident.
3. Starting on FastAPI-MCP because the shape fits — common#
The adapter shape matches the most common persona in this survey, which is why it has 11,991 stars. It has not released in thirteen months or been pushed in nine, across two protocol revisions.
Mitigation: if you already depend on it, test the handshake against a current client today. If you are choosing, do not.
4. Auto-deriving every endpoint into a tool — common, and the damage is invisible#
A model given forty mechanically-converted tools uses them badly; the same model given six well-named ones does not. Nothing errors, so nobody investigates — the server just performs poorly and the library gets blamed.
Mitigation: choose the tools by hand. This is the highest-leverage work in the category and no library does it for you.
5. Treating the tool list as documentation rather than as an attack surface — rare, serious#
A tool list is a capability list described in prose to a caller that improvises. On a multi-tenant server, authorisation must be checked inside each tool: the model will call anything it can see, and the protocol carries a session rather than an identity.
Mitigation: per-caller checks in the tool body, not only at the transport. Assume a caller that tries everything, because it will.
6. stdio cold start becoming user-visible latency — rare, easy to miss#
Under stdio the client spawns your server per session, so import time is felt on every invocation in a way it never is for a long-lived service. Heavy module-scope imports are the usual cause.
Mitigation: move expensive imports inside the tool function. Unmeasured here — see
../S2-comprehensive/measurement-plan.md, which records it as the thing worth measuring if
anyone needs the number.
7. Pinning the library but not the protocol version — rare, and it surfaces late#
A library version is not a protocol version. Two servers on the same library release can negotiate differently depending on configuration and client.
Mitigation: test the handshake in CI against a current client. It is the only check that answers the question a pinned version appears to answer and does not.
Where this category is going#
The protocol is the moving part, and that is unusual#
In most surveys the libraries move and the problem stands still. Here the specification shipped two major revisions in twelve months and the libraries followed. Any strategic read has to start there.
The consequence for a reader: a library’s version number is not conformance. The question “does this server still negotiate with current clients” is answered by testing a handshake, not by reading a changelog — and it is the question this category makes people ask that others do not.
The wrapper era ended and most guidance has not noticed#
Through 2025 the shape was clear: one official SDK, and FastMCP as the nicer way to use it. That is how nearly every tutorial still reads.
It is no longer true. fastmcp 3.4.7 does not depend on mcp (verified 2026-08-29), and
the official SDK renamed its own high-level class out from under the shared name on
2026-07-28. The category now has two independent implementations with one class name
between them, and the ambiguity is invisible at the import line.
Forecast: this resolves through naming rather than through one project winning. The rename has happened; what has not happened is the ecosystem’s documentation catching up, and that lag is measured in the half-life of blog posts and model training data rather than in releases.
The adapter shape will come back, and probably not as FastAPI-MCP#
“Derive MCP tools from the API you already have” is too attractive to stay dormant — 11,991 stars on a project that has not shipped in thirteen months is demand looking for a maintained home.
It returns with a narrower claim. The lesson of the dormant one is not that adapters are wrong; it is that mechanical conversion optimizes for the wrong thing, because a good REST endpoint is rarely a good tool. The version that lasts will select and rename rather than convert everything.
What would falsify this reading#
- The official SDK growing FastMCP’s surface — a client, real auth scaffolding — which would remove the main reason to take an independent implementation.
- FastMCP missing a protocol revision. It has shipped through one as an independent implementation; a second would make the obligation look sustainable, a miss would make it the central risk.
- The protocol stabilising. Two majors in a year is a young standard. If revisions slow, “who ships first” stops being decisive and this category becomes an ordinary API-taste choice — which is what most people already believe it is.
The boundary worth watching#
Not a library at all: what a tool list does to a model’s behavior. Every persona in S3 arrived at the same place — fewer tools, better descriptions — and no library helps with it. That is where this category’s difficulty sits, and it is closer to interface design than to dependency selection.
Viability, per option#
The official SDK (mcp)#
Governance: the Model Context Protocol organization — the specification’s own authors. MIT.
Signals: 2.1.1 released 2026-08-25, pushed 2026-08-28, 24,155 stars, 391 open issues, 53.3M weekly downloads.
The safest dependency here, and for a structural reason rather than a popularity one. It does not follow the specification; it is the reference reading of it. When the protocol revises, this is the library that revises with it, by definition. In a category that has shipped two majors in a year, that is worth more than any API preference.
Its risk is not abandonment. It is that a reference implementation optimizes for correctness and coverage rather than ergonomics, so the surface stays smaller than what some teams want — which is precisely the gap FastMCP fills.
FastMCP#
Governance: a single-maintainer-led open-source project (jlowin/fastmcp), Apache-2.0.
Signals: 3.4.7 released 2026-08-10, pushed 2026-08-29, 27,435 stars — more than the official SDK — 293 open issues, 11.4M weekly downloads.
Healthy, and carrying a heavier obligation than its users may realise. Verified
2026-08-29: it no longer depends on mcp. It is an independent implementation, so every
protocol revision is work its maintainers must do again rather than inherit.
That is the viability question in this category, and it is not answered by activity alone. The project is shipping now and shipped through the 2.x → 3.x revision, which is evidence it can. What a reader is betting on is that it keeps doing so, indefinitely, against a specification it does not control.
Star count over the official SDK is a real signal — people chose this on purpose, not incidental installs. It is also the number most inflated by a project having a nicer front page, so weigh it alongside the 5:1 download gap rather than instead of it.
If it stopped: migration to the official SDK is a rewrite of the server’s surface, not a config change. Both use decorators over typed functions, so the tools survive; the wiring does not.
FastAPI-MCP#
Signals: 0.4.0 released 2025-07-28, pushed 2025-11-24, 11,991 stars, 178 open issues, 416K weekly.
Do not start here, and the reason is sharper than staleness. 1.250 established that a quiet project is not necessarily a dying one — Flake8 had not released in fourteen months and its 23 open issues proved someone was answering. This is the other case: nothing shipped, nothing pushed, and the tracker is not being emptied, in a category whose protocol revised twice in the gap.
A protocol library that misses a protocol revision is not stale, it is wrong. The question for an existing user is not “should we migrate eventually” but “does our handshake still negotiate with current clients”, and that is testable today.
Raw JSON-RPC#
Timeless, and that is the whole argument. JSON-RPC 2.0 is not going to be deprecated, and a hand-rolled server has no dependency to go quiet.
What it has instead is you as the implementation that must track the specification. That is the same obligation FastMCP took on, with a team of one and no users to report bugs. Correct for learning; expensive as a strategy.