I've been building an MCP ecosystem as a SaaS product for the past few months — governed memory and a control layer for AI assistants, reached over MCP. Writing that up properly is a post for late autumn. This is one piece I hit along the way, and it turned out to be worth releasing on its own.
"Works with Claude" and "works" are two different milestones, and the gap between them is where I spent the last few weeks. Once you point a second client at your MCP server, the assumptions start diverging, and they diverge in ways that are individually small and collectively fatal. Some of what I ran into, all observed against my own servers:
- Claude picks CIMD only when the discovery document advertises both client_id_metadata_document_supported and "none" in token_endpoint_auth_methods_supported. Miss the second one and it silently falls back to dynamic registration.
- ChatGPT ships token_endpoint_auth_methods_supported inside its own client metadata document — a field that isn't in the draft's allowed member list, so a strict parser rejects the whole document.
- Mistral registers a fresh client on every single connection attempt, and it sent me a resource URI with a trailing slash on the root path, which fails a character-exact comparison unless you normalise exactly that one case and nothing else.
- Grok asks for the path-inserted protected resource metadata location first (/.well-known/oauth-protected-resource/mcp), where most setups only serve the root form and answer 404.
None of these are anybody's fault exactly. The specs are young, the drafts moved, and every client implemented a reasonable reading of them.
Keycloak, which I run underneath all of this, is genuinely good and I'm not looking to replace it. It just doesn't cover all of this yet. Since people will ask: Keycloak does have CIMD support now, but it is experimental and behind a feature flag, it is wired through the client policy framework with a client-id-metadata-document executor, and it currently covers the persistent CIMD case only. It is also still catching up on the details above — "none" not being advertised, and the executor rejecting metadata documents that carry fields outside the draft, which real clients do ship.
The bigger gap is on the other side: RFC 8707 resource indicators. Keycloak expresses audience through scopes and protocol mappers rather than the standard resource parameter, and resource indicators are a MUST in the MCP spec. Support is under discussion upstream and not merged. Which means that even after a client is admitted, your provider can't mint a correctly audienced token for a client it just met — the login succeeds and your resource server rejects the token anyway.
So I built cimd-proxy, and I've open sourced it under Apache-2.0. Feel free to use it.
It presents an OAuth 2.1 authorization server to the client, accepts a Client ID Metadata Document (or an RFC 7591 dynamic registration) as the client's identity, and federates the actual authentication to the provider you already run. Nothing has to be enabled in your realm — no feature flag, no client policy. One confidential client per protected resource, with the audience mapper you probably already have. No fork, no extension, no custom authenticator. And it keeps working for providers that will never implement CIMD, because the CIMD surface lives in the proxy and the upstream only ever sees a normal confidential client.
The part I'm happiest with: it never reads the token. It doesn't issue, verify, parse or sign one — the upstream token response is relayed verbatim. The only field it touches is refresh_token, which it swaps for a sealed envelope carrying the upstream token plus the resource identifier, so refreshes route with no server-side session state at all.
What it does enforce: allowlisted metadata document hosts, SSRF and DNS-rebinding protection with the socket pinned to the address that was actually validated, S256 PKCE, incremental body caps, refused redirects, no loopback exception in any mode.
CIMD draft-02, RFC 8414 / 9728 / 8707 / 7591 / 9207 / 7636. Python 3.13 + FastAPI, multi-arch image:
docker run --rm --env-file deploy.env -p 8080:8080 ghcr.io/kumbuka-ai/cimd-proxy:v0.2.1
https://github.com/Kumbuka-ai/cimd-proxy
Two honest notes. All four clients above connect through it today, but it's been fronting my own services for days, not months. And deploy/caddy/example.caddy is worth a read before you copy anything — the protected resource metadata has four details that decide whether a strict client gets through, and I had all four wrong in my own setup first.
Happy to answer questions.
Source: r/mcp · by /u/DerMozart