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.

LibraryRecommendationLicense
FastMCPprimaryApache-2.0
MCP Python SDKalternativeMIT
FastAPI-MCPbrownfield-onlyMIT
Raw JSON-RPCeducationalN/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 response

Transport Options#

TransportWhen to Use
stdioLocal development, CLI tools. Server runs as subprocess.
Streamable HTTPProduction, remote servers. Standard HTTP with optional streaming.
SSELegacy. Deprecated since late 2024.

Default: stdio for local, Streamable HTTP for deployed.


Python Library Options#

For Most Projects: FastMCP#

pip install fastmcp
from 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-mcp

Automatically 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.py

Connect 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#

  1. Input validation: Validate all tool parameters
  2. Authentication: Use FastMCP’s built-in auth or implement your own
  3. Least privilege: Only expose what’s necessary
  4. Rate limiting: Protect against abuse

Next Steps#

  1. Start simple: Build a server with 2-3 tools
  2. Test locally: Connect to Claude Desktop
  3. Add complexity: Resources, prompts, auth
  4. Deploy: Streamable HTTP for production

Resources#


Glossary#

TermDefinition
MCPModel Context Protocol - the standard
ToolFunction an LLM can invoke
ResourceRead-only data an LLM can access
PromptReusable message template
TransportCommunication channel (stdio, HTTP)
FastMCPHigh-level Python framework
JSON-RPC 2.0Underlying 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-mcp 0.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 a fastmcp or mcp server 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-mcp

Requirements: 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 /mcp

Schema 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 auth

ASGI 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 app

Separate Application#

app = FastAPI()
mcp_app = FastApiMCP(app).create_app()
# Deploy mcp_app separately

Comparison to FastMCP#

AspectFastAPI-MCPFastMCP 2.0
Use CaseBrownfield (existing API)Greenfield (new project)
ConfigurationZeroMinimal
AuthReuses FastAPI DependsBuilt-in OAuth providers
Learning CurveVery low (if you know FastAPI)Low
FeaturesEndpoint exposure onlyFull MCP primitives

Strengths#

  1. Zero configuration: Automatic endpoint introspection
  2. Preserves existing code: No rewrite needed
  3. Auth reuse: Same auth as REST endpoints
  4. ASGI native: Efficient, no HTTP hop
  5. Dual interface: Maintain REST + MCP simultaneously

Limitations#

  1. FastAPI-only: Requires FastAPI application
  2. Endpoint-focused: Doesn’t support MCP resources/prompts natively
  3. Brownfield-only: Not suitable for MCP-first projects
  4. 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

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/tasks

Sources#


FastMCP - Library Profile#

🔄 REFRESH NOTE — 2026-08-17 >#

Now fastmcp 3.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 still from fastmcp import FastMCP. What changed is that the official SDK renamed its copy of FastMCP to MCPServer in mcp 2.0.0 — a different package. This is the single most confusable fact in the category right now; see naming-the-two-fastmcps.md. > Pin the major. This project has moved a major roughly annually (2.x → 3.x), so fastmcp>=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 fastmcp

Requirements: Python >= 3.10


Key Statistics#

MetricValue
GitHub Stars21.2k
GitHub Forks1.6k
Open Issues201
PyPI DownloadsHigh (exact count varies)
Last ReleaseDecember 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:

  • Google
  • 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#

TransportSupportNotes
stdioFullDefault for local
Streamable HTTPFullProduction recommended
SSEFullLegacy support
WebSocketVia extensionmcp[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#

Aspectmcp.server.fastmcpfastmcp (standalone)
Version1.x (in SDK)2.x
FeaturesBasicFull enterprise
AuthLimitedComprehensive
MaintainedBy AnthropicBy Prefect
Importfrom mcp.server.fastmcpfrom fastmcp

Recommendation: Use standalone fastmcp for new projects (more features, actively developed).


Strengths#

  1. Simplicity: Minimal boilerplate, decorator-based
  2. Production-ready: Enterprise auth, deployment tools
  3. Well-documented: Comprehensive docs at gofastmcp.com
  4. Active community: 21.2k stars, responsive maintainers
  5. Proven: Validated in spawn-experiments 1.618

Limitations#

  1. Apache-2.0 license: May require attribution (not MIT like SDK)
  2. Cyclopts dependency: Licensing complexity for some orgs
  3. Abstraction layer: Less control than raw SDK
  4. 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#


MCP Python SDK - Library Profile#

🔄 REFRESH NOTE — 2026-08-17 >#

Now mcp 2.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: >

  • FastMCP is now MCPServer, and there is a first-class Client. The import moves from mcp.server.fastmcp to mcp.server.mcpserver.
  • Protocol types split into their own packagemcp-types (imported as mcp_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-28 revision and still serves 2025-era clients.
  • v1.x is maintenance mode — security fixes only. Pin mcp>=1.28,<2 to 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 to mcp-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#

ExtraPurpose
cliCommand-line interface tools
richEnhanced terminal output
wsWebSocket transport support

Key Statistics#

MetricValue
GitHub Stars20.6k
GitHub Forks2.9k
Open Issues241
LicenseMIT
Last ReleaseDecember 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#

TransportClassUse Case
stdiostdio_server()Local subprocess
Streamable HTTPstreamable_http_server()Remote/networked
SSEsse_server()Legacy (deprecated)
WebSocketwebsocket_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#

ComponentSourceNotes
mcp.server.fastmcpFastMCP 1.xIncorporated into SDK
fastmcp (pip)FastMCP 2.xStandalone, 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#

  1. Official: Reference implementation by Anthropic
  2. MIT License: Maximum permissiveness
  3. Complete: Full protocol implementation
  4. Low-level control: Direct access to all primitives
  5. Minimal: Fewer abstractions, smaller footprint

Limitations#

  1. More verbose: Requires more boilerplate than FastMCP 2.0
  2. Basic auth: OAuth support less comprehensive than FastMCP 2.0
  3. Less documentation: Compared to FastMCP’s gofastmcp.com
  4. 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#


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#

PackageClassCurrentProvenance
The standalone projectfastmcpFastMCP3.4.7 (2026-08-10)jlowin / PrefectHQ; the original
The official SDK’s copymcpFastMCPMCPServer2.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:

"FastMCP is now MCPServer, and there is a first-class Client"

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 FastMCP

What 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.

  • FastMCPMCPServer, plus a first-class Client
  • Protocol types split out into their own package: mcp-types (imported as mcp_types), also 2.0.0 on 2026-07-28
  • Backward compatibility retained: v2 supports the 2026-07-28 revision while still serving earlier 2025-era clients from the same server
  • v1.x is maintenance mode — security fixes only. Pin mcp>=1.28,<2 to 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:

ChooseWhen
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}\n

Message Types#

  1. Request: Has method, params, id
  2. Response: Has result or error, id
  3. Notification: Has method, params, no id

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#

  1. Initialize: Client sends initialize, server responds with capabilities
  2. Initialized: Client sends notifications/initialized (no response)
  3. Operation: Client calls tools/list, tools/call, etc.
  4. Shutdown: Client closes connection

Capabilities Declaration#

Server declares what it supports:

{
  "capabilities": {
    "tools": {},
    "resources": {},
    "prompts": {},
    "logging": {}
  }
}

Only declare capabilities you implement.


Error Codes#

CodeMeaning
-32700Parse error
-32600Invalid request
-32601Method not found
-32602Invalid params
-32603Internal error

Strengths#

  1. Zero dependencies: Pure Python stdlib
  2. Full understanding: Learn protocol internals
  3. Minimal footprint: ~50 lines for basic server
  4. Debuggable: Easy to log and inspect messages
  5. Portable: Works anywhere Python runs

Limitations#

  1. No schema validation: Must implement manually
  2. No transport abstraction: Locked to one transport
  3. No error handling: Must build robust error handling
  4. No testing utilities: No mock clients
  5. 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#


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:

  1. Educational purposes
  2. Understanding protocol before using SDK
  3. 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.md first. 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#

LibraryPackageVersionReleasedStatus
Standalone FastMCPfastmcp3.4.72026-08-10Active; class still FastMCP
Official MCP SDKmcp2.0.02026-07-28Active; FastMCPMCPServer
— protocol typesmcp-types2.0.02026-07-28New — split out of the SDK
Official SDK, previousmcp 1.x1.28+Maintenance: security fixes only
FastAPI-MCPfastapi-mcp0.4.02025-07-28⚠️ No release in 13 months
Raw JSON-RPCn/an/aTimeless; 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#

  1. “Use FastMCP 2.0” is retired. The number now points at a superseded line.
  2. The first question is which FastMCP, not which library. See naming-the-two-fastmcps.md.
  3. 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).
  4. FastAPI-MCP’s caveat is now about maintenance, not just fit.
  • 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 FastMCPMCPServer, 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#

PackageVersion (date)WeeklyStarsOpen issuesLast pushLicense
Official SDKmcp2.1.1 (2026-08-25)53,319,10224,1553912026-08-28MIT
FastMCPfastmcp3.4.7 (2026-08-10)11,403,33727,4352932026-08-29Apache-2.0
FastAPI-MCPfastapi-mcp0.4.0 (2025-07-28)415,88111,9911782025-11-24MIT
Raw JSON-RPCn/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 SDKit is mcpyou get the reference reading by definition
FastMCPno — independent implementationa second team must read the spec and ship
FastAPI-MCPyesinherits, and has not shipped in 13 months
Raw JSON-RPCnoyou 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 SDKFastMCPFastAPI-MCP
Tool from a typed functiondecoratordecoratorderived from existing routes
Schema from type hintsyes (Pydantic)yes (Pydantic)yes (FastAPI’s)
stdio transportyesyesvia the app
HTTP + SSEyesyesvia the app
Client library tooyesyesno
Auth helpersminimalmoreinherits FastAPI’s
High-level classMCPServer (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#

  1. Do you need a client too? → FastMCP.
  2. Is conformance-by-definition worth more than surface? → official SDK.
  3. Which deployment shape — stdio or HTTP? It changes the surrounding questions more than it changes the library choice. See architecture.md.
  4. 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…UseThe deciding reason
Building a personal toolOfficial SDK, stdioNo port, no auth, no deployment — and cold start is the only real cost
Exposing an existing serviceOfficial SDK, HTTP over your service layerA good REST endpoint is rarely a good tool; do not auto-derive
Shipping MCP as a productOfficial SDK, HTTP + your own authThe protocol carries a session, not an identity
Building server and clientFastMCPIt ships both halves and more auth scaffolding
Migrating from 2025Check your imports firstFastMCP now names two different codebases
Not sure it is neededA CLI firstSame 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 FastMCPMCPServer, 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 codebase

This 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.

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:

  1. 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.
  2. What does adopting an independent implementation commit you to?
  3. What happens to a server written today when the spec moves again?
  4. 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.

Published: 2025-12-14 Updated: 2026-08-29