🚀 FastMCP Extensions 🚀
🧩 The paved road on top of FastMCP. Wire the hard parts once, reuse them on every server you ship.
What It Adds Over Baseline FastMCP
Baseline FastMCP is the protocol engine: it gives you the machinery to register tools, prompts, and resources and to speak MCP over stdio or HTTP. This library encodes how you actually ship an MCP server, so each new one inherits the hardening instead of reinventing it:
- 🔐 Auth wired once, reused everywhere -
build_mcp_auth()is a pure, typed factory that assembles the right verifier — or aMultiAuthwhen several apply — from explicit configs: interactive OIDC for humans (browser Auth Code + PKCE), headless JWT for machines and agents, and opaque-token introspection. Harden it in one place and every server benefits. See Authenticating an MCP Server. - 🧯 Secure, predictable defaults - The auth factory reads no environment variables: each server owns its own env-var names and can validate a complete configuration before building the provider. Refresh-token storage is injectable, so a server can use a durable, shared backend across restarts and replicas without the library owning your database.
- 🕵️ Credential hygiene when you wire it in - An installable redaction filter scrubs bearer tokens and other credential values from controlled log records, while one-way key normalization makes arbitrary client IDs and other store keys legal for durable backends.
- 🎚️ Tool filtering from MCP annotations - Read-only mode, no-destructive mode, and module/tool exclusion use MCP tool annotations (
readOnlyHint,destructiveHint, …) and request/server configuration. Filters compose with logical AND, so layering can only narrow the surface, never widen it. See Tool Filtering. - 🧩 MCP Apps UI support without per-server wiring - Annotate a tool with
interactive-ui=True, opt into the standard filters, and the library hides it from clients that cannot render MCP Apps UI.run_mcp_http_server()carries the client's extension declaration through stateless HTTP automatically. See MCP Apps UI support. - 🛡️ Modality gating, safe in local and hosted deploys - The standard trusted-execution filter hides tools annotated
requiresClientFilesystem=Trueby default, and the gate is forced off under HTTP regardless of configuration. Callassert_http_trusted_execution_disabled()at HTTP startup to fail loudly on an unsafe configuration. - 🧵 Deferred registration, solved -
@mcp_tool/@mcp_prompt/@mcp_resourcetag tools, prompts, and resources into a registry (auto-detecting the domain from the file stem), and the domain-filteredregister_*functions register them in one call — organize by domain without fighting import order. - 🏭 A server factory with fewer moving parts -
mcp_server()hands you a FastMCP instance that already has a server-info resource, optional asset discovery, and credential resolution from HTTP headers or env vars viaget_mcp_config— typed pieces instead of hand-wired boilerplate. - 🖥️ One codebase, two front-ends -
cli_app()is the CLI counterpart ofmcp_server(): shared tool functions and the same telemetry sinks can power both surfaces. Write a tool once; call it from the command line and expose it over MCP. - 📖 Auto-generated docs for every tool - A Markdown docs generator (Docusaurus- and pdoc-compatible) renders your tool surface from the source of truth, giving every tool its own URL anchor to share with stakeholders. Documenting and announcing changes stops being a manual step.
- 📈 Telemetry that's free until you want it - Sentry, Segment, and structured-log sinks record timing, success, and error type across both MCP and CLI paths. Sentry and Segment are no-ops unless you supply their keys, so the telemetry wiring can ship in the base template.
- 🌐 Browser-friendly landing page - A registrable landing page so a browser
GETon your MCP HTTP endpoint returns something human-readable instead of an error. - 🧪 Test and debug tooling -
call_mcp_tool/run_tool_test/run_http_tool_testexercise tools with JSON args over stdio and HTTP, and tool-list measurement catches context-window truncation before it bites an agent. - 🧱 A buffer against major-version churn - Servers build against this library's API, not FastMCP's internals, so a FastMCP major bump lands here first. Through the 2.x→3.x transition this library supported both lines during the overlap and the servers on top needed little or no rework; it now targets FastMCP 3.x, and we expect to absorb the 4.x move the same way.
Philosophy
Opinionated on purpose.
- A CLI and an MCP server are two front-ends over one shared body of code, not two implementations that drift.
- Auth, filtering, telemetry, docs, and testing scaffolding are wired once and inherited.
- We want a more capable MCP server implementation as baseline - with fewer footguns and less repeated code.
Installation
pip install fastmcp-extensions
Or with uv:
uv add fastmcp-extensions
Quick Start
Using the MCP Server Factory
The mcp_server function creates a FastMCP instance with built-in server info resources and optional credential resolution:
from fastmcp_extensions import mcp_server, MCPServerConfigArg
app = mcp_server(
name="my-mcp-server",
package_name="my-package",
advertised_properties={
"docs_url": "https://github.com/org/repo",
"release_history_url": "https://github.com/org/repo/releases",
},
server_config_args=[
MCPServerConfigArg(
name="api_key",
http_header_key="X-API-Key",
env_var="MY_API_KEY",
required=True,
sensitive=True,
),
],
)
# Server info resource is automatically registered at {name}://server/info
# Get credentials from HTTP headers or environment variables
from fastmcp_extensions import get_mcp_config
api_key = get_mcp_config(app, "api_key")
Using Annotation Constants
from fastmcp_extensions import (
READ_ONLY_HINT,
DESTRUCTIVE_HINT,
IDEMPOTENT_HINT,
OPEN_WORLD_HINT,
)
# Use in tool annotations
annotations = {
READ_ONLY_HINT: True,
IDEMPOTENT_HINT: True,
}
Using Deferred Registration
from fastmcp import FastMCP
from fastmcp_extensions import (
mcp_tool,
mcp_resource,
register_mcp_tools,
register_mcp_resources,
)
# Define tools with the decorator (domain auto-detected from filename)
@mcp_tool(read_only=True, idempotent=True)
def list_items() -> list[str]:
"""List all available items."""
return ["item1", "item2"]
@mcp_resource("myserver://version", "Server version", "application/json")
def get_version() -> dict:
"""Get server version info."""
return {"version": "1.0.0"}
# Register with FastMCP app
app = FastMCP("my-server")
register_mcp_tools(app)
register_mcp_resources(app)
Measuring Tool List Size
import asyncio
from fastmcp_extensions.utils.describe_server import measure_tool_list_detailed
async def check_tool_size():
measurement = await measure_tool_list_detailed(app, server_name="my-server")
print(measurement)
# Output:
# MCP Server: my-server
# Tool count: 10
# Total characters: 5,432
# Average chars per tool: 543
asyncio.run(check_tool_size())
Testing Tools
from fastmcp_extensions.utils.test_tool import call_mcp_tool, run_tool_test
import asyncio
# Call a tool programmatically
result = asyncio.run(call_mcp_tool(app, "list_items", {}))
# Or use the CLI helper
run_tool_test(app, "list_items", "{}")
Getting Prompt Text
from fastmcp_extensions.prompts import get_prompt_text
import asyncio
# Get prompt text for agents that can't access prompts directly
text = asyncio.run(get_prompt_text(app, "my_prompt", {"arg": "value"}))
Authenticating an MCP Server
MCP servers built on this library should not talk to an identity provider or
manage token lifecycles themselves. They only declare which verifier(s) they
trust; FastMCP verifies the Authorization: Bearer <token> on every request.
Minting tokens is the client's job. This library owns the assembly.
The entry point is build_mcp_auth(): a pure, typed factory that assembles
an AuthProvider | None from explicit config objects (return None = run
unauthenticated, e.g. local stdio). It reads no environment variables — the
server owns its own env-var names (whatever branding it prefers) and maps them
into the configs, so this library never imposes a naming scheme or a backend:
import os
from fastmcp_extensions import (
JWTAuthConfig,
OIDCAuthConfig,
build_mcp_auth,
mcp_server,
)
app = mcp_server(name="my-mcp-server", package_name="my-package")
# The server decides its env-var names and maps them into typed configs. Read
# every field with os.getenv and only build the config once all are present, so
# a partially-configured deployment never raises a KeyError.
config_url = os.getenv("MY_OIDC_CONFIG_URL")
client_id = os.getenv("MY_OIDC_CLIENT_ID")
client_secret = os.getenv("MY_OIDC_CLIENT_SECRET")
base_url = os.getenv("MY_MCP_SERVER_URL")
oidc = None
if config_url and client_id and client_secret and base_url:
oidc = OIDCAuthConfig(
config_url=config_url,
client_id=client_id,
client_secret=client_secret,
base_url=base_url,
)
app.auth = build_mcp_auth(
oidc=oidc, # interactive humans (browser Auth Code + PKCE), optional
jwt=JWTAuthConfig( # headless machines / agents, optional
jwks_uri="https://idp.example/.well-known/jwks.json",
issuer="https://idp.example/",
audience="my-api",
),
)
build_mcp_auth() understands three transport-auth modes and combines any that
are configured via FastMCP's MultiAuth:
| Mode | Who it's for | Config object |
|---|---|---|
Interactive OIDC (OIDCProxy) |
humans (browser Auth Code + PKCE) | OIDCAuthConfig(config_url, client_id, client_secret, base_url, ...) |
Headless JWT (JWTVerifier) |
machines / agents | JWTAuthConfig(...) with either jwks_uri=... or public_key=..., plus issuer / audience / algorithm |
Opaque-token introspection (IntrospectionTokenVerifier) |
machines with opaque tokens | IntrospectionAuthConfig(introspection_url, client_id, client_secret) |
static_tokens=, base_url=, and required_scopes= round out the parameters.
It returns a single verifier when one is configured, or a MultiAuth when
several are. For a durable, shared interactive-OIDC store (so refresh tokens
survive restarts and span replicas), the server constructs its own backend and
injects it via OIDCAuthConfig(client_storage=...) — keeping all
backend-specific config (project, database, encryption) in the deployment, not
in this library.
Client side. A headless client mints its own short-lived bearer token and
sends it as Authorization: Bearer <token>; use
fetch_client_credentials_token(ClientCredentials(...)) for an OAuth 2.0
client-credentials grant. Nothing is stored server-side — no refresh-token
state. If the token the client mints is also a valid credential for a downstream
API (i.e. the verifier points at that API's issuer), the server can reuse the
verified token as the downstream bearer via FastMCP's get_access_token() — one
token doing both transport auth and downstream authorization.
MCP Apps UI support
MCP Apps UI support is a tool-visibility gate for servers that expose
interactive renderings. Annotate a tool with interactive_ui=True and enable
the standard filters:
from fastmcp_extensions import mcp_server, mcp_tool, register_mcp_tools
app = mcp_server(
name="my-server",
include_standard_tool_filters=True,
)
@mcp_tool(interactive_ui=True)
def show_dashboard() -> str:
"""Return data for an interactive dashboard."""
return "dashboard data"
register_mcp_tools(app)
The standard interactive_ui_filter leaves ordinary tools visible and hides
annotated tools from clients that did not declare the
io.modelcontextprotocol/ui extension. This is a rendering-capability check,
not a privilege boundary: extension declarations are client-controlled and
must never be used to grant authority.
Stateless HTTP capability carry-through
The problem. A client declares its extensions once, during initialize. A
stateless HTTP server builds a fresh session per request and discards it, so by
the time tools/list arrives that declaration is gone. The UI gate would
therefore hide interactive tools from every client, including the ones that can
render them.
The protocol rule we use. The streamable HTTP transport (revision
2025-03-26) says a server MAY return an Mcp-Session-Id header on its response
to initialize, and that a client receiving one MUST include it on every
subsequent request. That MUST is the only client-side behavior this relies on.
What we put in it. The spec makes the session ID server-assigned and opaque
to the client, and says nothing about its contents — so we make it carry the
data instead of pointing at it. run_mcp_http_server() encodes the declared
extensions into the ID it returns, and decodes them from the header on each
later request. The client stores the state; the server keeps none, which means
no session table, no sticky routing, and nothing lost across restarts or
replicas.
The value is <uuid4>.<base64url payload>: a random component for the
uniqueness the spec asks of session IDs, then the encoded extension IDs. The
encoding is not decoration — the spec restricts the value to visible ASCII
(0x21–0x7E), so arbitrary payloads have to be encoded to be legal.
The ID is minted once, on the initialize response, and never reissued: the
middleware sets the header only for that one exchange. So this carries state
fixed at session start, not state that changes call to call — a server wanting
the latter cannot get it by handing back a new ID, because clients capture the
session ID at initialize and are under no obligation to notice a later one.
Clients that do not echo it. Set the X-MCP-Extensions header on each
request, listing extension IDs separated by commas or whitespace. This is our
own header rather than a protocol feature — an escape hatch for clients that do
not implement session IDs.
Both paths are unauthenticated client statements, so gate rendering with them, never authority.
Goose Desktop is verified against this end to end: it declares the UI extension
at initialize, echoes the session ID, and renders interactive tools over
stateless HTTP with no custom configuration.
This mechanism has a known end date. MCP revision 2026-07-28 removes protocol
sessions and carries client capabilities in per-request _meta, which will
replace both paths above once the stack supports it.
HTTP server runner
run_mcp_http_server() builds and serves a FastMCP HTTP application. When
stateless HTTP is in effect — stateless_http=True, or FastMCP's own
stateless_http setting — it adds the capability carry-through layers by
default:
from fastmcp_extensions import run_mcp_http_server
run_mcp_http_server(
app,
path="/mcp",
transport="streamable-http",
stateless_http=True,
)
When stateless HTTP is in effect, the composed layers are the caller's
wrapper= innermost, then CapabilityTokenMiddleware, then the
path-scoped RejectEventStreamGetMiddleware outermost. The latter returns
405 with Allow: POST, DELETE for an SSE-style GET to the MCP endpoint
while allowing the browser landing page and unrelated routes through. Pass
enable_stateless_capability_middleware=False to opt out. Stateful HTTP and
SSE transport do not receive these stateless-only layers.
Tool Filtering
mcp_server() can add the standard filters with
include_standard_tool_filters=True:
app = mcp_server(
name="my-server",
include_standard_tool_filters=True,
)
The standard filters support read-only mode, no-destructive mode, module include/exclude, tool exclusion, and the trusted-execution gate. Read-only and no-destructive modes use the tool's MCP annotations; annotate tools at registration time:
@mcp_tool(read_only=True, destructive=False)
def list_items() -> list[str]:
return ["item1", "item2"]
Filters compose with logical AND, so each filter can only narrow the visible
tool set. Tools annotated requiresClientFilesystem=True remain hidden unless
trusted execution is enabled for a local stdio server. The gate is always forced
off for HTTP requests; call assert_http_trusted_execution_disabled(app) from
an HTTP entrypoint to fail fast if its configuration is enabled.
Poe Tasks for MCP Servers
This library provides template scripts for common MCP development tasks. Copy these to your project and customize:
bin/test_mcp_tool.py- Test tools with JSON arguments via stdiobin/test_mcp_tool_http.py- Test tools over HTTP transportbin/measure_mcp_tool_list.py- Measure tool list size
Add to your poe_tasks.toml:
[tool.poe.tasks.mcp-tool-test]
help = "Test MCP tools directly with JSON arguments"
cmd = "python bin/test_mcp_tool.py"
[tool.poe.tasks.mcp-tool-test-http]
help = "Test MCP tools over HTTP transport"
cmd = "python bin/test_mcp_tool_http.py"
[tool.poe.tasks.mcp-measure-tools]
help = "Measure the size of the MCP tool list output"
cmd = "python bin/measure_mcp_tool_list.py"
API Reference
Server Factory
mcp_server- Create a FastMCP instance with a built-in server info resource, optional asset discovery, credential resolution, and tool filtering.MCPServerConfigArg- Configuration for credential resolution and other server settings.get_mcp_config- Get a credential from HTTP headers or environment variables.
CLI
cli_app- Create a Cyclopts CLI app with shared structured-log, Sentry, and Segment telemetry.
Tool Filtering
- Standard filters - Read-only, no-destructive, module/tool exclusion, and trusted-execution filters based on MCP annotations and server configuration; enable them with
include_standard_tool_filters=True. ANNOTATION_INTERACTIVE_UI/interactive_ui_filter- Gate tools annotatedinteractive-uion the client'sio.modelcontextprotocol/uirendering capability.extension_tool_filter- Build a rendering-capability filter for any extension ID and annotation key.assert_http_trusted_execution_disabled- Fail fast when trusted execution is enabled for an HTTP entrypoint.
HTTP Helpers
run_mcp_http_server- Build and serve a FastMCP HTTP application with stateless capability carry-through defaults.DEFAULT_UVICORN_CONFIG- Default Uvicorn settings used byrun_mcp_http_server.fastmcp_extensions.utils.docs.generate_markdown_docs- Generate Docusaurus- and pdoc-compatible Markdown docs from a FastMCP server inspection.register_landing_page/render_default_landing_html- Add a browser-friendlyGETlanding page to an MCP HTTP endpoint.AuthorizationRedactionFilter/install_authorization_redaction- Scrub credential values from controlled log records.HashKeyNormalizer/NormalizedKeysWrapper- Normalize arbitrary storage keys for durable key-value backends.
MCP Apps and capability carry-through
CapabilityTokenMiddleware/RejectEventStreamGetMiddleware- Carry extension declarations through stateless HTTP and reject SSE-styleGETrequests at the MCP path.encode_capability_token/decode_capability_token- Encode and fail-closed decode self-describing capability tokens.client_supports_extension/client_declared_extensions_from_headers- Resolve client extension declarations from FastMCP session capabilities, the session token, and the fallback header.DEFAULT_EXTENSIONS_HEADER- Default fallback header name,X-MCP-Extensions.
Annotations
| Constant | Description | FastMCP Default |
|---|---|---|
READ_ONLY_HINT |
Tool only reads data | False |
DESTRUCTIVE_HINT |
Tool modifies/deletes data | True |
IDEMPOTENT_HINT |
Repeated calls have same effect | False |
OPEN_WORLD_HINT |
Tool interacts with external systems | True |
Decorators
@mcp_tool(read_only, destructive, idempotent, open_world, requires_client_filesystem, interactive_ui, extra_help_text)- Tag a tool for deferred registration; the domain comes from the defining module's file stem@mcp_prompt(name, description)- Tag a prompt for deferred registration@mcp_resource(uri, description, mime_type)- Tag a resource for deferred registration@mcp_provider(interactive_ui, annotations)- Tag a provider factory for deferred tool registration.
Registration Functions
register_mcp_tools(app, domain, exclude_args)- Register tools with FastMCP appregister_mcp_prompts(app, domain)- Register prompts with FastMCP appregister_mcp_resources(app, domain)- Register resources with FastMCP app
Testing Utilities
call_mcp_tool(app, tool_name, args)- Call a tool asynchronouslylist_mcp_tools(app)- List all available toolsrun_tool_test(app, tool_name, json_args)- Run a tool test with JSON argsrun_http_tool_test(http_server_command, port, tool_name, args, env)- Test over HTTP
Measurement Utilities
measure_tool_list(app)- Get (tool_count, total_chars) tuplemeasure_tool_list_detailed(app, server_name)- Get detailed measurementget_tool_details(app)- Get per-tool size breakdown
Prompt Utilities
get_prompt_text(app, prompt_name, arguments)- Get prompt text contentlist_prompts(app)- List all available prompts
Telemetry
ToolCallTelemetryMiddleware- Record MCP tool-call timing, success, and error type.TelemetrySinks/TelemetryRecord/ToolCallTelemetryRecord- Configure telemetry destinations and represent emitted records.
Auth Utilities
build_mcp_auth(*, oidc=None, jwt=None, introspection=None, static_tokens=None, base_url=None, required_scopes=None)- Pure, typed factory that assembles one verifier or aMultiAuthfrom explicit configs. Reads no environment variables — the calling server maps its own env into the configs.OIDCAuthConfig/JWTAuthConfig/IntrospectionAuthConfig- Typed configs for the three verifier modes.fetch_client_credentials_token(ClientCredentials(...))- Client-side OAuth 2.0 client-credentials grant to mint a short-lived bearer token.ClientCredentials- Parameters for the client-credentials grant (token URL, client id/secret, scope, audience, auth method).ClientCredentialsExchangeMiddleware/wrap_client_credentials- Exchange presented client credentials for a bearer token before FastMCP authentication.build_client_credentials_post_kwargs- Build token-request form fields for the configured client-credentials auth method.
Development
# Install dependencies
uv sync --extra dev
# Run tests
uv run poe test
# Format and lint
uv run poe fix
# Run all checks
uv run poe check
License
MIT License - see LICENSE for details.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fastmcp_extensions-0.20.0.tar.gz.
File metadata
- Download URL: fastmcp_extensions-0.20.0.tar.gz
- Upload date:
- Size: 237.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2b365223cd379edd4192b15c878d81b4a7c3ed301744da4c7547dd8f0f9849b5
|
|
| MD5 |
616ee3f0abb4c3b224fe08d61f1252ea
|
|
| BLAKE2b-256 |
ab6b340dab4c4118d4f1a628996e706d832a88e6ec8a8ee6156601c3cf9190bf
|
Provenance
The following attestation bundles were made for fastmcp_extensions-0.20.0.tar.gz:
Publisher:
publish.yml on airbytehq/fastmcp-extensions
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fastmcp_extensions-0.20.0.tar.gz -
Subject digest:
2b365223cd379edd4192b15c878d81b4a7c3ed301744da4c7547dd8f0f9849b5 - Sigstore transparency entry: 2461634843
- Sigstore integration time:
-
Permalink:
airbytehq/fastmcp-extensions@aa7d372e67f5e28bda7568b40fa8427ab3a92439 -
Branch / Tag:
refs/tags/v0.20.0 - Owner: https://github.com/airbytehq
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@aa7d372e67f5e28bda7568b40fa8427ab3a92439 -
Trigger Event:
release
-
Statement type:
File details
Details for the file fastmcp_extensions-0.20.0-py3-none-any.whl.
File metadata
- Download URL: fastmcp_extensions-0.20.0-py3-none-any.whl
- Upload date:
- Size: 89.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9c3ce3dc08306d01d15a0f7abc382aa56a0bbf09326eee677f481c0a16702bb3
|
|
| MD5 |
7bb7cfa9f9029dea75ccadbfd3bd1971
|
|
| BLAKE2b-256 |
bb3ee821e955dc7d3696a868dd85d99aea06a2a80d8a5a2b155a17c02904904d
|
Provenance
The following attestation bundles were made for fastmcp_extensions-0.20.0-py3-none-any.whl:
Publisher:
publish.yml on airbytehq/fastmcp-extensions
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fastmcp_extensions-0.20.0-py3-none-any.whl -
Subject digest:
9c3ce3dc08306d01d15a0f7abc382aa56a0bbf09326eee677f481c0a16702bb3 - Sigstore transparency entry: 2461634850
- Sigstore integration time:
-
Permalink:
airbytehq/fastmcp-extensions@aa7d372e67f5e28bda7568b40fa8427ab3a92439 -
Branch / Tag:
refs/tags/v0.20.0 - Owner: https://github.com/airbytehq
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@aa7d372e67f5e28bda7568b40fa8427ab3a92439 -
Trigger Event:
release
-
Statement type: