feat(cimd): standalone Client ID Metadata Document publisher - #1711
Open
omrishiv wants to merge 4 commits into
Open
feat(cimd): standalone Client ID Metadata Document publisher#1711omrishiv wants to merge 4 commits into
omrishiv wants to merge 4 commits into
Conversation
Decoupled from the token-proxy work (PR agentic-community#1692, closed). Contains only the CIMD publisher: build_cimd_document/build_cimd_client_id_url, the /.well-known/mcp-client-metadata route, cimd_* settings, docs and the publisher integration test. References no token-proxy symbols. Dropped from feat/cimd (both belong to agentic-community#1692's token proxy, not the publisher): TestCimdClientIdPassthrough in test_token_proxy.py and the python-multipart auth-server dependency.
…ack base Repoint the CIMD publisher from /.well-known/mcp-client-metadata to the idiomatic /oauth/client-metadata.json (a client-published resource at an operator-chosen public path per draft-ietf-oauth-client-id-metadata-document and MCP/AT-Protocol convention, not a .well-known authority document). - Root-mounted cimd_router; rides the public catch-all nginx location / (no auth_request), so no nginx change and the AS fetches it unauthenticated. - Derive client_id + default redirect from settings.egress_oauth_callback_base (egress_oauth_callback_base_url or registry_url), ported from oauth-2-1, so both share the externally reachable origin the IdP redirects to. - Keep the opt-in gate, HTTPS enforcement, and public max-age caching. Tests cover the new path, client_id parity, callback-base precedence, and the root-mount/no-prefix invariant.
The 7 registry-consumed CIMD settings (cimd_publisher_enabled, cimd_cache_ttl,
cimd_client_name, cimd_redirect_uris, cimd_scope, cimd_logo_uri, cimd_contacts)
are now first-class on every surface, mirroring SSRF_ALLOWED_HOSTS /
GATEWAY_GENERIC_PROXY_ENABLED (registry-consumed, non-secret):
- config_routes.py: new 'CIMD Publisher' CONFIG_GROUPS group (System Config UI)
- docs/unified-parameter-reference.md: 7 rows
- .env.example + docker-compose{,.podman,.prebuilt}.yml: typed ${VAR:-default}
so empty values never reach Pydantic (bool/int)
- charts/registry values.yaml + deployment.yaml (bool/int via toString|quote)
+ reserved-env-names.txt (chart-managed, not extraEnv-shadowable)
- terraform/aws-ecs: root + module variables, module passthrough, ecs-services
env (bool/int via tostring()), tfvars.example
Verified: helm template renders true/1200; terraform fmt clean; CONFIG_GROUPS
order unique + all non-sensitive; publisher tests 6 passed.
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
The 7 CIMD publisher env vars (env[16]-env[22], after the SSRF pair) shifted every positional env assertion after env[15] by 7. Update the index map + the extraEnv/chart-var assertions to match, and add explicit CIMD_* index checks. Renders verified against helm template. (Pre-existing, unrelated: the stack chart's extra_env_forwarding_test 'yaml: line 99' error reproduces on main.)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs: #992 (publisher slice — see Not covered below; does not close it)
Parent epic: #988 (Phase 4 CIMD)
What
Publishes the registry's own Client ID Metadata Document (CIMD) so the registry can identify itself as an OAuth client (by URL) to external CIMD-aware IdPs, without pre-registration or DCR. Per the CIMD draft, the document's URL is the
client_id.Opt-in (
CIMD_PUBLISHER_ENABLED, default off → 404). Returnsclient_id,client_name,client_uri,redirect_uris,grant_types,response_types,token_endpoint_auth_method: none,scope, and optionallogo_uri/contacts, withCache-Control: public, max-age=<CIMD_CACHE_TTL>.Why this is a standalone branch
The original CIMD work was built on top of #1692 (the gateway token proxy), which was closed. The CIMD publisher has no functional dependency on the token proxy — it references no token-proxy symbols. This branch is the publisher rebased cleanly onto
main, so #992's publisher can land independently of the closed token-proxy effort. (The token-proxy-coupled bits from the original branch — theTestCimdClientIdPassthroughtests and thepython-multipartdep — were intentionally dropped; they belong with a token proxy, not the publisher.)Design decisions
/oauth/client-metadata.json, not/.well-known/…. A CIMD is a client-published resource at an operator-chosen public path — RFC 8615.well-knownis for authority/server documents,./oauth/client-metadata.jsonmatches the MCP spec's own example and the AT-Protocol convention. [Phase 4] Publish CIMD (Client ID Metadata Document) for AI Registry Tools MCP client #992 explicitly left the path "TBD, track the draft," so this satisfies that note rather than contradicting it.cimd_router; the gateway nginx catch-alllocation /proxies to the registry with noauth_request, so the AS fetches it unauthenticated with zero nginx changes.client_id/redirect_urisderive from the egress OAuth callback base (settings.egress_oauth_callback_base=EGRESS_OAUTH_CALLBACK_BASE_URLorREGISTRY_URL), notregistry_urlalone — so a deployment whose external callback host differs fromregistry_urlstill publishes aclient_idand redirect that share the origin the IdP redirects back to. A single canonicalbuild_cimd_client_id_urlguarantees the published URL and any future outboundclient_idcan't drift.enforce_httpson theclient_id, andpublic, max-agecaching.Config surface (three-surface parity)
All 7 registry-consumed, non-secret params are wired first-class:
registry.app.*)CIMD_PUBLISHER_ENABLEDcimd_publisher_enabledcimdPublisherEnabledcimd_publisher_enabledfalseCIMD_CACHE_TTLcimd_cache_ttlcimdCacheTtlcimd_cache_ttl3600CIMD_CLIENT_NAMEcimd_client_namecimdClientNamecimd_client_name"AI Registry Tools"CIMD_REDIRECT_URIScimd_redirect_uriscimdRedirectUriscimd_redirect_uris""CIMD_SCOPEcimd_scopecimdScopecimd_scope""CIMD_LOGO_URIcimd_logo_uricimdLogoUricimd_logo_uri""CIMD_CONTACTScimd_contactscimdContactscimd_contacts""CIMD Publishergroup inconfig_routes.pyCONFIG_GROUPS(all non-sensitive).docs/unified-parameter-reference.md..env.example+ all three compose files (typed${VAR:-false}/${VAR:-3600}defaults so an empty value never breaks Pydantic).charts/registryvalues + deployment (bool/int viatoString | quote) +reserved-env-names.txt(chart-managed, not extraEnv-shadowable).ecs-services.tfenv (bool/int viatostring()),terraform.tfvars.example.Testing
tests/integration/test_cimd_publisher.py— document shape, required fields, field-order stability, cache header,client_id== served URL parity, callback-base precedence overregistry_url, disabled→404, and a root-mount/no-prefix invariant that readsmain.pyso a future rename/prefix can't silently break parity. 6 passed.helm template charts/registryrendersCIMD_PUBLISHER_ENABLED: "true",CIMD_CACHE_TTL: "1200"(bool/int coercion correct).terraform fmt -checkclean on all.tffiles.ruff check/format, pre-commit (syntax, fast tests) clean.Not covered by this PR (so #992 is not closed by mistake)
client_idwiring — nothing today sends the registry's CIMD URL as itsclient_idon an outbound/authorize(egress uses per-server operator credentials). There is no such flow to wire yet;build_cimd_client_id_urlis the single source of truth the future outbound path must reuse. [Phase 4] Publish CIMD (Client ID Metadata Document) for AI Registry Tools MCP client #992 frames this as forward-looking ("once AI Registry Tools calls external MCP servers").client_idon/authorize//oauth/tokenis not in this branch. The original approach ("Option Y": the token proxy forwardsclient_idverbatim to the upstream IdP) depended on [Phase 3] RFC 8707 resource enforcement + gateway token proxy (IdP-signed MCP tokens) #1692's token proxy, which was dropped here. [Phase 4] CIMD consumer: accept CIMD URL as client_id on /authorize #993 returns with the token-proxy / OAuth-discovery track.Files
20 files, +518 / −7. Publisher core:
registry/auth/oauth_metadata.py,registry/api/wellknown_routes.py,registry/main.py,registry/core/config.py. Surface wiring + docs + tests as above.By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.