Skip to main content

Authsome MCP Proxy

A Model Context Protocol (MCP) proxy that bridges upstream MCP servers protected by token validation or static credentials to MCP clients such as Claude Desktop, Claude Code, Cursor, Codex, MCP Inspector, and Claude.ai. ("Authsome" as in awesome, but with auth.)

The proxy can run as:

What Is This For?

Use authsome-mcp-proxy when you need to connect MCP clients to an upstream MCP server that:

  • Validates inbound bearer tokens against an external IdP but doesn't run its own OAuth authorization server, so MCP clients see no auth metadata and skip authentication entirely (resulting in 401 Unauthorized on every tool call); or

  • Authenticates callers via static credentials (API keys, API tokens, personal access tokens, OAuth client_credentials) while your MCP client is web-based — Claude.ai, for example. Such clients accept only a connector URL in their config and expect the server they point at to be — or pretend to be — a standard OAuth/OIDC authorization server, which an upstream behind raw static credentials isn't. The proxy terminates OIDC inbound and injects the static credential outbound.

The proxy fills these gaps by:

  1. Inbound — terminating the standard MCP auth handshake against an IdP the operator configures (Keycloak, OIDC, AWS Cognito, Google, Azure), so downstream MCP clients can perform Dynamic Client Registration and the OAuth code flow without modifying the upstream; and

  2. Outbound — forwarding the user's session token to the upstream (the default; works when the upstream and the proxy validate against the same IdP), or substituting a configured static header / service-account token if the upstream uses a separate credential mechanism. See Advanced: Outbound Credential Substitution for the latter case.

The proxy presupposes the upstream is itself an MCP-native server (one that exposes the MCP protocol — typically a web connector backed by a REST API in the same codebase). If your upstream is a bare REST API without MCP, build a full FastMCP server with @mcp.tool()-decorated handlers instead — this proxy can't synthesize tool definitions for you.

Prerequisites

This tool requires uvx (part of uv). Install it via:

# Windows
winget install --id=astral-sh.uv -e

# macOS
brew install uv

# Linux
curl -LsSf https://astral.sh/uv/install.sh | sh

See the uv installation guide for more options.

Web Connector (Recommended)

Run the proxy as a persistent HTTP server. Downstream MCP clients connect by URL (e.g. https://mcp.example.com/mcp). A single instance serves many users concurrently; each MCP session gets an isolated upstream connection so users never see each other's traffic.

How it works

Browser           MCP Client (Claude.ai/Code/Inspector)
   │                       │
   │  OAuth login          │  1. DCR + auth-code + token
   │ ◄────────────────────►│ ◄──────────────────────────────┐
   │                       │                                │
   │                       │  2. tools, prompts, resources  │
   │                       │     (Authorization: Bearer P,  │
   │                       ▼      issued by the proxy)      │
   │            ┌─────────────────────┐                     │
   │            │  authsome-mcp-proxy │  looks up the IdP   │
   │            │  --transport http   │  token U behind P   │
   │            └─────────────────────┘  in its store       │
   │                       │                                │
   │                       │  3. Authorization: Bearer U    │
   │                       ▼   (forwarded)                  │
   │            ┌─────────────────────┐                     │
   │            │   Upstream MCP      │                     │
   │            │   server            │                     │
   │            └─────────────────────┘                     │
   │                                                        │
   └─── Upstream MCP validates U against the same IdP ──────┘

The proxy wears two OAuth 2.0 hats at the same time: it is an OAuth 2.0 client against the upstream IdP / Authorization Server, and a OAuth 2.0 Authorization Server for the MCP client, which may identify itself either by Dynamic Client Registration or by CIMD URL. The MCP client never sees the upstream IdP's tokens: the proxy keeps them in its store and issues its own signed tokens that refer to them. On every request it swaps the client's token for the upstream one, which is what OUTBOUND_AUTH=forward sends on. For the full sequence diagram, persistence model, and pod-restart implications, see docs/oauth-flow.md; for keeping sessions across restarts, see Surviving restarts.

The proxy is IdP-agnostic — pick the inbound provider that matches your deployment via --inbound-auth-provider. The following sections give a working example for each.

Keycloak

For modern Keycloak (≥ 26.6.0) the proxy uses KeycloakAuthProvider, a RemoteAuthProvider. Keycloak supports MCP-compatible Dynamic Client Registration natively, so the proxy holds no IdP credentials of its own — downstream MCP clients DCR directly against Keycloak. The proxy's job reduces to JWT verification + protected-resource metadata advertising.

uvx authsome-mcp-proxy \
  --transport http \
  --port 8000 \
  --proxy-base-url https://mcp.example.com \
  --inbound-auth-provider keycloak \
  --oidc-issuer-url https://keycloak.example.com/realms/myrealm \
  --oidc-scopes "openid email" \
  --audience mcp-server \
  https://mcp-upstream.example.com/mcp

For Keycloak < 26.6.0, use --inbound-auth-provider oidc instead — older Keycloak versions don't support MCP-compatible DCR natively, so the DCR-bridge mode of OIDCProxy is required.

Generic OIDC

Use this for any OIDC-compliant IdP that doesn't support MCP-compatible DCR natively (older Keycloak, Auth0, Okta, etc.). The proxy itself holds a pre-registered static client with the IdP and bridges downstream DCR requests to the IdP's static-client model.

uvx authsome-mcp-proxy \
  --transport http \
  --port 8000 \
  --proxy-base-url https://mcp.example.com \
  --inbound-auth-provider oidc \
  --oidc-issuer-url https://idp.example.com \
  --oidc-client-id mcp-proxy-client \
  --oidc-client-secret <secret> \
  --oidc-scopes "openid email profile" \
  --audience mcp-server \
  https://mcp-upstream.example.com/mcp

The OIDC discovery URL is derived as {oidc-issuer-url}/.well-known/openid-configuration. Register the proxy's {proxy-base-url}/auth/callback as an allowed redirect URI in your IdP's client config.

AWS Cognito

uvx authsome-mcp-proxy \
  --transport http \
  --port 8000 \
  --proxy-base-url https://mcp.example.com \
  --inbound-auth-provider aws-cognito \
  --cognito-user-pool-id eu-central-1_XXXXXXXXX \
  --cognito-aws-region eu-central-1 \
  --oidc-client-id <cognito-app-client-id> \
  --oidc-client-secret <cognito-app-client-secret> \
  --oidc-scopes openid \
  https://mcp-upstream.example.com/mcp

Cognito requires a confidential app client (one with "Generate a client secret" enabled) — FastMCP's AWSCognitoProvider mandates a client_secret. Register {proxy-base-url}/auth/callback as an allowed callback URL in the app client configuration. Cognito issues refresh tokens automatically when "Authorization code grant" is enabled — no offline_access scope needed.

Google

uvx authsome-mcp-proxy \
  --transport http \
  --port 8000 \
  --proxy-base-url https://mcp.example.com \
  --inbound-auth-provider google \
  --oidc-client-id <google-oauth-client-id>.apps.googleusercontent.com \
  --oidc-client-secret <google-oauth-client-secret> \
  --oidc-scopes "openid email profile" \
  https://mcp-upstream.example.com/mcp

Create the OAuth client via the Google Cloud Console. Add {proxy-base-url}/auth/callback to the client's authorized redirect URIs.

Azure (Entra ID)

uvx authsome-mcp-proxy \
  --transport http \
  --port 8000 \
  --proxy-base-url https://mcp.example.com \
  --inbound-auth-provider azure \
  --azure-tenant-id <tenant-uuid> \
  --azure-identifier-uri "api://mcp-server" \
  --oidc-client-id <azure-app-client-id> \
  --oidc-client-secret <azure-app-client-secret> \
  --oidc-scopes "User.Read" \
  https://mcp-upstream.example.com/mcp

Azure has two specifics worth knowing: --oidc-scopes are taken literally and prefixed with --azure-identifier-uri automatically before being sent to Azure (api://mcp-server/User.Read), and the tenant ID is required because the issuer URL embeds it.

Connecting downstream MCP clients

Once the proxy is running on https://mcp.example.com:8000/mcp:

Claude.ai: in Settings → Connectors → Add custom connector, paste the URL. Claude will perform DCR against the proxy and walk the user through the OAuth flow in the browser.

Claude Code:

claude mcp add --transport http my-server https://mcp.example.com:8000/mcp

MCP Inspector:

npx -y @modelcontextprotocol/inspector

…then in the UI: set Transport Type to Streamable HTTP, paste https://mcp.example.com:8000/mcp into URL, and — importantly — set Connection Type to Via Proxy (the default Direct mode bypasses Inspector's local OAuth helper and breaks the authorization flow against the proxy). Then click Connect.

Client ID Metadata Documents (CIMD)

DCR isn't the only way a downstream client can identify itself. With Client ID Metadata Documents (adopted into MCP as SEP-991) a client hosts a small JSON document at an HTTPS URL it controls, and that URL is its client_id. The proxy fetches the document on first use instead of handing out a freshly registered client.

Two things follow from that. The client keeps one durable identity across every server it talks to, rather than a different client_id per proxy instance. And because the metadata lives at the client's own URL, the proxy can re-fetch it after a restart — where a DCR registration that lived only in the proxy's client store is simply gone. The client's session is not covered by that: its tokens live in the proxy's store like everyone else's, see Surviving restarts.

CIMD is enabled by default and is purely additive: DCR keeps working exactly as before, so nothing changes for clients that don't use CIMD. Turn it off with --no-enable-cimd (or ENABLE_CIMD=false).

Support depends on the inbound provider, because it depends on who is acting as the authorization server:

--inbound-auth-provider CIMD
oidc, google, azure, aws-cognito ✅ the proxy is the authorization server, and handles CIMD itself
keycloak ⚠️ not the proxy's call — clients register directly with Keycloak, so CIMD works only if your Keycloak does. ENABLE_CIMD is inert here.

To try it, generate and check a document with FastMCP's CLI:

uvx fastmcp cimd create --name "My Client" -r "http://localhost:*/callback"
# host the result at e.g. https://client.example.com/oauth/client.json
uvx fastmcp cimd validate https://client.example.com/oauth/client.json

…then connect with that URL as the client id. A successful CIMD connection reaches /authorize with no /register call ahead of it.

Identity advertised to downstream clients

In stdio mode the proxy relays the upstream MCP server's identity (name, version, instructions, website URL, icons) at startup, so the proxy appears transparent.

In web mode the proxy cannot read the upstream at startup: with --outbound-auth forward the upstream only accepts a user's token, and no user has connected yet. So the identity is split:

  • Instructions, version, website URL and icons are relayed from the upstream when a client connects. The first handshake fetches them with that client's credential; the result is cached for 10 minutes, so a redeployed upstream is picked up without restarting the proxy. If the fetch fails, the client still connects and sees the proxy's own values. If the upstream cannot be reached and you set no version, FastMCP's own version is shown.
  • The name is yours to set. FastMCP also uses the server name outside the handshake, where a relayed value would not reach it. Unset, it falls back to FastMCP's auto-generated FastMCPProxy-xxxx.

Any field you set explicitly wins over the relayed one:

uvx authsome-mcp-proxy ... \
  --proxy-name "Awesome tool" \
  --proxy-version 1.2.3 \
  --proxy-instructions "Use this tool to do amazing things." \
  --proxy-website-url https://awesome.tool.example.com

Serving several hostnames from one deployment

Sometimes the same proxy has to be reachable under more than one public hostname — an alias domain for customers whose networks block your primary TLD, a vanity host, or both names running side by side during a migration. Pass them as a comma-separated list:

uvx authsome-mcp-proxy ...   --proxy-base-url https://mcp.example.io,https://mcp.example.com

The first entry is canonical: it is the identity served to requests that arrive with no Host header or an unrecognized one, which is what container health probes addressing the pod IP do.

Each hostname is served as a fully separate OAuth identity — its own protected-resource metadata, its own authorization-server metadata, its own redirect_uri. A client that reaches mcp.example.com is never handed mcp.example.io to continue its flow with, which is the entire point when the alias exists because the primary domain is unreachable.

You will need to register {base_url}/auth/callback for every entry as an allowed redirect URI with your IdP. Two entries sharing a hostname (differing only in scheme, port or path) are rejected at startup, since the Host header cannot tell them apart.

Surviving restarts

As the authorization server for its MCP clients (every provider except keycloak), the proxy keeps state: the clients registered through DCR, the upstream IdP's access and refresh tokens, and the mapping from the tokens it issued to them. It signs those tokens with a signing key. Both decide what a restart costs:

  • Store. By default the state lives in an encrypted file store under ${FASTMCP_HOME}/oauth-proxy/ (/app/.local/share/fastmcp/oauth-proxy/ in the published image). On a VM with a normal disk that is durable. In a container whose filesystem does not survive a restart, such as a Kubernetes Deployment, every restart loses all registrations and signs every user out — CIMD clients included, which keep only their registration. A volume at that path helps, but turns the proxy into a single-replica StatefulSet.
  • Signing key. Unset, the proxy derives the signing key — and from it the store's encryption key and directory — from the upstream client secret (OIDC_CLIENT_SECRET). Rotating that secret then invalidates every issued token and strands everything stored, volume or not.

A restart-safe deployment therefore sets both a signing key and an external store backend, shared by every replica:

uvx 'authsome-mcp-proxy[dynamodb]' ... \
  --jwt-signing-key "$(openssl rand -base64 32)" \
  --store-backend dynamodb \
  --store-dynamodb-table authsome-mcp-proxy-oauth-state \
  --store-dynamodb-region eu-central-1

Keep the signing key in a secret store and stable across deployments — generating it at startup, as in this one-liner, would defeat the purpose. With an external backend the proxy refuses to start without one. The values written to an external store are Fernet-encrypted with a key derived from it, the same way FastMCP encrypts its own file store, so the table never holds an upstream token in plaintext. A JWT_SIGNING_KEY is worth setting with the default file store too: it decouples the stored state from the upstream client secret. It must be at least 32 characters; a shorter one is refused at startup. The startup log names the store and signing key in use. Only the stored values are encrypted: each entry's collection, key and expiry, among them client IDs and, while they are valid, authorization codes, are readable to whoever can read the table, so keep write access to it with the proxy alone. An entry that is not encrypted is ignored rather than trusted. The settings and the store come from fastmcp-seaworthy, so other FastMCP servers built on it read the same variables.

DynamoDB. The backend needs the dynamodb extra, which the published container image already includes. Credentials and region come from the AWS SDK's usual chain (IRSA or EKS Pod Identity, instance profile, AWS_* environment variables); AWS_ENDPOINT_URL_DYNAMODB points it at DynamoDB Local for testing. Create the table with your infrastructure code:

Property Value
Partition key collection (String)
Sort key key (String)
Time to live enabled on the attribute ttl
Billing mode any; on-demand fits the access pattern

The proxy needs dynamodb:DescribeTable, dynamodb:DescribeTimeToLive, dynamodb:GetItem, dynamodb:PutItem and dynamodb:DeleteItem on the table. It checks the table at startup and refuses to start, naming the table, if it is missing. --store-dynamodb-auto-create makes it create a missing table instead (on-demand, TTL enabled); that additionally needs dynamodb:CreateTable and dynamodb:UpdateTimeToLive, and is off by default so the proxy can run with the narrower permissions. Without TTL enabled on the table, the proxy also needs dynamodb:UpdateTimeToLive to enable it at startup.

All of these settings are inert for --inbound-auth-provider keycloak, where Keycloak is the authorization server and the proxy keeps no OAuth state.

Local Stdio Proxy (Developer Use)

The original mode — the proxy is launched as a subprocess by the MCP client, performs the OAuth flow against an external OIDC IdP on the developer's behalf, caches tokens locally, and forwards them upstream.

Use stdio when:

  • You don't have a server to host the proxy on.
  • You're a developer who wants per-user authentication on their own machine rather than relying on a shared web deployment.
  • The upstream is a token-validating MCP server that issues no OAuth metadata of its own, and your MCP client (Claude Desktop, etc.) therefore needs a local OAuth proxy to authenticate against the IdP on its behalf.

Claude Desktop / Cursor / Codex

Add to your Claude Desktop config (accessible via Settings → Developer → Edit Config). Cursor and Codex use the same JSON format in their respective MCP settings files.

{
  "mcpServers": {
    "company-tools": {
      "command": "uvx",
      "args": [
        "authsome-mcp-proxy",
        "https://mcp-upstream.company.com/mcp"
      ],
      "env": {
        "OIDC_ISSUER_URL": "https://auth.company.com",
        "OIDC_CLIENT_ID": "claude-desktop-client",
        "OIDC_CLIENT_SECRET": "your-client-secret", // omit for public OIDC clients that don't require one
        "OIDC_SCOPES": "openid profile mcp:read mcp:write"
      }
    }
  }
}

Restart the client to apply changes. On first connection the proxy opens your browser for OIDC login; subsequent runs use the cached token.

To always use the latest version from PyPI (auto-updates), pin authsome-mcp-proxy@latest:

"args": ["authsome-mcp-proxy@latest", "https://mcp-upstream.company.com/mcp"]

Claude Code

claude mcp add --transport stdio my-server -- \
  uvx authsome-mcp-proxy \
  --oidc-issuer-url https://auth.example.com \
  --oidc-client-id my-client-id \
  https://mcp-upstream.example.com/mcp

Defaults to project scope (stored in .mcp.json). Add --scope user to make the server available across all your projects.

MCP Inspector

Create an mcp.json:

{
  "mcpServers": {
    "authsome-mcp-proxy": {
      "command": "uvx",
      "args": ["authsome-mcp-proxy", "https://mcp.example.com/mcp"],
      "env": {
        "OIDC_ISSUER_URL": "https://auth.example.com",
        "OIDC_CLIENT_ID": "inspector-client",
        "OIDC_CLIENT_SECRET": "your-client-secret" // omit for public OIDC clients that don't require one
      }
    }
  }
}

Then launch:

npx -y @modelcontextprotocol/inspector --config mcp.json --server authsome-mcp-proxy

Development from source

When developing or testing local changes:

{
  "mcpServers": {
    "local-dev": {
      "command": "uv",
      "args": [
        "run",
        "--with-editable",
        "/path/to/authsome-mcp-proxy",
        "authsome-mcp-proxy",
        "https://mcp.example.com/mcp"
      ],
      "env": {
        "OIDC_ISSUER_URL": "https://auth.example.com",
        "OIDC_CLIENT_ID": "dev-client",
        "OIDC_CLIENT_SECRET": "your-client-secret" // omit for public OIDC clients that don't require one
      }
    }
  }
}

Custom redirect port

If port 8080 is already in use, override the OAuth callback URL:

"env": {
  "OIDC_ISSUER_URL": "https://auth.example.com",
  "OIDC_CLIENT_ID": "my-client-id",
  "OIDC_CLIENT_SECRET": "your-client-secret", // omit for public OIDC clients that don't require one
  "OIDC_REDIRECT_URL": "http://localhost:9090/auth/callback"
}

…and add the chosen URL to your OIDC client's allowed redirect URIs.

Advanced: Outbound Credential Substitution

For upstream MCP servers that don't validate tokens against the same IdP that fronts the proxy. Two outbound mechanisms are supported, both configured via --outbound-auth. These cover single-tenant deployments where the upstream uses a tenant-wide service-account token or a per-user API key / API token; full multi-user identity-bridging with per-user credential storage is out of scope (see Caveats).

When to use this

You need outbound credential substitution if the upstream MCP server authenticates the caller via something other than a bearer token from your inbound IdP — for example a tenant-wide service-account token, a per-user API key (CoinDesk-style), or a flat API key (Lexware-style).

If the proxy and upstream share the same IdP, don't use this — the default --outbound-auth forward is what you want.

static mode (API key, API token, PAT)

Injects a literal header value on every outbound request. Use this when the upstream wants a fixed API key, API token, or PAT regardless of who the calling user is.

uvx authsome-mcp-proxy \
  --transport http \
  --port 8000 \
  --proxy-base-url https://mcp.example.com \
  --inbound-auth-provider keycloak \
  --oidc-issuer-url https://kc.example.com/realms/r \
  --outbound-auth static \
  --outbound-header-name Authorization \
  --outbound-header-value "Bearer ${UPSTREAM_API_KEY}" \
  https://mcp-upstream.example.com/mcp

For upstreams that take the key in a non-Authorization header:

  --outbound-header-name X-API-Key \
  --outbound-header-value "${UPSTREAM_API_KEY}" \

The scope of the credential you paste in determines the effective behaviour: a tenant-wide key terminates identity at the proxy (every user looks the same to the upstream), while a per-user key bridges that single user's identity through to the upstream (single-user instance).

Common shapes:

  • Personio-style tenant-scoped credentials (one shared service-account key for the whole org)
  • Lexware-style flat tenant API key (no per-user identity at the upstream)
  • CoinDesk-style per-user API token (the proxy holds the user's individual key)
  • GitLab / GitHub PATs (per-user; same shape as CoinDesk)

oauth-client-credentials mode (service-account OAuth)

Proxy obtains its own access token via the OAuth 2.0 client_credentials grant against an outbound token endpoint that is independent of the inbound IdP. The token is cached and refreshed on expiry.

uvx authsome-mcp-proxy \
  --transport http \
  --port 8000 \
  --proxy-base-url https://mcp.example.com \
  --inbound-auth-provider keycloak \
  --oidc-issuer-url https://kc.example.com/realms/r \
  --outbound-auth oauth-client-credentials \
  --outbound-token-url https://upstream-idp.example.com/oauth/token \
  --outbound-client-id "${UPSTREAM_SVC_CLIENT_ID}" \
  --outbound-client-secret "${UPSTREAM_SVC_CLIENT_SECRET}" \
  https://mcp-upstream.example.com/mcp

Equivalent to static with a tenant-scoped credential conceptually, but the token is short-lived and refreshed automatically — preferred over a flat shared API key when the upstream supports it.

Caveats

  • Single-tenant only. Identity is terminated at the proxy in tenant-scoped configurations — no ACL is enforced by the proxy itself, and every upstream call is indistinguishable to the upstream from any other authenticated user's. Tenant-scoped policy enforcement needs a separate PEP layer on top (the planned fastmcp-edge-authz); multi-user deployments with per-user credentials need a credential vault (the planned fastmcp-edge-byok).
  • The proxy presupposes the upstream is itself an MCP server. For a bare REST API, embed MCP tools directly into the API codebase (Spring AI MCP Server Boot Starter, Quarkus MCP Server, official Node/Python SDKs, etc.) rather than fronting it with this proxy. The proxy can't synthesize tool definitions from a REST API.

Configuration Reference

All options can be set via CLI flags or matching environment variables (CLI wins). The full list:

Environment variables and CLI flags

Upstream coordinates — required:

Env var CLI flag Description
UPSTREAM_MCP_URL (positional) URL of the upstream MCP server.

Proxy runtime — all optional:

Env var CLI flag Default Description
MCP_PROXY_TRANSPORT --transport {stdio,http} stdio Which transport to serve on.
MCP_PROXY_HOST --host 0.0.0.0 HTTP-mode bind address.
MCP_PROXY_PORT --port 8000 HTTP-mode listen port.
MCP_PROXY_BASE_URL --proxy-base-url required for http Publicly reachable URL of the proxy. Used to advertise auth metadata to downstream clients. Accepts a comma-separated list to serve several hostnames, each as its own OAuth identity — see Serving several hostnames from one deployment.
MCP_PROXY_DEBUG --debug off Enable debug logging.

Server identity advertised to downstream clients (http mode):

Env var CLI flag Description
MCP_PROXY_NAME --proxy-name Display name (e.g. "Acme Analytics").
MCP_PROXY_VERSION --proxy-version Display version. Optional override: unset, the upstream's version is relayed.
MCP_PROXY_INSTRUCTIONS --proxy-instructions Tool-selection hints for the LLM. Optional override: unset, the upstream's instructions are relayed.
MCP_PROXY_WEBSITE_URL --proxy-website-url Project URL shown in client UIs. Optional override: unset, the upstream's website URL is relayed.

Inbound auth (http mode — pick a provider, then supply the fields it needs):

Env var CLI flag Description
INBOUND_AUTH_PROVIDER --inbound-auth-provider {oidc,keycloak,aws-cognito,google,azure} Which provider class to use.
OIDC_ISSUER_URL --oidc-issuer-url OIDC issuer / Keycloak realm URL.
OIDC_CLIENT_ID --oidc-client-id OAuth client ID at the IdP.
OIDC_CLIENT_SECRET --oidc-client-secret OAuth client secret (required for aws-cognito; optional for oidc/google/azure; unused for keycloak).
OIDC_SCOPES --oidc-scopes Space-separated scopes.
AUDIENCE --audience JWT aud claim to require (oidc/keycloak; recommended for production).
COGNITO_USER_POOL_ID --cognito-user-pool-id AWS Cognito user pool ID.
COGNITO_AWS_REGION --cognito-aws-region AWS region for the Cognito user pool.
AZURE_TENANT_ID --azure-tenant-id Azure AD tenant ID.
AZURE_IDENTIFIER_URI --azure-identifier-uri Azure Application ID URI used for scope prefixing.
ENABLE_CIMD --enable-cimd / --no-enable-cimd Accept Client ID Metadata Documents from downstream clients. On by default — see Client ID Metadata Documents (CIMD).

OAuth proxy state (http mode, inert for keycloak — see Surviving restarts):

Env var CLI flag Default Description
JWT_SIGNING_KEY --jwt-signing-key derived from OIDC_CLIENT_SECRET Signs the tokens the proxy issues and keys its store's encryption. At least 32 characters. Required with a STORE_BACKEND other than file.
STORE_BACKEND --store-backend {file,dynamodb} file Where client registrations and sessions are kept. dynamodb needs the dynamodb extra.
STORE_DYNAMODB_TABLE --store-dynamodb-table required for dynamodb DynamoDB table name.
STORE_DYNAMODB_REGION --store-dynamodb-region AWS SDK default AWS region of the table.
STORE_DYNAMODB_AUTO_CREATE --store-dynamodb-auto-create / --no-store-dynamodb-auto-create off Create the table at startup when it is missing, instead of refusing to start.

Outbound auth (http mode — forward is the default and needs no extra fields):

Env var CLI flag Description
OUTBOUND_AUTH --outbound-auth {forward,oauth-client-credentials,static} Outbound mechanism.
OUTBOUND_TOKEN_URL --outbound-token-url Token endpoint for oauth-client-credentials.
OUTBOUND_CLIENT_ID --outbound-client-id Client ID for oauth-client-credentials.
OUTBOUND_CLIENT_SECRET --outbound-client-secret Client secret for oauth-client-credentials.
OUTBOUND_HEADER_NAME --outbound-header-name Header name for static (default Authorization).
OUTBOUND_HEADER_VALUE --outbound-header-value Literal header value for static.

Inbound (stdio mode) — only OIDC_* apply; transport and inbound auth are not exposed downstream since the MCP client launches the proxy as a trusted subprocess:

Env var CLI flag Default Description
OIDC_REDIRECT_URL --oidc-redirect-url http://localhost:8080/auth/callback Local OAuth callback URL.

Run uvx authsome-mcp-proxy --help for the always-current list.

Credential Management

Tokens obtained in stdio mode (the proxy's OAuth code flow) are cached in ~/.cache/authsome-mcp-proxy-<version>/ (where <version> is the installed package version) as a SQLite database:

~/.cache/authsome-mcp-proxy-0.5.0/
  ├── cache.db
  ├── cache.db-shm
  └── cache.db-wal

To force re-authentication (switching accounts, expired tokens, etc.):

# Linux / macOS
rm -rf ~/.cache/authsome-mcp-proxy*

# Windows
rmdir /s %USERPROFILE%\.cache\authsome-mcp-proxy*

In web mode the proxy doesn't cache its own tokens — downstream MCP clients each hold their own session token they obtained via DCR against the proxy. Each client manages its own cache.

Headless stdio first-run: if your stdio deployment target has no browser (Docker, headless VM, CI), authenticate once on a machine with a browser to populate the cache, then copy it over:

# Linux / macOS
scp -r ~/.cache/authsome-mcp-proxy-<version> user@server:~/.cache/

Refresh tokens keep the cache valid for as long as the IdP allows. Add offline_access to OIDC_SCOPES for Keycloak / Auth0 / Okta to get long-lived refresh tokens; Cognito issues them automatically.

Troubleshooting

Browser doesn't open (stdio mode) — check that port 8080 (or your custom redirect port) isn't blocked, manually open the URL shown in the proxy logs, and verify your firewall isn't blocking localhost connections.

401 Unauthorized from the upstream — verify OIDC_ISSUER_URL matches the IdP your upstream validates against, check requested scopes, and clear the cache (rm -rf ~/.cache/authsome-mcp-proxy*) before retrying. Run with --debug for token-level diagnostics.

redirect_uri mismatch from the IdP — add the proxy's callback URL to the OAuth client's allowed redirect URIs at the IdP. For stdio mode that's http://localhost:8080/auth/callback (or whatever OIDC_REDIRECT_URL is set to); for web mode it's {MCP_PROXY_BASE_URL}/auth/callback. The match has to be exact, including trailing slashes. When MCP_PROXY_BASE_URL lists several hostnames, each one needs its own callback registered — a hostname whose callback is missing fails only at the end of the flow, after the user has already logged in.

One hostname advertises another in its OAuth metadata — the proxy is running with only that other hostname in MCP_PROXY_BASE_URL. Adding a host at the load balancer is not enough; see Serving several hostnames from one deployment.

Tokens won't refresh — most IdPs require offline_access in the scope list to issue refresh tokens (openid profile email offline_access). Cognito is the exception — it issues them automatically once the app client has "Authorization code grant" enabled.

Web-mode proxy advertises itself as FastMCPProxy-xxxx — set the --proxy-name flag; the version, instructions and website URL are relayed from the upstream. See Identity advertised to downstream clients.

MCP client doesn't recognise the proxy at all (stdio) — JSON syntax errors are by far the most common cause; double-check trailing commas and quotes. Confirm uvx is on PATH and restart the MCP client completely (not just reload).

Connection to the upstream fails — verify the upstream URL is correct and reachable from the host running the proxy. Browser-test the URL directly; check for VPN / corporate-proxy interference.

Debug logging — --debug (CLI) or MCP_PROXY_DEBUG=1 (env) for detailed flow tracing. In web mode, FastMCP also logs the routes it serves at startup — useful for confirming that protected-resource metadata is being advertised at the expected /.well-known/oauth-protected-resource/mcp path.

Still stuck? Check the examples directory for a working test setup, and open an issue on GitHub with --debug output (redacted of any secrets).

Contributing

See CONTRIBUTING.md for development setup, testing, CI/CD workflows, and the release process.

Metadata

Release files for authsome-mcp-proxy 0.8.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 authsome-mcp-proxy 0.8.0
File Size Uploaded Provenance Actions
authsome_mcp_proxy-0.8.0.tar.gz 305.4 kB Without provenance Details for authsome_mcp_proxy-0.8.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for authsome-mcp-proxy 0.8.0
File Interpreter ABI Platform Actions
authsome_mcp_proxy-0.8.0-py3-none-any.whl Python 3 none any Details for authsome_mcp_proxy-0.8.0-py3-none-any.whl

Total release size: 359.7 kB

Release files / authsome_mcp_proxy-0.8.0.tar.gz

Download URL authsome_mcp_proxy-0.8.0.tar.gz
Size 305.4 kB
Tags Source
SHA-256 checksum
How to use checksums
7b24fa45a72e08fac272b21ac6dc7cc9bf94a34f80ffdbf39d0c14c8fd5caa48
BLAKE2b-256 checksum
How to use checksums
4918c9ae6304b920d7e360151dd2761b7ef7a5c60e88813bcdfbd91bfb074daa
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.3 {"installer":{"name":"uv","version":"0.13.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / authsome_mcp_proxy-0.8.0-py3-none-any.whl

Download URL authsome_mcp_proxy-0.8.0-py3-none-any.whl
Size 54.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8f38072186660b5030ab69979d61d38ac0230dd33e97f68a1657eb38704386c7
BLAKE2b-256 checksum
How to use checksums
e8131c25b2c77e1422c93022ef2d391aa74451d6757484c60faa2ec349cf0de2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.3 {"installer":{"name":"uv","version":"0.13.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for authsome-mcp-proxy 0.8.0

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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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