Skip to main content

fastmcp-credentials

Secure credential injection middleware for FastMCP servers.

Keeps secrets out of the LLM. Credentials are resolved server-side and injected into tools transparently, so the agent never sees tokens, API keys, or client secrets.


How it works

  1. CredentialMiddleware intercepts every tool call and resolves credentials via the configured backend.
  2. Credentials are stored in a request-scoped ContextVar, so they never leak between concurrent requests.
  3. Your tool calls get_credentials() to read them. It's a plain synchronous function, no await, no ctx.
  4. After the tool returns (or raises), the ContextVar is always reset in a finally block.

The LLM only ever sees your tool's business parameters, never the credentials.


Installation

pip install fastmcp-credentials

Requires Python 3.11+ and fastmcp==4.0.5 (pinned exactly, see below).

This package targets FastMCP 4 only, built on MCP Python SDK v2 and the modern, stateless 2026-07-28 protocol. There's no initialize handshake and no Mcp-Session-Id. fastmcp<4.0.0 and the older handshake-era protocol are not supported.

fastmcp is pinned to an exact version, not a range (fastmcp==4.0.5, never >= or ~=). Every server in the MewCP fleet depends on this package, so they all need to resolve to the same, tested FastMCP version. A range here would let a future FastMCP release get picked up silently on the next unrelated redeploy. To move to a newer FastMCP patch, bump this pin and cut a new release instead of loosening it into a range.


Backends

Backend Best for
EnvCredentialBackend Local development, self-hosted single-user servers
HeaderCredentialBackend Gateway-managed multi-user deployments

Quick start: static credentials (env vars)

Static credentials are arbitrary key/value fields loaded from environment variables. All fields are available on cred.fields.

# Option 1: JSON object (recommended for multi-field providers)
export MYSERVICE_FIELDS='{"apiKey":"sk-abc123","secretKey":"xyz789"}'

# Option 2: individual FIELD_<name> vars (useful with secrets managers)
export MYSERVICE_FIELD_apiKey=sk-abc123
export MYSERVICE_FIELD_secretKey=xyz789
import requests
from fastmcp import FastMCP
from fastmcp_credentials import CredentialMiddleware, EnvCredentialBackend, get_credentials

backend = EnvCredentialBackend(prefix="MYSERVICE_")
mcp = FastMCP("My Service", middleware=[CredentialMiddleware(backend, "static")])

@mcp.tool()
def search(query: str) -> list:
    creds = get_credentials()
    response = requests.get(
        "https://api.myservice.com/search",
        headers={"Authorization": f"Bearer {creds.fields['apiKey']}"},
        params={"q": query},
    )
    return response.json()

Quick start: OAuth (env vars)

For OAuth tokens, set {PREFIX}CRED_TYPE=oauth:

export MYSERVICE_ACCESS_TOKEN=ya29...
export MYSERVICE_REFRESH_TOKEN=1//...
export MYSERVICE_CLIENT_ID=your_client_id
export MYSERVICE_CLIENT_SECRET=your_client_secret
export MYSERVICE_TOKEN_URI=https://auth.myservice.com/token
export MYSERVICE_SCOPES=read write
backend = EnvCredentialBackend(prefix="MYSERVICE_")
mcp = FastMCP("My Service", middleware=[CredentialMiddleware(backend, "oauth")])

@mcp.tool()
def list_items(folder_id: str) -> list:
    creds = get_credentials()
    response = requests.get(
        "https://api.myservice.com/items",
        headers={"Authorization": f"Bearer {creds.access_token}"},
        params={"folder": folder_id},
    )
    return response.json()

Quick start: gateway-injected credentials (hosted mode)

For multi-user deployments where a gateway decrypts, refreshes, and injects credentials as HTTP headers before forwarding requests to your MCP server:

import requests
from fastmcp import FastMCP
from fastmcp_credentials import CredentialMiddleware, HeaderCredentialBackend, get_credentials

backend = HeaderCredentialBackend()
mcp = FastMCP("My Service", middleware=[CredentialMiddleware(backend, "oauth")])

@mcp.tool()
def call_api(resource_id: str) -> dict:
    creds = get_credentials()
    return requests.get(
        f"https://api.example.com/resources/{resource_id}",
        headers={"Authorization": f"Bearer {creds.access_token}"},
    ).json()

The gateway sends these headers directly. No tool parameters, no LLM involvement:

X-MCP-Cred-Access-Token: ya29...
X-MCP-Cred-Fields: {"apiKey":"sk-...","secretKey":"..."}
X-MCP-Cred-Scopes: read write
X-MCP-Cred-Extra: {"tenant_id": "..."}
X-MCP-Cred-Expires-At: 2026-05-04T12:00:00Z

Tools access credentials identically to env-based mode via get_credentials().


OAuth extras

Some OAuth providers include non-sensitive metadata alongside the token, like a data-centre region or a workspace identifier. These are collected into cred.extra for OAuth credentials only.

Env vars: use the {PREFIX}EXTRA_{NAME} pattern.

export MYSERVICE_CRED_TYPE=oauth
export MYSERVICE_ACCESS_TOKEN=ya29...
export MYSERVICE_EXTRA_DC=us10
export MYSERVICE_EXTRA_WORKSPACE=my-workspace
@mcp.tool()
def call_api() -> dict:
    creds = get_credentials()
    base_url = f"https://{creds.extra['dc']}.api.example.com"
    return requests.get(base_url, headers={"Authorization": f"Bearer {creds.access_token}"}).json()

Gateway mode: the gateway encodes extras in the X-MCP-Cred-Extra header as a JSON object.


Selecting a backend based on deployment mode

If you need to switch backends at runtime (e.g. env vars locally, header-injected in production), use the get_mode() helper which reads the FASTMCP_CREDENTIAL_MODE environment variable:

from fastmcp_credentials import CredentialMiddleware, EnvCredentialBackend, HeaderCredentialBackend, get_mode, CredentialMode

if get_mode() == CredentialMode.HOSTED:
    backend = HeaderCredentialBackend()
else:
    backend = EnvCredentialBackend(prefix="MYSERVICE_")

mcp = FastMCP("My Service", middleware=[CredentialMiddleware(backend, "oauth")])
# Local / self-hosted (default, no env var needed)
# FASTMCP_CREDENTIAL_MODE=oss

# Production behind a gateway
export FASTMCP_CREDENTIAL_MODE=hosted

The ResolvedCredential object

get_credentials() always returns a ResolvedCredential dataclass, regardless of which backend is used:

@dataclass
class ResolvedCredential:
    type: Literal["static", "oauth"]

    # Static auth: all provider fields by name
    fields: dict[str, str]

    # OAuth
    access_token: str | None
    refresh_token: str | None
    client_id: str | None
    client_secret: str | None
    token_uri: str | None
    scopes: list[str] | None
    expires_at: datetime | None

    # OAuth metadata only (e.g. dc, workspace). Empty for static credentials.
    extra: dict[str, Any]

    def is_expired(self) -> bool: ...

is_expired() returns True if the access token has expired or expires within the next 60 seconds.


Environment variable reference

All variables use the prefix you pass to EnvCredentialBackend(prefix="...").

Variable Default Description
{PREFIX}FIELDS none JSON object with all static fields, e.g. {"apiKey":"...","secretKey":"..."}
{PREFIX}FIELD_{NAME} none Individual static field (key name preserved as-is) → cred.fields["NAME"]
{PREFIX}EXTRA_{NAME} none OAuth metadata only → cred.extra["name"]
{PREFIX}ACCESS_TOKEN none OAuth access token
{PREFIX}REFRESH_TOKEN none OAuth refresh token
{PREFIX}CLIENT_ID none OAuth client identifier
{PREFIX}CLIENT_SECRET none OAuth client secret
{PREFIX}TOKEN_URI none Token refresh endpoint URL
{PREFIX}SCOPES none Space-separated OAuth scopes
{PREFIX}EXPIRES_AT none ISO 8601 token expiry (e.g. 2026-05-04T12:00:00+00:00)

{PREFIX}FIELDS takes priority over individual {PREFIX}FIELD_{NAME} vars when both are set.


Header reference (gateway-injected mode)

When using HeaderCredentialBackend, the gateway injects these headers. At least one of the first two must be present.

Header Required for Description
X-MCP-Cred-Access-Token "oauth" type OAuth access token
X-MCP-Cred-Fields "static" type JSON object with all static credential fields
X-MCP-Cred-Scopes No Space-separated string of OAuth scopes
X-MCP-Cred-Extra No JSON object with OAuth provider metadata
X-MCP-Cred-Expires-At No Token expiry as ISO 8601 UTC timestamp

The required header depends on the credential type configured in CredentialMiddleware. If the type-appropriate header is missing, a MissingCredentialHeaderError is raised.


Running the tests

Clone the repo and install with the dev extras:

git clone https://github.com/AStheTECH/fastmcp-credentials.git
cd fastmcp-credentials
pip install -e ".[dev]"

Run the full suite:

python -m pytest

Run a specific file or test:

python -m pytest tests/backends/test_env.py
python -m pytest tests/backends/test_headers.py::test_parse_scopes

Run with verbose output:

python -m pytest -v

License

Apache-2.0

Release files for fastmcp-credentials 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for fastmcp-credentials 0.2.0
File Size Uploaded
fastmcp_credentials-0.2.0.tar.gz 26.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for fastmcp-credentials 0.2.0
File Interpreter ABI Platform
fastmcp_credentials-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 44.2 kB

Release files / fastmcp_credentials-0.2.0.tar.gz

Download URL fastmcp_credentials-0.2.0.tar.gz
Size 26.5 kB
Tags Source
SHA-256 checksum
How to use checksums
c9c0019995c073369a7d8ed15b8338ccde4355d2e65e38ad1ac602c6a34a07bf
BLAKE2b-256 checksum
How to use checksums
b7111c8215b34eeba5112c9b923ee324d87d732b759088f2a7cc318ae630e75b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 18, 2026.

Transparency log

Release files / fastmcp_credentials-0.2.0-py3-none-any.whl

Download URL fastmcp_credentials-0.2.0-py3-none-any.whl
Size 17.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2bc6bb57ded3f5ee07fc2da6c7f7b6ce3939af21ee918ef4ef1cb7a17bae10a2
BLAKE2b-256 checksum
How to use checksums
f4d07c5fc9cc526f7c02e20512ba5f9bc78f6493cda2560a70ccb0d1d971c46f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 18, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page