🚀 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. - 🛡️ 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.
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. assert_http_trusted_execution_disabled- Fail fast when trusted execution is enabled for an HTTP entrypoint.
Documentation and HTTP Helpers
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.
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(domain, read_only, destructive, idempotent, open_world, extra_help_text)- Tag a tool for deferred registration@mcp_prompt(name, description, domain)- Tag a prompt for deferred registration@mcp_resource(uri, description, mime_type, domain)- Tag a resource for deferred 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
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).
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.16.0.tar.gz.
File metadata
- Download URL: fastmcp_extensions-0.16.0.tar.gz
- Upload date:
- Size: 224.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 |
05ab240b713525b46e201623963b937bade2588f41ca09e797f9301e469e8467
|
|
| MD5 |
a1822f918486c6cf44ad3b52a1d40968
|
|
| BLAKE2b-256 |
9ef046c5e096ed8d8add4c0ea48f99ba5967b2033fe05bc71afb9f88b7cf2606
|
Provenance
The following attestation bundles were made for fastmcp_extensions-0.16.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.16.0.tar.gz -
Subject digest:
05ab240b713525b46e201623963b937bade2588f41ca09e797f9301e469e8467 - Sigstore transparency entry: 2453353134
- Sigstore integration time:
-
Permalink:
airbytehq/fastmcp-extensions@b16611123a680214290ffcfdfcd761b63ba536e8 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/airbytehq
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b16611123a680214290ffcfdfcd761b63ba536e8 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file fastmcp_extensions-0.16.0-py3-none-any.whl.
File metadata
- Download URL: fastmcp_extensions-0.16.0-py3-none-any.whl
- Upload date:
- Size: 82.1 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 |
5cbbeebe4635ddd86c7317de68a0eb7b4978727d1926bc09691f89aa9bafafbe
|
|
| MD5 |
cea379580eeb5fd90e5a512e8871137f
|
|
| BLAKE2b-256 |
7ed1b4bfabb8beb10ed5e18bf7901a5da79d0888ff5f393820537f2c73ecefc0
|
Provenance
The following attestation bundles were made for fastmcp_extensions-0.16.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.16.0-py3-none-any.whl -
Subject digest:
5cbbeebe4635ddd86c7317de68a0eb7b4978727d1926bc09691f89aa9bafafbe - Sigstore transparency entry: 2453353166
- Sigstore integration time:
-
Permalink:
airbytehq/fastmcp-extensions@b16611123a680214290ffcfdfcd761b63ba536e8 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/airbytehq
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b16611123a680214290ffcfdfcd761b63ba536e8 -
Trigger Event:
workflow_dispatch
-
Statement type: