PromptisePromptise
Docs
GitHub
Promptise - AI Framework LogoPromptise

The foundation layer for agentic intelligence. Build, secure and operate autonomous AI systems with Promptise Foundry.

pip install promptise

[01] Foundry

  • MCPcast
  • The Promptise Agent
  • Reasoning Engine
  • MCP
  • Agent Runtime
  • Prompt Engineering
  • Execution Engine
  • Agent Identity

[02] Resources

  • Documentation
  • GitHub
  • Guides
  • Learning Paths
  • Questions

[03] Company

  • About
  • Terms of Service
  • Privacy Policy
  • Cookie Policy
  • Subprocessors

© 2026 Promptise by Manser Ventures. All rights reserved.

Open source · Python

← Guides> Tools and the Model Context Protocol (MCP)

FastMCP Alternatives for Production MCP Servers (2026)

FastMCP alternatives compared: the official MCP Python and TypeScript SDKs, Promptise Foundry and more, for auth, multi-tenancy, observability and deploys.

Level
Intermediate
Reading time
16 min
Published
Oct 20, 2026
Last updated
Oct 20, 2026
Tested with
Promptise 1.4.3
By
Promptise Team
  • Model Context Protocol (MCP)
  • AI Agent Frameworks
  • Authentication and Access Control
  • Multi-Tenancy
  • Observability
  • Deployment
Contents · 17 sections↓
  1. 01What is the best FastMCP alternative?
  2. 02What is FastMCP, and which one are you using?
  3. 03How do production MCP server frameworks compare?
  4. 04When to stay with FastMCP (and when to switch)
  5. 05What you need
  6. 06Step 1: Write the server in FastMCP and the official SDK
  7. 07Step 2: Write it with Promptise Foundry
  8. 08Step 3: Write it in TypeScript with the official SDK
  9. 09Step 4: Expose an existing FastAPI app with FastAPI-MCP
  10. 10Step 5: Call every server with one client
  11. 11Step 6: Add auth, tenants and rate limits
  12. 12FastMCP vs Promptise: which is better for production?
  13. 13Running it in production
  14. 14Common problems when switching
  15. 15Limitations
  16. 16Frequently asked questions
  17. 17Next steps

A FastMCP alternative is worth a look when your MCP server grows past a handful of tools and starts to need auth, tenant isolation, metrics and a deployment story. This guide is for developers who build MCP servers with FastMCP and want to know what else is there in 2026: the official MCP SDKs, Promptise Foundry, FastAPI-MCP and a few TypeScript frameworks. You get a comparison table, the same server written in five of them, and a clear answer on when to stay with FastMCP.

[01]

What is the best FastMCP alternative?

There is no single best one. It depends on what you need that FastMCP doesn't give you, or gives you in a way you don't like:

If you need

Use

Why

The protocol reference with few dependencies

Official MCP Python SDK 2 (MCPServer)

FastMCP's original API, maintained by the MCP project

Auth, tenants, rate limits, audit logs and metrics in one package

Promptise Foundry

Production middleware is built in and tenant-aware

To expose an existing FastAPI app

FastAPI-MCP, or FastMCP's own from_fastapi

Turns routes into tools

TypeScript, Node or edge runtimes

Official TypeScript SDK 2, xmcp, mcp-handler

Same protocol, JavaScript ecosystem

Go, Java, C# or Rust

The official SDK for that language

First-party and spec-aligned

OAuth providers, proxies for remote servers, the largest community

Stay with FastMCP

Nothing else in Python has as much of this

  • "FastMCP" means two things. The class in the official SDK, renamed MCPServer in mcp 2.0 (July 2026), and Prefect's standalone fastmcp package, at 4.1 in October 2026.

  • All of them speak the same protocol. One client called all five servers in this guide. The differences are the protocol revision, auth and operations features.

  • FastMCP 4 is not missing much. OAuth providers, authorization, middleware, OpenTelemetry and background tasks are built in. It has no tenant concept, circuit breaker, signed audit log or Prometheus metrics.

  • Switching costs sit in auth and middleware, not in tools. A tool function moves with a decorator change.

  • Watch the `mcp` pin. FastMCP 4 needs mcp 2.x. Promptise 1.4 and FastAPI-MCP need mcp 1.x, so neither can share an environment with FastMCP 4.


[02]

What is FastMCP, and which one are you using?

Jeremiah Lowin started FastMCP in 2024 as a high-level Python API for MCP servers. The official MCP Python SDK adopted it, and the project continued separately under Prefect. That history explains most of the confusion in search results:

Text
1
2
3
4
5
6
7
8
2024      FastMCP 1.0 ──▶ adopted by the official MCP Python SDK
                          from mcp.server.fastmcp import FastMCP
2025      standalone fastmcp 2.x (Prefect): auth, composition, proxying, OpenAPI, middleware
2026-02   fastmcp 3.0: components, providers, transforms, authorization
2026-07   mcp 2.0: the SDK's FastMCP class becomes MCPServer, protocol 2026-07-28
          from mcp.server.mcpserver import MCPServer
2026-08   fastmcp 4.0: built on mcp 2.x, background tasks, the 2026-07-28 revision
2026-10   fastmcp 4.1.2 and 3.4.10, mcp 2.3.0 and 1.30.0 (both lines maintained)

So check your import:

  • from fastmcp import FastMCP is the standalone framework (Apache-2.0, maintained by Prefect). It is the one the FastMCP docs at gofastmcp.com describe, and it is a full framework: servers, clients, auth, composition, proxies, OpenAPI generation, and apps.

  • from mcp.server.fastmcp import FastMCP is the SDK's class in mcp 1.x. In mcp 2.x the import fails with ModuleNotFoundError and points you to MCPServer.

  • The npm package fastmcp is a different project: a TypeScript framework by another author, unrelated to Prefect's.

MCP itself is the protocol between an AI app (the client) and a server that exposes tools, resources and prompts. Every framework here produces a server that any MCP client can use. If MCP is new to you, start with what is the Model Context Protocol.


[03]

How do production MCP server frameworks compare?

Versions as of 2026-10-20. Claims about other projects come from their docs and package source. "Your code" means the framework gives you the token claims and you build the rest.

FastMCP 4.1

MCP Python SDK 2.3

Promptise Foundry 1.4.3

TypeScript SDK 2.3

FastAPI-MCP 0.4

Language

Python

Python

Python

TypeScript

Python

Maintainer

Prefect

MCP project

Promptise

MCP project

Tadata

License

Apache-2.0

MIT

Apache-2.0

MIT, moving to Apache-2.0

MIT

Newest protocol revision

2026-07-28

2026-07-28

2025-11-25 (mcp 1.x)

2026-07-28

2025-11-25 (mcp 1.x)

Tool API

@mcp.tool

@mcp.tool()

@server.tool()

registerTool with Zod

FastAPI routes

Authentication

JWT and JWKS, OAuth proxy, full OAuth server, about 15 provider classes

Token verifier, OAuth server provider

JWT (HS256, asymmetric, JWKS), API keys, dev token endpoint

requireBearerAuth, resource metadata

FastAPI dependencies

Authorization

Scopes, roles, tags

Required scopes

Roles, scopes, guards

Scopes per tool

FastAPI dependencies

Multi-tenancy

Your code

Your code

Tenant from a token claim; rate limits, cache and audit are tenant-scoped

Your code

Your code

Rate limiting

Token bucket or sliding window, per client with get_client_id

Not built in

Per client and tenant, optionally per tool

Not built in

Not built in

Resilience

Retry, error handling, response limits

Not built in

Circuit breaker, timeouts, concurrency limits

Not built in

Not built in

Observability

OpenTelemetry on by default, logging, timing

OpenTelemetry on by default

OpenTelemetry, Prometheus, structured logs, HMAC-chained audit log, dashboard

Not built in

Through FastAPI

Deployment

run(), ASGI app, fastmcp CLI, Prefect Horizon

run(), ASGI apps

run() with health probes, ASGI app

Node, Express, Hono, Fastify, Workers

Inside your FastAPI app

GitHub stars

about 28,000

about 25,000

under 1,000

about 14,000

about 12,000

FastMCP leads on breadth: OAuth providers, proxies for remote servers, providers and transforms, and the biggest community by far. The official SDKs lead on protocol fidelity and have the fewest dependencies. Promptise Foundry leads on operations features that FastMCP leaves to you, such as tenant isolation, circuit breakers and signed audit logs, and trails on protocol revision and community size. FastAPI-MCP is the quickest way to put an existing FastAPI app behind MCP, but its last release was in July 2025.


[04]

When to stay with FastMCP (and when to switch)

Stay with FastMCP when:

  • You rely on its auth integrations. The OAuth proxy and provider classes (GitHub, Google, Azure, WorkOS, Auth0, Keycloak and more) save real work.

  • You proxy remote servers or lean on providers. create_proxy() fronts any MCP server, and transforms such as tool search go further than in other Python frameworks.

  • You want the newest protocol revision first. FastMCP 4 tracks the SDK's 2.x line.

  • Your team values community. Most MCP tutorials and answers online use FastMCP.

Switch, or add a layer, when:

  • Tenant isolation must be structural. In FastMCP every tool reads the tenant from the token and every key must include it. One forgotten call site leaks data between customers.

  • You need operations features you would otherwise build: a circuit breaker per downstream service, a tamper-evident audit log, Prometheus metrics or a durable job queue.

  • You want the smallest dependency tree. The official SDK is what FastMCP builds on.

  • Your stack is TypeScript or another language. Use that language's official SDK.

Rewriting a working FastMCP server only to change frameworks rarely pays off. Middleware and auth code is where the migration effort is, so compare the production features you would use, not the hello-world.


[05]

What you need

  • Python 3.10 or later. The examples ran on Python 3.12.

  • One virtual environment per framework, because the mcp pins conflict: - pip install "fastmcp==4.1.2" - pip install "mcp==2.3.0" (plus pyjwt for step 6) - pip install "promptise==1.4.3" - pip install "fastapi-mcp==0.4.0" "mcp<2" uvicorn

  • Node.js 20 or later for the TypeScript server. The example ran on Node 24.

  • About 20 minutes. No API key is needed: there is no model in this guide.


[06]

Step 1: Write the server in FastMCP and the official SDK

Steps 1 to 4 write the same tool, get_order_status, and serve it over Streamable HTTP. Here it is in FastMCP 4:

Pythonfastmcp_server.py
1
2
3
4
5
6
7
8
9
10
11
12
13
from fastmcp import FastMCP

mcp = FastMCP("orders")


@mcp.tool
def get_order_status(order_id: str) -> dict:
    """Get the shipping status of an order."""
    return {"order_id": order_id, "status": "shipped", "server": "FastMCP 4"}


if __name__ == "__main__":
    mcp.run(transport="http", port=8101)  # http://127.0.0.1:8101/mcp

And in the official MCP Python SDK 2. The code is almost identical, because this is the API FastMCP 1.0 gave the SDK:

Pythonsdk_server.py
1
2
3
4
5
6
7
8
9
10
11
12
13
from mcp.server.mcpserver import MCPServer  # mcp 2.x; in mcp 1.x: from mcp.server.fastmcp import FastMCP

mcp = MCPServer("orders")


@mcp.tool()
def get_order_status(order_id: str) -> dict:
    """Get the shipping status of an order."""
    return {"order_id": order_id, "status": "shipped", "server": "MCP Python SDK 2"}


if __name__ == "__main__":
    mcp.run(transport="streamable-http", port=8102)  # http://127.0.0.1:8102/mcp

Two differences matter when you port code. The SDK's decorator needs parentheses: a bare @mcp.tool raises TypeError: The @tool decorator was used incorrectly. Did you forget to call it? Use @tool() instead of @tool. And the SDK uses transport="streamable-http" where FastMCP accepts "http". Both serve at /mcp.


[07]

Step 2: Write it with Promptise Foundry

Promptise Foundry's MCP server is a separate implementation built on the official SDK's low-level server (mcp 1.x). The tool code is the same shape:

Pythonpromptise_server.py
1
2
3
4
5
6
7
8
9
10
11
12
13
from promptise.mcp.server import MCPServer

server = MCPServer(name="orders", version="1.0.0")


@server.tool()
async def get_order_status(order_id: str) -> dict:
    """Get the shipping status of an order."""
    return {"order_id": order_id, "status": "shipped", "server": "Promptise Foundry"}


if __name__ == "__main__":
    server.run(transport="http", port=8103)  # http://127.0.0.1:8103/mcp

The differences show up in production: MCPServer binds 127.0.0.1 by default and validates Host and Origin, answers /health and /health/ready for container probes, and takes middleware for auth, tenants, rate limits, circuit breakers and audit logs. Step 6 uses some of them. The Promptise MCP server docs cover the full middleware list.


[08]

Step 3: Write it in TypeScript with the official SDK

If your team works in TypeScript, the official TypeScript SDK 2 is the reference. Version 2 split the old @modelcontextprotocol/sdk package into @modelcontextprotocol/server, client and per-framework adapters. This follows the Node example from the SDK's HTTP serving guide:

TypeScriptts_server.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// npm install @modelcontextprotocol/server @modelcontextprotocol/node zod
import { createServer } from 'node:http';
import { createMcpHandler, McpServer } from '@modelcontextprotocol/server';
import { localhostHostValidation, localhostOriginValidation, toNodeHandler } from '@modelcontextprotocol/node';
import * as z from 'zod/v4';

const handler = createMcpHandler(() => {
    const server = new McpServer({ name: 'orders', version: '1.0.0' });
    server.registerTool(
        'get_order_status',
        { description: 'Get the shipping status of an order.', inputSchema: z.object({ order_id: z.string() }) },
        async ({ order_id }) => ({
            content: [{ type: 'text', text: JSON.stringify({ order_id, status: 'shipped', server: 'TypeScript SDK 2' }) }]
        })
    );
    return server;
});

const nodeHandler = toNodeHandler(handler);
const validateHost = localhostHostValidation();
const validateOrigin = localhostOriginValidation();
createServer((req, res) => {
    if (!validateHost(req, res) || !validateOrigin(req, res)) return;
    void nodeHandler(req, res);
}).listen(8104, '127.0.0.1'); // http://127.0.0.1:8104/mcp
>_Terminal
npm install @modelcontextprotocol/server@2.3.1 @modelcontextprotocol/node@2.1.1 zod tsx
npx tsx ts_server.ts

The schema is Zod instead of Python type hints, and the handler returns MCP content blocks itself. The Host and Origin checks guard a local server against DNS rebinding. For higher-level TypeScript options, xmcp adds file-based tools and auth plugins, and Vercel's mcp-handler mounts an MCP server in Next.js, Nuxt, SvelteKit, Hono and other Fetch-based frameworks.


[09]

Step 4: Expose an existing FastAPI app with FastAPI-MCP

If your tools already exist as FastAPI endpoints, FastAPI-MCP turns the routes into MCP tools. The route's operation_id becomes the tool name:

Pythonfastapi_mcp_server.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import uvicorn
from fastapi import FastAPI
from fastapi_mcp import FastApiMCP

app = FastAPI()


@app.get("/orders/{order_id}/status", operation_id="get_order_status")
def get_order_status(order_id: str) -> dict:
    """Get the shipping status of an order."""
    return {"order_id": order_id, "status": "shipped", "server": "FastAPI-MCP"}


FastApiMCP(app).mount_http()  # adds the MCP endpoint at /mcp

if __name__ == "__main__":
    uvicorn.run(app, port=8105)

Pin mcp<2. FastAPI-MCP 0.4.0 declares only mcp>=1.12, so a fresh install pulls mcp 2.3.0, and the app fails at startup:

Output
TypeError: Server.__init__() takes 2 positional arguments but 3 were given

FastMCP can do the same job with FastMCP.from_fastapi(app), which is maintained and supports the newer protocol. Choose FastAPI-MCP if you want auth to stay in your FastAPI dependencies. For more ways to serve MCP from FastAPI, see FastAPI MCP servers.


[10]

Step 5: Call every server with one client

With all five servers running, one script calls each of them with the official MCP Python SDK 2 client:

Pythoncall_all.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
"""Call the same tool on every server with the official MCP Python SDK client."""
import asyncio
import json
import sys

from mcp import Client

PORTS = {"FastMCP": 8101, "MCP Python SDK": 8102, "Promptise": 8103, "TypeScript SDK": 8104, "FastAPI-MCP": 8105}


async def main() -> None:
    for name, port in PORTS.items():
        if len(sys.argv) > 1 and name not in sys.argv[1:]:
            continue
        async with Client(f"http://127.0.0.1:{port}/mcp") as client:
            tools = await client.list_tools()
            result = await client.call_tool("get_order_status", {"order_id": "A-1001"})
            print(f"{name:15} protocol {client.protocol_version}  {[t.name for t in tools.tools]} -> {json.loads(result.content[0].text)}")


asyncio.run(main())
Output
FastMCP         protocol 2026-07-28  ['get_order_status'] -> {'order_id': 'A-1001', 'status': 'shipped', 'server': 'FastMCP 4'}
MCP Python SDK  protocol 2026-07-28  ['get_order_status'] -> {'order_id': 'A-1001', 'status': 'shipped', 'server': 'MCP Python SDK 2'}
Promptise       protocol 2025-11-25  ['get_order_status'] -> {'order_id': 'A-1001', 'status': 'shipped', 'server': 'Promptise Foundry'}
TypeScript SDK  protocol 2026-07-28  ['get_order_status'] -> {'order_id': 'A-1001', 'status': 'shipped', 'server': 'TypeScript SDK 2'}
FastAPI-MCP     protocol 2025-11-25  ['get_order_status'] -> {'order_id': 'A-1001', 'status': 'shipped', 'server': 'FastAPI-MCP'}

Every server listed the tool and returned the same answer. The protocol value is the real difference: FastMCP 4 and both official SDKs negotiated the 2026-07-28 revision, while Promptise and FastAPI-MCP, both on mcp 1.x, negotiated 2025-11-25. The client handled both. Clients that follow the spec negotiate down, so a 2025-11-25 server keeps working, but it won't offer features that only the newer revision defines.


[11]

Step 6: Add auth, tenants and rate limits

Hello-world servers look the same everywhere. The comparison that matters is the production server. This step builds the same multi-tenant server twice, in FastMCP and in Promptise: a JWT with a tenant_id claim, a tool that returns only that tenant's orders, a rate limit of three requests in a burst per tenant, and no access without a token. Both servers share this demo data:

Pythonorders_data.py
1
2
3
4
5
6
SECRET = "dev-only-secret-change-me-32-bytes!"  # HS256 for the demo; use your IdP's JWKS in production
AUDIENCE = "orders-mcp"
ORDERS = {
    "acme": [{"id": "A-1001", "status": "shipped"}],
    "globex": [{"id": "G-2001", "status": "pending"}],
}

In FastMCP, JWTVerifier checks the token, and the tenant comes from its claims through get_access_token(). The rate limiter keys on whatever get_client_id returns:

Pythonfastmcp_tenants.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from fastmcp import FastMCP
from fastmcp.server.auth.providers.jwt import JWTVerifier
from fastmcp.server.dependencies import get_access_token
from fastmcp.server.middleware.rate_limiting import RateLimitingMiddleware

from orders_data import AUDIENCE, ORDERS, SECRET

mcp = FastMCP("orders", auth=JWTVerifier(public_key=SECRET, algorithm="HS256", audience=AUDIENCE))


def tenant_of(context) -> str:
    token = get_access_token()
    return token.claims["tenant_id"] if token else "anonymous"


# One bucket per tenant: 3 requests at once, then 1 every 10 seconds.
mcp.add_middleware(RateLimitingMiddleware(max_requests_per_second=0.1, burst_capacity=3, get_client_id=tenant_of))


@mcp.tool
def list_orders() -> list[dict]:
    """List the caller's orders."""
    return ORDERS[get_access_token().claims["tenant_id"]]


if __name__ == "__main__":
    mcp.run(transport="http", port=8111)

In Promptise, AuthMiddleware reads the tenant claim into ctx.client.tenant_id, and RateLimitMiddleware puts the tenant into the bucket key itself:

Pythonpromptise_tenants.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from promptise.mcp.server import AuthMiddleware, JWTAuth, MCPServer, RateLimitMiddleware, RequestContext

from orders_data import AUDIENCE, ORDERS, SECRET

server = MCPServer(name="orders", version="1.0.0", require_auth=True)  # 401 without a token
server.add_middleware(AuthMiddleware(JWTAuth(secret=SECRET, audience=AUDIENCE), tenant_claim="tenant_id"))
# Buckets are per client and tenant automatically: 3 requests at once, then 6 per minute.
server.add_middleware(RateLimitMiddleware(rate_per_minute=6, burst=3))


@server.tool()
async def list_orders(ctx: RequestContext) -> list[dict]:
    """List the caller's orders."""
    return ORDERS[ctx.client.tenant_id]


if __name__ == "__main__":
    server.run(transport="http", port=8113)

The test client mints tokens for two tenants, calls the tool four times as acme, once as globex, and once without a token:

Pythontenant_check.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
"""Call list_orders as two tenants and without a token: python tenant_check.py <port>"""
import asyncio
import json
import sys
import time

import httpx2
import jwt
from mcp import Client
from mcp.client.streamable_http import streamable_http_client
from mcp.shared.exceptions import MCPError

from orders_data import AUDIENCE, SECRET

URL = f"http://127.0.0.1:{sys.argv[1]}/mcp"


def token(client_id: str, tenant: str) -> str:
    claims = {"sub": client_id, "client_id": client_id, "tenant_id": tenant, "aud": AUDIENCE, "exp": int(time.time()) + 300}
    return jwt.encode(claims, SECRET, algorithm="HS256")


async def call(label: str, bearer: str | None, times: int = 1) -> None:
    headers = {"Authorization": f"Bearer {bearer}"} if bearer else {}
    try:
        async with httpx2.AsyncClient(headers=headers) as http:
            async with Client(streamable_http_client(URL, http_client=http)) as client:
                for _ in range(times):
                    try:
                        result = await client.call_tool("list_orders", {})
                    except MCPError as exc:  # a JSON-RPC error response
                        print(f"{label:8} MCPError: {exc}")
                        continue
                    data = json.loads(result.content[0].text)
                    if result.is_error:  # a tool error result, readable by the model
                        err = data["error"]
                        print(f"{label:8} tool error: {err['message']} (retryable: {err['retryable']}) {err.get('suggestion', '')}")
                    else:
                        print(f"{label:8} {data}")
    except Exception as exc:  # noqa: BLE001  (the session itself failed)
        while isinstance(exc, ExceptionGroup):
            exc = exc.exceptions[0]
        print(f"{label:8} session failed: {type(exc).__name__}: {exc}")


async def main() -> None:
    await call("acme", token("agent-1", "acme"), times=4)
    await call("globex", token("agent-2", "globex"))
    await call("no token", None)


asyncio.run(main())
Output
$ python tenant_check.py 8111   # fastmcp
acme     [{'id': 'A-1001', 'status': 'shipped'}]
acme     MCPError: Rate limit exceeded for client: acme
acme     MCPError: Rate limit exceeded for client: acme
acme     MCPError: Rate limit exceeded for client: acme
globex   [{'id': 'G-2001', 'status': 'pending'}]
no token session failed: MCPError: Server returned an error response
Output
$ python tenant_check.py 8113   # promptise
acme     [{'id': 'A-1001', 'status': 'shipped'}]
acme     [{'id': 'A-1001', 'status': 'shipped'}]
acme     [{'id': 'A-1001', 'status': 'shipped'}]
acme     tool error: Rate limit exceeded (retryable: True) Wait 10 seconds before retrying.
globex   [{'id': 'G-2001', 'status': 'pending'}]
no token session failed: MCPError: Server returned an error response

Both servers kept the tenants apart, limited acme without touching globex, and refused the request without a token with HTTP 401, which the client reports as "Server returned an error response". Two differences are worth knowing:

  • What counts against the limit. FastMCP's RateLimitingMiddleware counts every MCP request. With the 2026-07-28 protocol, the client's server/discover and tools/list requests used two of acme's three tokens, so only one tool call got through. Promptise counts tool calls, so three got through. In FastMCP, size the bucket for all requests or write a middleware that limits on_call_tool only.

  • How a rejection looks. FastMCP raises a JSON-RPC error, so the client gets an exception. Promptise returns a tool error marked retryable, with a hint on how long to wait, which an agent can read and act on.

Neither is wrong. The real gap is that in FastMCP the tenant is a convention your code applies at every call site, and in Promptise it is part of the auth context that the middleware uses for every key. For the patterns behind this, see per-client rate limiting for MCP servers and MCP server authentication with JWT and OAuth.


[12]

FastMCP vs Promptise: which is better for production?

Promptise Foundry is the framework behind this site, so here is a direct comparison, including where FastMCP is the better choice. Both register tools with decorators and type hints, verify JWTs, run middleware, rate-limit, cache, emit OpenTelemetry, mount servers into one, bridge OpenAPI specs and ship an in-memory test client. The differences:

Area

FastMCP 4.1 is stronger

Promptise Foundry 1.4.3 is stronger

Protocol

The 2026-07-28 revision on mcp 2.x

Behind: 2025-11-25 on mcp 1.x

Authentication

OAuth proxy, full OAuth server, MultiAuth, about 15 provider classes

API keys with a tenant and roles per key; sessions bound to the credential

Multi-tenancy

Tenant handling is your code

The tenant claim scopes rate limits, caches, audit entries and tool visibility

Rate limiting

Global, per client or sliding window

Per client and tenant, per tool, with retryable errors and a wait hint

Resilience

Retry and error-handling middleware

Circuit breaker, timeouts, concurrency limiter

Observability

OpenTelemetry on by default, with telemetry modes

Prometheus, HMAC-chained audit log, live dashboard, plus OpenTelemetry

Human approval

Elicitation (the server asks the user)

Approval gates on tools that fail closed

Composition

Proxies for remote servers, more providers and transforms

mount(), an OpenAPI provider, versioning, basic transforms

Ecosystem

About 28,000 stars, most tutorials, Prefect Horizon hosting

The same package builds the agents and the runtime that call the server

Choose FastMCP if you need OAuth with an identity provider, the newest protocol revision, remote proxies, or the community and hosting that come with it. Choose Promptise if you serve several customers from one server and want tenant isolation, circuit breakers and a signed audit trail without writing that middleware, or if you also build the agents in Python and want one framework for both. You can run both: they are separate services, and any MCP client calls either one, as step 5 shows.


[13]

Running it in production

Authentication

Whichever framework you pick, verify tokens from your identity provider with JWKS and check aud, so a token minted for another service is refused. FastMCP has the most ready-made options, including an OAuth proxy for providers without dynamic client registration. The official SDKs implement the spec's resource-server role and leave the rest to you. Promptise covers JWT, JWKS and API keys, and with require_auth=True it binds each HTTP session to the credential that opened it.

Multi-tenancy

Put the tenant in the token, never in a tool argument the model can change. Then make sure every cache key, rate-limit bucket, audit entry and database query includes it. In FastMCP and the SDKs that is your code. In Promptise the middleware does it once. Either way, test it with two tenants, as step 6 does. Multi-tenant AI agents covers the agent side.

Observability

FastMCP and the official Python SDK both emit OpenTelemetry spans by default. They are no-ops until you install an SDK and exporter, such as opentelemetry-distro with OTEL_EXPORTER_OTLP_ENDPOINT. Promptise adds Prometheus metrics, structured logs and an HMAC-chained audit log on top. Whatever you use, record the tool name, the caller, the tenant, the latency and the error code of every call. See MCP server monitoring.

Deployment

Use Streamable HTTP behind a reverse proxy with TLS, and bind 127.0.0.1 unless the proxy is on another machine. Streamable HTTP sessions live in one process, so with several replicas either route each mcp-session-id to one replica or run stateless. FastMCP 4.1 now expires idle HTTP sessions after the SDK's 30-minute default. Prefect Horizon is the hosted option for FastMCP servers; the others run anywhere an ASGI app or Node runs.

Upgrades

Pin exact versions and read release notes before upgrading. FastMCP 4.0 removed server-initiated sampling and roots and changed proxy behavior, and mcp 2.0 renamed classes and replaced httpx with httpx2. Run your client tests against every upgrade, as in testing an MCP server.


[14]

Common problems when switching

Symptom

Cause

Fix

ModuleNotFoundError: No module named 'mcp.server.fastmcp'

You upgraded to mcp 2.x, where the class is MCPServer

from mcp.server.mcpserver import MCPServer, or pin mcp<2

TypeError: The @tool decorator was used incorrectly. Did you forget to call it?

Bare @mcp.tool from FastMCP code in the official SDK 2

Use @mcp.tool()

TypeError: Server.__init__() takes 2 positional arguments but 3 were given at startup

FastAPI-MCP 0.4.0 with mcp 2.x

Pin mcp<2 next to fastapi-mcp

The resolver says fastmcp==4.1.2 and promptise>=1.4.3 are incompatible

FastMCP 4 needs mcp>=2.2, Promptise 1.4 needs mcp<2

Separate environments or services; FastMCP 3.x still uses mcp 1.x

A FastMCP tutorial's code doesn't run

It was written for FastMCP 2 or 3, or for the npm fastmcp package

Check the version badge in the FastMCP docs and your import

The first tool call is already rate-limited in FastMCP

RateLimitingMiddleware counts server/discover, tools/list and other requests

Raise the burst, or limit on_call_tool only

404 Session not found behind a load balancer

The session lives in another replica

Sticky sessions on mcp-session-id, or stateless HTTP


[15]

Limitations

  • This is a snapshot. FastMCP shipped 4.1.2 on the day this was written, and every project here moves fast. Check versions before you decide.

  • Promptise Foundry is behind on the protocol. It is built on mcp 1.x and speaks revisions up to 2025-11-25, and its community is much smaller than FastMCP's.

  • The table compares built-in features. Anything marked "your code" or "not built in" can be added with middleware in that framework.

  • Not every option was run. Five servers ran for this guide. mcp-use (Python and TypeScript), Golf (a framework on top of FastMCP 4), xmcp, mcp-handler and the Go, Java, C# and Rust SDKs are described from their repositories only.


[16]

Frequently asked questions

Is FastMCP the same as the MCP Python SDK?

No. FastMCP 1.0 was adopted into the official SDK in 2024 as mcp.server.fastmcp.FastMCP. The standalone fastmcp package, maintained by Prefect, grew into a much larger framework. In mcp 2.0 the SDK renamed its class to MCPServer, so today "FastMCP" almost always means Prefect's package, which is built on top of the SDK.

What is a good FastMCP alternative for multi-tenant servers?

Choose a framework that makes the tenant part of the auth context instead of a value each tool reads. Promptise Foundry does this: the tenant claim from the token scopes rate limits, caches, audit entries and tool visibility. With FastMCP or the official SDKs, read the claim from the access token in every tool and include it in every key, and test with two tenants.

Is FastMCP production ready?

Yes, for most servers. FastMCP 4 has OAuth and JWT auth, authorization, rate limiting, caching, OpenTelemetry, background tasks and a hosted deployment option in Prefect Horizon. What you add yourself is mostly tenant isolation, circuit breakers, audit logging and metrics export. Pin versions, because major releases have breaking changes.

FastMCP vs FastAPI-MCP: which should I use?

Use FastAPI-MCP when you have a FastAPI app and want its routes and auth dependencies as MCP tools with little new code. Use FastMCP for MCP-first servers, or FastMCP.from_fastapi(app) to wrap a FastAPI app with a maintained framework. FastAPI-MCP's last release was 0.4.0 in July 2025 and it needs mcp<2.

What is the best FastMCP alternative for TypeScript?

The official TypeScript SDK 2 (@modelcontextprotocol/server) is the reference and runs on Node, Express, Hono, Fastify and edge runtimes. For more structure, xmcp offers file-based tools and auth plugins, and Vercel's mcp-handler mounts MCP routes in Next.js, Nuxt, SvelteKit or Hono apps. The npm package called fastmcp is a separate TypeScript project, not Prefect's FastMCP.

FastMCP vs Promptise: which should I use?

Use FastMCP for OAuth with identity providers, the 2026-07-28 protocol revision, proxies for remote servers and the largest community. Use Promptise Foundry for multi-tenant servers that need tenant-scoped limits, caches and audit logs, circuit breakers, Prometheus metrics and approval gates built in, or when you also build your agents with it. Both answer the same MCP clients, so the choice is about production features.

Can I switch frameworks without changing my MCP clients?

Yes. Clients see only the protocol, the endpoint and the tool schemas. Keep the URL, the tool names and the parameter names, and clients keep working, as step 5 shows with one client and five servers. Check the negotiated protocol revision, and move auth carefully, because token validation rules often differ between frameworks.


[17]

Next steps

  • Build a production MCP server step by step: production MCP server in Python.

  • Secure it: MCP security best practices.

  • Add per-client limits: MCP rate limiting per client.

  • Watch it in production: MCP server monitoring.

  • Add middleware for logging, limits and audit: MCP server middleware.

  • Reference: FastMCP docs, MCP Python SDK v2 migration guide, TypeScript SDK v2 docs and Promptise MCP server middleware.

Contents

  1. 01What is the best FastMCP alternative?
  2. 02What is FastMCP, and which one are you using?
  3. 03How do production MCP server frameworks compare?
  4. 04When to stay with FastMCP (and when to switch)
  5. 05What you need
  6. 06Step 1: Write the server in FastMCP and the official SDK
  7. 07Step 2: Write it with Promptise Foundry
  8. 08Step 3: Write it in TypeScript with the official SDK
  9. 09Step 4: Expose an existing FastAPI app with FastAPI-MCP
  10. 10Step 5: Call every server with one client
  11. 11Step 6: Add auth, tenants and rate limits
  12. 12FastMCP vs Promptise: which is better for production?
  13. 13Running it in production
  14. 14Common problems when switching
  15. 15Limitations
  16. 16Frequently asked questions
  17. 17Next steps

Learning paths

Want more structure? Paths put guides in order, like a short course.

See the paths →

> Tools and the Model Context Protocol (MCP)

Related guides

All guides in this topic →
  • // 01Beginner · 20 minHow to Build an AI Agent in Python (2026 Guide)Build an AI agent in Python step by step: write the tool loop, serve tools over MCP, add model fallback, approvals and limits. Runnable code, real output.
  • // 02Intermediate · 15 minAI Agent Audit Logging: Tamper-Evident Logs in PythonLog every AI agent tool call to an HMAC-chained file, then catch edits, deletions and reordering with one command. Real tamper tests, real output.
  • // 03Intermediate · 20 minAI Agent Human in the Loop: Approve Tool Calls in PythonMake your AI agent ask a human before it refunds, emails or deletes. Approve or reject tool calls in Python, with real runs and honest limits.
  • // 04Intermediate · 18 minAI Agent Prompt Injection: How to Defend Against ItHow indirect prompt injection reaches AI agents through tool results, and how to defend: untrusted-data prompts, scanners, approval gates, least privilege.
  • // 05Intermediate · 18 minAI Agent YAML: Define and Run an Agent From a Config FileDefine an AI agent in a YAML file: model, instructions and MCP servers. Validate it, run it from the CLI or Python, keep secrets out, review it in Git.
  • // 06Intermediate · 24 minBuild a Multi-Agent System in Python (and When Not To)Build a coordinator that delegates to specialist AI agents in Python, trace every call, and see what a multi-agent system really costs.

Keep going.

Browse every guide by topic and level, or follow a learning path that puts them in order.

All guidesLearning paths