Skip to content

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:

  1. Discovery: The MCP client fetches OAuth metadata from /.well-known/oauth-protected-resource/mcp/
  2. Dynamic Client Registration: The client registers itself with the MCP server's OAuth proxy
  3. Authorization Request: The client initiates the OAuth flow
  4. IdP Redirect: The OAuth proxy redirects to the upstream Identity Provider
  5. Auth Code Return: The IdP returns an authorization code
  6. Token Exchange: The OAuth proxy exchanges the code for tokens and issues its own JWT
  7. MCP Requests: The client makes authenticated MCP requests with the Bearer token
  8. Internal API Calls: The MCP server reaches pygeoapi in-process through httpx.ASGITransport against 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
Google Supported Google OAuth 2.0