Skip to main content

The security, access-control, and governance layer for MCP servers.

Project description

pontifex-mcp

The security and governance layer for MCP servers, built on the official MCP Python SDK.

pontifex-mcp lets you build MCP servers that connect AI agents to real systems without giving up control over who can call what. You write the tools; it handles authentication, per-caller scopes, rate limits, and a full audit trail.

Key features

  • Secure by default — OAuth 2.1 JWTs and sk_… API keys; every tool call is authenticated. Any OIDC provider (Auth0, Entra, Clerk, Keycloak).
  • Least-privilege scopesdomain:resource:action, checked before every call. Callers can't widen their own access.
  • Auditable — every call recorded: who, what, when, data source, cache hit, latency.
  • Standards-based — RFC 9728 discovery + WWW-Authenticate; MCP clients bootstrap auth on their own.
  • Resilient — per-caller rate limiting, adapter failover, circuit breaking.
  • Observable — Logfire / OpenTelemetry tracing and metrics wired in.
  • Drop-in connectors — generate governed tools from an OpenAPI spec (code or config), with optional per-user OAuth token exchange (RFC 8693) to the downstream.
  • Built on the MCP SDK — keep its tools, protocol, and transports; add the controls a production server needs.

Asymmetric-only JWT validation, generic auth errors, and no token claim can escalate a caller.

Install

pip install pontifex-mcp     # or: uv add pontifex-mcp

Requires Python 3.12+. The floor below needs nothing else; Postgres and Redis come in only when you turn on API-key auth.

Start in a few lines

PontifexMCP is a drop-in subclass of the MCP SDK's FastMCP. The floor needs no database, no Redis, and no auth — the caller is anonymous and every call is audited to stdout.

from pontifex_mcp import PontifexMCP

mcp = PontifexMCP("payments")

@mcp.tool(scope="balance:read")
async def get_balance() -> dict:
    return {"available": 421000, "currency": "usd"}

@mcp.tool(scope="refunds:execute")
async def issue_refund(charge_id: str, amount: int, idempotency_key: str) -> dict:
    return {"refunded": amount, "charge_id": charge_id, "status": "succeeded"}

if __name__ == "__main__":
    mcp.run()                 # stdio; mcp.run(http=True) binds 127.0.0.1

The scope= you declared is advisory until you add an auth backend — then it's enforced, unchanged.

Graduate to enforcement

from pontifex_mcp import PontifexMCP, ApiKeyAuth

mcp = PontifexMCP(
    "payments",
    auth=ApiKeyAuth(),        # Bearer required, scopes enforced (DATABASE_URL + REDIS_URL)
    audit="audit.db",         # durable audit — SQLite here; a Postgres URL in production
)

Now every request needs a valid sk_… API key (or an OAuth 2.1 JWT via JwtAuth()), a caller without payments:refunds:execute is rejected before issue_refund runs, and each call persists an audit row — who, what, when, latency. The same switches flip from the environment, so laptop → production is config, not code.

Auth, scope checks, rate limiting, the audit row, and the structured error envelope are all applied for you — your handler just returns data.

Configuration

Infrastructure settings read from bare, unprefixed env vars:

DATABASE_URL, REDIS_URL          # required (the app fails fast if unset)
AUTH_JWKS_URL, AUTH_ISSUER, AUTH_AUDIENCE, AUTH_SCOPES_CLAIM   # enable the OAuth/JWT path
PUBLIC_BASE_URL                  # canonical URL advertised in OAuth discovery

Domain-specific settings on your subclass read with your domain's env_prefix.

Connect an existing API (no hand-written tools)

If the system already has an OpenAPI spec, generate governed tools from it — each one wrapped in the same tool_runtime (scope check, audit, error envelope). Operations are opt-in via an explicit include allowlist.

from pontifex_mcp import register_openapi_tools, BearerFromEnv

register_openapi_tools(
    mcp,
    spec="https://api.internal/openapi.json",   # URL, path, or dict; JSON or YAML
    domain="orders",
    base_url="https://api.internal",
    audit=audit,
    auth=BearerFromEnv("ORDERS_API_TOKEN"),     # service credential to the backend
    include=["GET /orders", "GET /orders/{id}"],
)

Or onboard with config alone — point PONTIFEX_CONNECTORS_CONFIG at a connectors YAML file and the server registers the tools at startup, no domain code.

For a backend that enforces its own per-user permissions, swap the service credential for OAuth token exchange (RFC 8693) — Pontifex exchanges the caller's token for one scoped to the downstream, on their behalf (the inbound token is never forwarded):

from pontifex_mcp import TokenExchange

auth = TokenExchange(
    token_endpoint="https://idp.example.com/oauth/token",
    audience="https://api.internal",
    client_id_env="PONTIFEX_OAUTH_CLIENT_ID",
    client_secret_env="PONTIFEX_OAUTH_CLIENT_SECRET",
)

Exchanged tokens are cached in process memory by default, or in Redis (PONTIFEX_TOKEN_CACHE=redis, encrypted at rest). See the Connectors guide for the full configuration.

Who it's for

Reach for pontifex-mcp when you're exposing internal or proprietary systems — an orders API, a customer database, an analytics warehouse — to AI agents (Claude Desktop, your own agents, anything that speaks MCP), and unauthenticated tool access isn't an option.

If you're shipping a single public tool over non-sensitive data, the MCP SDK on its own is simpler. Come here when access control and an audit trail start to matter.

License

Apache-2.0 © Chris Dare. Part of Argonauts.

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

pontifex_mcp-0.4.0.tar.gz (50.2 kB view details)

Uploaded Source

Built Distribution

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

pontifex_mcp-0.4.0-py3-none-any.whl (66.7 kB view details)

Uploaded Python 3

File details

Details for the file pontifex_mcp-0.4.0.tar.gz.

File metadata

  • Download URL: pontifex_mcp-0.4.0.tar.gz
  • Upload date:
  • Size: 50.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for pontifex_mcp-0.4.0.tar.gz
Algorithm Hash digest
SHA256 6b851ab848789f957c7c1e70b40cb6a280534e9c70708c22693c0c64240bb3b9
MD5 35fa6a338ca4ad855fcd69e7637130c4
BLAKE2b-256 222c27cee4c2a01c77bb181e141ad49180ab43946af642ad032fb5b86b15e28e

See more details on using hashes here.

Provenance

The following attestation bundles were made for pontifex_mcp-0.4.0.tar.gz:

Publisher: publish-pypi.yml on chris-dare/pontifex

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

File details

Details for the file pontifex_mcp-0.4.0-py3-none-any.whl.

File metadata

  • Download URL: pontifex_mcp-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 66.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for pontifex_mcp-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 fa3ae980e065a56fc1e71d20ab436c6e45891f067e3590a02df904e40dca4056
MD5 ffae57ffd84d91ca1f4abbdd7dcf370a
BLAKE2b-256 6fe253a7d67bef3f5074214809fdfd6b8bff32fd745a0ac41d18b7d911221585

See more details on using hashes here.

Provenance

The following attestation bundles were made for pontifex_mcp-0.4.0-py3-none-any.whl:

Publisher: publish-pypi.yml on chris-dare/pontifex

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