Repository navigation
v0.2.0
Provider-neutral OAuth for MCP resource servers: authorization server plus Python and Go SDKs.
This release makes a real MCP client work end to end against a path-mounted issuer. Every fix below was found by a client failing where curl succeeded.
Breaking
MCP_AUTH_TRUST_PROXY_TLS defaults to false. X-Forwarded-Proto is set by the caller, so it was never evidence of TLS — anything able to reach the process satisfied MCP_AUTH_REQUIRE_HTTPS by sending one header. Verified against a live cluster, where an unrelated pod reached the authorization server and got 200.
A deployment behind a TLS-terminating proxy must now set MCP_AUTH_TRUST_PROXY_TLS=true, or every request is refused with https_required. The rejection names the setting. Treat it as a claim about your topology and enforce it with a NetworkPolicy or equivalent, so the proxy really is the only route in.
Authorization server
- Serve RFC 8414 metadata at the path-insertion location for a path-mounted issuer. Clients request
/.well-known/oauth-authorization-server/<path>, not<issuer>/.well-known/..., so discovery previously 404'd. - Post the consent form to the issuer-mounted path. A root-relative action 404'd before the upstream identity provider was ever reached.
/registerechoesclient_id_issued_at,grant_types,response_types, andscope(RFC 7591). Clients that cannot read back what they registered re-register on every attempt.- Advertise
subject_types_supportedandid_token_signing_alg_values_supported. Clients validating against the OpenID Connect Discovery schema rejected the whole document without them. - Protected resource metadata is served per resource. It previously answered the bare well-known path with
resources[0]plus a non-standardresourcesmember, handing multi-resource deployments an audience the client never asked for.
SDKs
ProtectedResourceMetadataHandler(Go) andprotected_resource_metadata(Python) derivescopes_supportedfrom the verifier's required scopes, so the advertised set cannot drift from the enforced one. A server that omits it leaves the client nothing to request; the token carries no scope and every call fails403with nothing explaining why.unauthorized_headers_for_error(Python), matching Go's existing helper, so a Python resource server can emitinsufficient_scopeand let a client retry.unauthorized_headers(Python) now comma-separates auth-params per RFC 9110 section 11.6.1. Lenient parsers accepted the old form; strict ones did not.- Discovery in both clients tries the RFC 8414 forms before the OIDC suffix forms.
Docs
New security model page. The README is now an index; its duplicated setup, commands and release sections were already in docs/development.md. The deliberate RFC 8252 private-use-scheme deviation from the spec's localhost-or-HTTPS rule is documented, since desktop MCP clients cannot work without it.
Verified
Full flow against a live deployment for both a Go and a Python resource server: discovery, DCR, PKCE S256, Keycloak login, token with bound audience, initialize, tools/list, tools/call. Cross-audience tokens rejected 401 both ways. Confirmed from Cursor as a real client.
Modules
github.com/Agent-Hellboy/mcp-auth/auth-serveratauth-server/v0.2.0github.com/Agent-Hellboy/mcp-auth/auth-client/goatauth-client/go/v0.2.0- Python
mcp-auth-client0.2.0
🤖 Generated with Claude Code