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.


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.3.tar.gz (11.8 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.3-py3-none-any.whl (10.4 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: mcp_auth_middleware-0.1.3.tar.gz
  • Upload date:
  • Size: 11.8 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.3.tar.gz
Algorithm Hash digest
SHA256 c8036f2bebe66450fea6b552e0b669aea5dbbdbb17d6036bdfc7d5c57f9e601d
MD5 325c102be0bb9157be7e08f9336f7b2d
BLAKE2b-256 32c9f9890d3a39ee5664e9f60fd618fd461134646b7320cdabc65e93a79eb270

See more details on using hashes here.

Provenance

The following attestation bundles were made for mcp_auth_middleware-0.1.3.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.3-py3-none-any.whl.

File metadata

File hashes

Hashes for mcp_auth_middleware-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 4d453059935d9d4a8cc8031ef00867a1a410065b5d6bc98303ac217eacd750b1
MD5 383e13161b8235e562374bc9d8b7a318
BLAKE2b-256 406655e037f07133290d94ea0a49a9faa390032ee7126bd1f9716c0cf8ece1d7

See more details on using hashes here.

Provenance

The following attestation bundles were made for mcp_auth_middleware-0.1.3-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