{
  "markdown": "# mcp-server-auth-template\n\n[![quality](https://github.com/brunovicco/mcp-server-auth-template/actions/workflows/quality.yml/badge.svg)](https://github.com/brunovicco/mcp-server-auth-template/actions/workflows/quality.yml)\n[![compatibility](https://github.com/brunovicco/mcp-server-auth-template/actions/workflows/compatibility.yml/badge.svg)](https://github.com/brunovicco/mcp-server-auth-template/actions/workflows/compatibility.yml)\n[![release](https://img.shields.io/github/v/release/brunovicco/mcp-server-auth-template)](https://github.com/brunovicco/mcp-server-auth-template/releases)\n![python](https://img.shields.io/badge/python-3.13%20%7C%203.14-blue.svg)\n[![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n\n*[Leia em português](README.pt-BR.md)*\n\n> A production-oriented OAuth 2.1 resource-server reference for remote MCP: Microsoft Entra ID and\n> generic OIDC, exact token/resource validation, fail-closed authorization, progressive scope\n> challenges, stateless MCP `2026-07-28`, and metadata-only OpenTelemetry evidence.\n\nUse this repository when the hard part is not \"how do I expose an MCP tool?\" but **how do I expose\nit without weakening identity, authorization, transport, and observability boundaries**. The server\npairs with [`mcp-client-auth-template`](https://github.com/brunovicco/mcp-client-auth-template) for\nan executable end-to-end reference using synthetic identities and no production credentials.\n\n## What this repository proves\n\nThe paired executable path validates real resource-server behavior rather than configuration claims:\n\n- ✅ RFC 9728 Protected Resource Metadata is published by the resource server\n- ✅ RFC 8707 resource binding becomes an exact JWT audience boundary\n- ✅ issuer, signature, expiry, algorithm/key compatibility and caller type fail closed\n- ✅ delegated scopes and Entra application roles remain distinct authorization concepts\n- ✅ `403 insufficient_scope` is returned before dispatch for progressive authorization\n- ✅ wrong-audience tokens are rejected with `401`\n- ✅ protected tools stay hidden from anonymous catalog discovery\n- ✅ MCP `2026-07-28` stays stateless and does not mint `Mcp-Session-Id`\n- ✅ generic OIDC and Microsoft Entra ID share one application boundary without provider leakage\n- ✅ W3C trace context reaches the server while OAuth/MCP sensitive values stay out of telemetry\n- ✅ release artifacts, container evidence, SBOMs and provenance are validated by executable gates\n\nFor a requirement-by-requirement view of the paired OAuth/MCP behavior, including explicit evidence\ngaps and discussion topics for the MCP Authorization Interest Group / Tool Scopes Working Group, see\nthe [Authorization Implementer Report](docs/AUTHORIZATION_IMPLEMENTER_REPORT.md).\n\n## Architecture\n\n```mermaid\nflowchart LR\n    Client[\"MCP client\"] -->|\"OAuth 2.1 / OIDC\"| AS[\"Authorization server<br/>Entra ID or generic OIDC\"]\n    Client -->|\"MCP 2026-07-28<br/>resource-bound bearer\"| Admission[\"Transport admission\"]\n    Admission --> AuthN[\"Token verification\"]\n    AuthN --> AuthZ[\"Tool authorization\"]\n    AuthZ --> Tools[\"MCP tools\"]\n    Server[\"This resource server\"] --- Admission\n\n    Server -->|\"OIDC discovery + cached JWKS\"| AS\n    Server -.->|\"W3C trace context + OTLP\"| Collector[\"OpenTelemetry Collector\"]\n    Collector --> Tempo[\"Tempo\"]\n    Tempo --> Grafana[\"Grafana\"]\n```\n\nThe authorization server owns login, consent, client registration and token issuance. This\nrepository owns the protected resource: transport admission, metadata publication, access-token\nverification, request-scoped principal construction, tool authorization and dispatch.\n\nFor layer boundaries and the detailed authorization sequence, see\n[Architecture](docs/ARCHITECTURE.md).\n\n## 5-minute verification\n\nThe companion client owns the executable cross-repository reference flow. With both repositories\ncloned as siblings, verify this server directly from source:\n\n```bash\ncd ../mcp-client-auth-template\n./scripts/run_reference_demo.sh \\\n  --server-root ../mcp-server-auth-template\n```\n\nThe flow starts the real server from this checkout plus a deterministic local OIDC provider and\nproves CIMD-first Authorization Code + PKCE, authenticated `whoami`, bounded scope step-up,\nwrong-audience rejection and stateless MCP behavior.\n\nFor the observable published-image proof:\n\n```bash\ncd ../mcp-client-auth-template\n./scripts/run_observability_demo.sh --keep\n```\n\nThe observable flow verifies one distributed trace across client and server, positive Collector\nreceipt, Tempo retrieval, Grafana provisioning and telemetry privacy assertions.\n\nSee [Verification guide](docs/VERIFICATION.md) for the exact evidence boundary.\n\n### Visual proof\n\nThe terminal proof below is captured from the source-level paired reference flow:\n\n![Server reference demo](docs/assets/server-reference-demo.gif)\n\nThe trace screenshots are captured from a successful observable run and focus on\n`mcp-server-auth-template` spans:\n\n![Server distributed trace](docs/assets/server-observability-trace.png)\n\n![Server distributed trace detail](docs/assets/server-observability-trace-detail.png)\n\n## Authentication profiles\n\n| Profile | Intended use | Key behavior |\n| --- | --- | --- |\n| Entra delegated | Interactive enterprise users | Validates `scp`, tenant/application identifiers, issuer, audience and subject |\n| Entra application | Provider-specific app-only deployments | Requires explicit `idtyp=app`; keeps `roles` separate from delegated scopes |\n| Generic OIDC delegated | Standards-based interactive clients | Validates issuer/audience/signature/expiry and OAuth scopes |\n| Generic OIDC client credentials | Unattended services in the deterministic pair profile | Accepts pre-registered machine tokens and progressive OAuth scopes |\n\nSet `MCP_SERVER_AUTH_PROVIDER=entra` or `generic` to switch adapters. The example `whoami` tool\nreturns the verified caller identity; `health` requires the additional `mcp:tools:health` scope and\ndemonstrates a pre-dispatch `403 insufficient_scope` challenge.\n\n## Quick start\n\nPrerequisites: Python 3.13 or 3.14 and\n[`uv`](https://docs.astral.sh/uv/getting-started/installation/).\n\n```bash\ngit clone https://github.com/brunovicco/mcp-server-auth-template.git\ncd mcp-server-auth-template\ncp .env.example .env\nuv sync --frozen --all-groups\nuv run uvicorn mcp_server_auth_template.entrypoints.mcp_server:create_app --factory --reload\n```\n\nConfigure either the Entra or generic-OIDC block in `.env`, then point an MCP client at\n`http://localhost:8000/mcp`.\n\n| Endpoint | Purpose | Authentication |\n| --- | --- | --- |\n| `/mcp` | MCP Streamable HTTP | Bearer token |\n| `/.well-known/oauth-protected-resource` | Authorization-server discovery metadata | Public |\n| `/livez` | Process liveness | Public, minimal response |\n| `/readyz` | MCP lifespan readiness | Public, minimal response |\n\nFor production-style execution:\n\n```bash\nuv run python -m mcp_server_auth_template.entrypoints.serve\n```\n\nSee [Production operations](docs/OPERATIONS.md) before exposing the service outside loopback.\n\n## Official MCP Registry readiness\n\nP2.1 prepares this repository for the Official MCP Registry namespace\n`io.github.brunovicco/mcp-server-auth-template`. `server.json` describes the public GHCR image as\nan OCI package using the real `streamable-http` transport; it does not claim a hosted `remotes`\nendpoint. Version `0.6.1` is reserved as the first immutable image version carrying the required\n`io.modelcontextprotocol.server.name` ownership label.\n\nRegistry publication is deliberately separate from this readiness change and happens only after the\nsecure release pipeline validates the final OCI index. See [Official MCP Registry](docs/REGISTRY.md).\n\n## Security properties\n\nThe implementation is deliberately fail closed:\n\n- exact issuer and audience validation, bounded clock checks, algorithm/key compatibility and\n  cached JWKS refresh;\n- hardened discovery/JWKS egress against unsafe schemes, redirects, compression, oversized bodies,\n  private/reserved destinations, mixed DNS answers and DNS rebinding;\n- Host, Origin, header, envelope, body-size and concurrency admission before authentication and tool\n  dispatch;\n- delegated and application identities remain distinct; extension negotiation never grants\n  authorization by itself;\n- bearer tokens and decoded claims remain request-local and are never logged or persisted;\n- tracing excludes credentials, arbitrary headers and URLs, MCP arguments/results, bodies, baggage\n  and exception text.\n\nThis is a transparent reference implementation, not a security certification. Read\n[Privacy and data handling](docs/PRIVACY.md) and the architecture decisions under\n[`docs/adr/`](docs/adr/) before adapting the boundary.\n\n## MCP `2026-07-28`\n\nThe paired templates exercise the modern stateless profile as executable behavior:\n\n- `server/discover` and per-request `_meta` carry protocol version, client identity and capabilities\n  without the legacy `initialize` / `initialized` handshake;\n- modern requests use `MCP-Protocol-Version`, `Mcp-Method` and `Mcp-Name`;\n- responses do not mint `Mcp-Session-Id`;\n- Protected Resource Metadata drives authorization-server discovery;\n- RFC 8707 `resource` binds the access token audience exactly;\n- runtime `403 insufficient_scope` preserves prior grants and permits only one bounded replay of\n  the undispatched operation;\n- machine-to-machine access is opt-in through\n  `io.modelcontextprotocol/oauth-client-credentials`.\n\nSee [Compatibility](docs/COMPATIBILITY.md) and the companion client's\n[cross-repository E2E evidence](https://github.com/brunovicco/mcp-client-auth-template/blob/main/docs/E2E.md).\n\n## Observability\n\n`a2a-otel-kit` continues W3C trace context at the MCP ASGI boundary. Export remains network-silent\nunless `A2A_OTEL_ENABLED=true` and a complete OTLP traces endpoint are configured. Spans are\nmetadata-only and sit inside hardened HTTP admission but outside authentication and tool dispatch.\n\nSee [LLM and application observability](docs/LLM_OBSERVABILITY.md).\n\n## Engineering evidence\n\n- deterministic quality gate covering lint, format, strict Mypy, architecture, tests/coverage,\n  Bandit, dependency audit, supply-chain controls, governance and vendored contract validation;\n- SHA-pinned GitHub Actions with read-only permissions by default and isolated release authorities;\n- CycloneDX source/runtime inventories, complete vulnerability evidence and fail-closed exception\n  policy;\n- allowlisted byte-reproducible Python release artifacts with SHA-256 manifests and GitHub build\n  provenance;\n- policy-approved GHCR publication with immutable digest, provenance and SBOM attestations;\n- Python 3.13/3.14 against MCP SDK 2.0.0 and latest compatible 2.x;\n- offline JWT fixtures using local keys and synthetic identities;\n- ADRs documenting security, protocol, operations, compatibility, observability and supply-chain\n  decisions.\n\n## Demo vs production\n\n| Reference evidence | Production adoption |\n| --- | --- |\n| Synthetic local OIDC in companion demo | Enterprise authorization server with reviewed registration and consent |\n| Loopback/local reference networking | TLS-protected service networking and explicit proxy ownership |\n| Local Collector/Tempo/Grafana | Organization-managed telemetry pipeline and retention policy |\n| Synthetic signing keys and identities | Managed keys, secrets and provider-specific controls |\n| Reference `whoami` / `health` tools | Domain tools with explicit authorization policies and side-effect controls |\n\nThe reference settings prove boundaries; they are not production defaults.\n\n## Repository structure\n\n```text\nsrc/                    resource-server implementation\ntests/                  unit, contract and security evidence\nscripts/                quality, governance and release automation\ndocs/                   architecture, operations, privacy and security\nexamples/                deployment/reference configuration\n.github/workflows/      CI, compatibility and release workflows\n```\n\nLocal editor and coding-agent state is intentionally excluded from the public repository.\n\n## Documentation\n\n| Document | Use it for |\n| --- | --- |\n| [Verification](docs/VERIFICATION.md) | Source-level and observable paired proof |\n| [Architecture](docs/ARCHITECTURE.md) | Context, layers, dependency rules and request sequence |\n| [Compatibility](docs/COMPATIBILITY.md) | Supported versions and executable client/server contract |\n| [Operations](docs/OPERATIONS.md) | Preflight, probes, shutdown, containers and Kubernetes |\n| [Privacy](docs/PRIVACY.md) | Data inventory, retention, logging, tracing and external processors |\n| [Supply chain](docs/SUPPLY_CHAIN.md) | Dependency policy, CI trust boundary, threats and exceptions |\n| [Observability](docs/LLM_OBSERVABILITY.md) | OpenTelemetry and optional Langfuse configuration |\n| [Development](docs/DEVELOPMENT.md) | Local environment, checks and container workflow |\n| [Architecture decisions](docs/adr/) | Rationale and trade-offs behind material decisions |\n\n## Testing and quality\n\n```bash\nuv lock --check\nuv sync --frozen --all-groups\nuv run pytest\nuv run python scripts/quality_gate.py\n```\n\nThe quality gate is the definition of done. It covers lint, format, architecture, strict typing,\ntests/coverage, Bandit, dependency audit, supply-chain controls, governance and vendored contract\nvalidation.\n\n## Scope and production adoption\n\nThis repository is a reference template, not a hosted identity service. A concrete deployment must\nstill provide TLS termination, immutable image publishing, secret delivery, provider-specific\nregistration, network policy, capacity planning, monitoring ownership and live IdP validation.\nChecked-in `.invalid` and all-zero values are placeholders and fail production preflight.\n\n## License\n\n[MIT](LICENSE)\n",
  "bytes": 13820,
  "sha": "08e7834ad74eecc599dfa9a2f81df05e264c15aa282172b94bf8aa792f554cd7",
  "repo_slug": "brunovicco/mcp-server-auth-template",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_brunovicco_mcp_server_auth_tem_8af32f09/readme"
}