Supported specificationsΒΆ
The fastgeoapi MCP server plays two roles in an identity-secured MCP deployment: it is an MCP Server (the protected resource AI agents talk to) and it embeds its own OAuth Authorization Server (the OIDC proxy that faces MCP clients, independent of the upstream Identity Provider). The matrix below describes what each role supports, following the structure used by the OpenID AIIM interoperability program.
Support matrixΒΆ
Legend: β supported and verifiable on a live deployment Β· π§ͺ supported upstream, end-to-end verification in progress Β· πΊοΈ on the roadmap Β· β not supported (by design where noted).
As MCP Server (protected resource)ΒΆ
| Specification | Status | Notes |
|---|---|---|
| MCP Streamable HTTP transport (stateless) | β | Every request is self-contained: restarts, redeploys and autosuspend are transparent to clients |
MCP protocol versions up to 2025-11-25 | β | Negotiated with the initialize handshake |
Sessionless protocol 2026-07-28 (MCP SDK v2) | β | Negotiated per connection β see Protocol versions |
Tool annotations (readOnlyHint, destructiveHint, idempotentHint, openWorldHint) | β | Reads and CQL2 queries are read only; PUT and DELETE are destructive and idempotent; running a process is only marked as not read only, since the document does not say what a process does. Tool titles come from the operation summaries |
MCP Apps extension (io.modelcontextprotocol/ui) | β | FastMCP declares the extension to clients that negotiate 2026-07-28 (fastmcp#4919), but this server registers no app and no ui:// resource to render |
| Server card discovery (SEP-2127) | π§ͺ | /.well-known/mcp-server-card, published only with MCP enabled; also a valid MCP Registry server.json (which adds packages) β SEP in review |
| OAuth 2.0 Protected Resource Metadata (RFC 9728) | β | /.well-known/oauth-protected-resource/mcp/, advertised in the WWW-Authenticate challenge |
| Bearer token usage and error semantics (RFC 6750) | β | Distinguishes missing vs invalid token in challenges |
scope parameter in WWW-Authenticate | β | The challenge carries the configured scopes (scope="openid" on the demo), in the anonymous challenge and in the invalid_token one alike; verified on the production deployment on 2026-09-11 and pinned by a test |
| OAuth token based access with own Resource AS | β | The embedded OAuth proxy issues the tokens this server accepts |
As OAuth Authorization Server (embedded OIDC proxy)ΒΆ
| Specification | Status | Notes |
|---|---|---|
| OAuth 2.1-style authorization code + PKCE (RFC 7636) | β | S256; the proxy fronts any OIDC-compliant upstream IdP |
| Authorization Server Metadata (RFC 8414) | β | Path-aware, with the openid-configuration alias and no-trailing-slash variants |
| Dynamic Client Registration (RFC 7591) | β | With redirect-URI validation (unsafe schemes and unregistered URIs rejected) |
| Refresh token rotation | β | One-time-use refresh tokens; offline_access supported |
Client-facing token TTL decoupled from IdP expires_in | β | FASTGEOAPI_MCP_ACCESS_TOKEN_EXPIRY_SECONDS |
| CIMD β Client ID Metadata Document (draft-ietf-oauth-client-id-metadata-document) | β | Advertised (client_id_metadata_document_supported: true) and covered end to end; see the CIMD detail below |
| Mixed-key JWKS validation (RSA / EC / Ed25519) | β | Unsupported key types in an IdP's JWKS are skipped instead of failing the whole set β pairs with Keycloak, Ory Hydra, Rauthy out of the box |
| EMA β Enterprise Managed Authorization (ID-JAG) | π§ͺ | The jwt-bearer grant accepts assertions from configured enterprise IdPs; see Enterprise-Managed Authorization |
| MTLS client authentication (RFC 8705) | β | Not supported |
CIMD feature detailΒΆ
CIMD is not theoretical here: Claude identifies itself this way against this server in production, with a document that declares no scope field.
| CIMD feature | Status | Notes |
|---|---|---|
| Client ID as URL, metadata document fetch | β | SSRF-hardened fetcher (loopback and private ranges rejected, including IPv6 transition addresses) |
redirect_uris from the metadata document | β | Enforced: a redirect the document does not list is refused |
Authorization code + PKCE, none client auth | β | Public URL-identified client completes the flow with no prior registration |
Authorization code + PKCE, private_key_jwt | β | Assertion signed in RS256, as token_endpoint_auth_signing_alg_values_supported says, and verified against the inline jwks published in the document |
jwks_uri (remote key set) in the document | β | Keys fetched from the URL the document publishes, SSRF-guarded on that hop too |
Documents that omit scope | β | Real documents often do (Claude's declares none): the client then inherits the authorization server's default scopes, so those must cover what such clients request |
| Shared-secret client authentication for CIMD clients | β by design | A URL-identified client cannot hold a usable secret: such documents are refused and yield no token |
| Key enforcement | β | An assertion signed with a key the document does not publish is rejected, even with a matching kid |
Enterprise-Managed AuthorizationΒΆ
In the default flow each user authorises this server themselves, through a browser. EMA (SEP-990) moves that decision to the organisation: the client asks its own enterprise identity provider for an ID-JAG β a signed assertion that this employee may reach this server β and exchanges it here for an access token. No browser, no consent screen, and access is revoked centrally at the IdP rather than per client registration.
Configure the issuers whose assertions are honoured (see Configuration); with none configured the grant answers unsupported_grant_type and the surface does not exist.
| Feature | Status | Notes |
|---|---|---|
urn:ietf:params:oauth:grant-type:jwt-bearer grant | π§ͺ | Advertised in the authorization server metadata, together with the id-jag grant profile, only when issuers are configured |
| Assertion validation | π§ͺ | Signature against the issuer's JWKS (discovered via OIDC), trusted issuer, aud, typ (oauth-id-jag+jwt), exp/nbf/iat, and jti replay protection |
| Client binding | π§ͺ | The assertion's signed client_id must match the authenticated client β an assertion minted for one client cannot be presented by another |
resource support | π§ͺ | The issued token is bound to the MCP server the assertion names |
scope support | π§ͺ | The scopes the IdP put in the assertion are carried into the issued token |
| Refresh tokens | β by design | EMA clients re-exchange their assertion instead; the renewable credential stays with the IdP, which is what makes central revocation meaningful |
Protocol versionsΒΆ
Since the move to FastMCP 4 (MCP SDK v2) the server speaks the sessionless protocol 2026-07-28 as well as the older initialize handshake, down to 2024-11-05. The mode is negotiated per connection, so clients on either side of the change work without configuration.
The version you actually get is the one your client asks for. Observed in production on 23 September 2026: Claude's connector (Anthropic/Toolbox 1.0.0) negotiates 2026-07-28, and the server answers on that version.
On either protocol the server reports the fastgeoapi version and the address of the API in serverInfo, the same two values the server card publishes. It also sends instructions, which the client passes to the model: how the tool names are formed, where to start, and which tools exist only for some collections.
Before FastMCP 4 a client that defaulted to 2026-07-28 with no fallback was answered with Bad Request: Unsupported protocol version and could not connect at all; that is no longer the case.
Supported OAuth flowsΒΆ
| Flow | Use Case | Configuration |
|---|---|---|
| Authorization Code + PKCE | Interactive clients (Claude Desktop, mcp-remote) | JWKS_ENABLED=true with OIDC config |
| Client Credentials | Machine-to-machine, service accounts | JWKS_ENABLED=true with OIDC config |
| Dynamic Client Registration (DCR) | Auto-registration for MCP clients | Enabled automatically with OIDC |
OAuth Proxy ArchitectureΒΆ
When OAuth is enabled, the MCP server acts as an OAuth Proxy. This architecture allows the MCP server to handle OAuth flows on behalf of MCP clients, simplifying authentication for AI assistants.
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β MCP Client ββββββΆβ MCP Server ββββββΆβ Identity β
β (mcp-remote) β β (OAuth Proxy) β β Provider β
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β β β
β 1. Discovery β β
ββββββββββββββββββββββββΆβ β
β /.well-known/... β β
β β β
β 2. DCR (register) β β
ββββββββββββββββββββββββΆβ β
β β β
β 3. Authorization β 4. Redirect to IdP β
ββββββββββββββββββββββββΆββββββββββββββββββββββββΆβ
β β β
β β 5. Auth Code β
β βββββββββββββββββββββββββ
β 6. Token Exchange β β
βββββββββββββββββββββββββ β
β β β
β 7. MCP Requests β 8. In-process ASGI β
β (with Bearer token) β call to pygeoapi β
ββββββββββββββββββββββββΆββββββββββββββββββββββββΆβ
OAuth Flow Steps:
- Discovery: The MCP client fetches OAuth metadata from
/.well-known/oauth-protected-resource/mcp/ - Dynamic Client Registration: The client registers itself with the MCP server's OAuth proxy
- Authorization Request: The client initiates the OAuth flow
- IdP Redirect: The OAuth proxy redirects to the upstream Identity Provider
- Auth Code Return: The IdP returns an authorization code
- Token Exchange: The OAuth proxy exchanges the code for tokens and issues its own JWT
- MCP Requests: The client makes authenticated MCP requests with the Bearer token
- Internal API Calls: The MCP server reaches pygeoapi in-process through
httpx.ASGITransportagainst a raw sub-app β no network hop and no shared secret; the OAuth middleware chain is simply not mounted on this internal path
Security FeaturesΒΆ
| Feature | Description |
|---|---|
| Fail-closed startup guard | With MCP enabled and no authentication configured, the server refuses to start unless passthrough mode is explicitly opted in |
| JWT Validation | Tokens are validated using JWKS from the IdP |
| Opaque Token Support | Supports IdPs that return opaque tokens (e.g., Logto without API Resources) |
| RFC 6750 Compliance | Proper error handling distinguishing "no token" vs "invalid token" |
| In-Process Internal Calls | MCP-to-pygeoapi calls run in-process via httpx.ASGITransport on a non-routable virtual host β no bypass key or header exists |
| Scope Validation | Configurable required scopes for access control |
| PKCE Support | Prevents authorization code interception in public clients |
Supported Identity ProvidersΒΆ
The MCP server is provider-agnostic and works with any OIDC-compliant Identity Provider:
| Provider | Status | Notes |
|---|---|---|
| Logto | Tested | OAuth proxy with DCR, opaque token support |
| Auth0 | Supported | Full OIDC support |
| Keycloak | Supported | Full OIDC and OPA integration |
| Okta | Supported | Standard OIDC flows |
| Azure AD | Supported | Microsoft identity platform |
| Supported | Google OAuth 2.0 |