Skip to main content

JWE authentication middleware for MCP/Starlette applications

Project description

mcp-auth-middleware

JWE authentication middleware for HTTP MCP servers. Encrypt user data end-to-end so that only your MCP server can read it.

What it does

mcp-auth-middleware gives your MCP server two things:

  1. A middleware that intercepts incoming requests, decrypts a JWE Bearer token, and makes the authenticated user's claims available via get_user().
  2. A CLI (mcp-auth-middleware) that generates RSA key pairs in JWKS format, outputs Kubernetes Secret YAML, and securely deletes local keys when you're done.

Installation

pip install mcp-auth-middleware

This installs the library and the mcp-auth-middleware CLI.


Testing

python -m pip install -r requirements.txt
python -m pip install -e .
pytest --cov=mcp_auth_middleware --cov-report=term-missing --cov-fail-under=80

To generate an HTML coverage report:

pytest --cov=mcp_auth_middleware --cov-report=html

The report is written to htmlcov/index.html.


Quick start (local development)

1. Generate keys

mcp-auth-middleware generate

Output:

Keys generated (JWKS format):
  Private: .keys/mcp-private.json
  Public:  .keys/mcp-public.json

Add .keys/ to your .gitignore immediately.

2. Set the environment variable

Create a .env file (or export directly):

MCP_KEY_FILE_PATH=.keys/mcp-private.json

3. Add the middleware to your MCP server

Works with any Starlette-based MCP server:

from fastmcp import FastMCP
from mcp_auth_middleware import JWKSAuthMiddleware, get_user
import uvicorn

mcp = FastMCP("My Server")

# Register your tools on `mcp` as usual …

app = mcp.http_app()
app.add_middleware(JWKSAuthMiddleware)

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

Or with the official MCP Python SDK directly:

from mcp.server.fastmcp import FastMCP
from mcp_auth_middleware import JWKSAuthMiddleware, get_user
import uvicorn

mcp = FastMCP("My Server")

app = mcp.http_app()
app.add_middleware(JWKSAuthMiddleware)

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

4. Access user claims inside any tool

@mcp.tool()
def whoami() -> str:
    user = get_user()
    return f"Hello, {user.name or 'anonymous'}!"

get_user() returns an AuthUser dict. Access claims as attributes — missing keys return None instead of raising.


Kubernetes deployment

1. Generate keys and pipe straight into kubectl

mcp-auth-middleware k8s | kubectl apply -f -

This creates a Kubernetes Secret named mcp-server-keys in the default namespace. Customise with flags:

mcp-auth-middleware k8s --namespace my-ns --secret-name my-mcp-keys | kubectl apply -f -

2. Clean up local key material

mcp-auth-middleware clean

Uses shred (Linux) for secure deletion when available; falls back to overwrite-then-delete.

3. Mount the secret in your deployment

Add the following snippets to your existing Deployment YAML.

Volume definition (under spec.template.spec.volumes):

- name: mcp-secret-volume
  secret:
    secretName: mcp-server-keys   # must match --secret-name
    defaultMode: 0400
    items:
      - key: mcp_jwks
        path: key.json

Volume mount (under your container's volumeMounts):

- mountPath: /etc/mcp/secrets
  name: mcp-secret-volume
  readOnly: true

Environment variable (under env):

- name: MCP_KEY_FILE_PATH
  value: "/etc/mcp/secrets/key.json"

A full example Deployment + Service is in examples/k8s-deployment.yaml.


CLI reference

All commands accept -o / --output <dir> to change the key directory (default: .keys).

Command Description
mcp-auth-middleware generate Generate an RSA-4096 JWKS key pair
mcp-auth-middleware k8s Generate keys and print a Kubernetes Secret YAML to stdout
mcp-auth-middleware clean Securely delete keys from the output directory

mcp-auth-middleware k8s flags

Flag Default Description
-n, --namespace default Kubernetes namespace
-s, --secret-name mcp-server-keys Name of the K8s Secret

API reference

JWKSAuthMiddleware

Starlette middleware. Attach it to any HTTP-based MCP server app:

app.add_middleware(
    JWKSAuthMiddleware,
    verifier=None,        # optional: provide your own JWETokenVerifier
    jwks_path="/.well-known/jwks.json",  # public-key endpoint path
)

The middleware automatically serves your server's public JWKS at the configured path so that token producers can discover your encryption key.

get_user() -> AuthUser

Returns the authenticated user's claims for the current request. Safe to call from any async context (tools, routes, dependencies) during request handling.

user = get_user()
user.email   # claim value or None
user["role"]  # also works as a regular dict

JWETokenVerifier

Lower-level class if you need to verify tokens outside the middleware:

from mcp_auth_middleware import JWETokenVerifier

verifier = JWETokenVerifier()          # reads MCP_KEY_FILE_PATH
claims = await verifier.verify_token(token_string)
public_jwks = verifier.get_jwks()       # for publishing

Environment variables

Variable Required Description
MCP_KEY_FILE_PATH Yes Path to the private JWKS JSON file

How the token flow works

Producer (client/gateway)             MCP Server
――――――――――――――――――――――――――             ――――――――――
                                      GET /.well-known/jwks.json
                              ◄────   { public RSA key }
Encrypt claims with public key
POST /mcp/tool  ──────────────────────►
  Authorization: Bearer <JWE>
                                      Middleware decrypts JWE
                                      with private key
                                      ─► get_user() returns claims
  1. The token producer fetches the server's public key from /.well-known/jwks.json.
  2. It encrypts a JSON payload (user claims) into a JWE token using that public key.
  3. The token is sent as a Bearer token in the Authorization header.
  4. The middleware decrypts it with the private key and exposes claims via get_user().


Accessing the JWKS endpoint from a browser

If your token producer is a browser-based application that needs to fetch the public key from /.well-known/jwks.json, you must add CORS middleware after JWKSAuthMiddleware. Starlette applies middleware in reverse registration order, so the last one added runs first on incoming requests — this ensures CORS headers are set before auth is checked.

from mcp_auth_middleware import JWKSAuthMiddleware
from starlette.middleware.cors import CORSMiddleware

app = mcp.http_app()
app.add_middleware(JWKSAuthMiddleware)
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # restrict to your domain(s) in production 
)

Without this, browsers will block the cross-origin request to the JWKS endpoint due to the same-origin policy.

Project structure

mcp-auth-middleware/
├── pyproject.toml
├── README.md
├── LICENSE
├── CHANGELOG.md
├── requirements.txt
├── requirements-dev.txt
├── MANIFEST.in
├── .gitignore
├── mcp_auth_middleware/
│   ├── __init__.py
│   ├── py.typed
│   ├── cli.py
│   ├── middleware.py
│   └── verifier.py
├── tests/
└── examples/
    ├── server.py
    └── k8s-deployment.yaml

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mcp_auth_middleware-0.1.4.tar.gz (14.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mcp_auth_middleware-0.1.4-py3-none-any.whl (10.6 kB view details)

Uploaded Python 3

File details

Details for the file mcp_auth_middleware-0.1.4.tar.gz.

File metadata

  • Download URL: mcp_auth_middleware-0.1.4.tar.gz
  • Upload date:
  • Size: 14.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for mcp_auth_middleware-0.1.4.tar.gz
Algorithm Hash digest
SHA256 9471e8aa141976ee3184890f9bcb089ca248c4d48061f6a10656f7572437d92b
MD5 89b5ec52c75419af21db133852b6f52e
BLAKE2b-256 bb60dd1927d56b257dc5fb264be8522bdba52d75ec88ac3e31c35a788a28d9c2

See more details on using hashes here.

Provenance

The following attestation bundles were made for mcp_auth_middleware-0.1.4.tar.gz:

Publisher: publish.yml on fhswf/mcp-auth-middleware

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file mcp_auth_middleware-0.1.4-py3-none-any.whl.

File metadata

File hashes

Hashes for mcp_auth_middleware-0.1.4-py3-none-any.whl
Algorithm Hash digest
SHA256 94cb6666234abd7e5958f7e4b888d64fd30f4d2d420aacbc8f79a7f5d8f9aa94
MD5 1c73c03f3fb7c2183c2d7bdb1db7438f
BLAKE2b-256 7cf62d5ef78b2cc44c2b1d7fa3d4dd9f1f9fc1bbc4da132a61cc093cc9692170

See more details on using hashes here.

Provenance

The following attestation bundles were made for mcp_auth_middleware-0.1.4-py3-none-any.whl:

Publisher: publish.yml on fhswf/mcp-auth-middleware

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page