You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
As a ToolHive operator, I want to use the authserver as a DCR client, so that vMCP can proxy to upstream MCP servers (e.g. Datadog) that require OAuth 2.0 / RFC 7591 without hand-registering a client.
Context
Today the ToolHive authserver cannot configure an OAuth2 upstream without a pre-issued client_id. RFC 7591 Dynamic Client Registration primitives already exist under pkg/auth/oauth/ but are not reachable from pkg/authserver/ without introducing an import cycle. This story closes that gap end-to-end across three phases: the primitives are relocated into a new leaf package pkg/oauthproto/ (alongside a rename of the existing pkg/oauth/), the authserver performs RFC 8414 metadata discovery and RFC 7591 registration at startup, credentials are cached (first in-memory, then persistently with memory and Redis backends) keyed on (issuer, redirect_uri, scopes_hash), and the standard upstream OAuth2 flow drives the session after that.
The end-to-end flow this unlocks:
MCP Client -> [Corp OIDC] -> vMCP embedded AS -> [Datadog OAuth] -> Datadog MCP Server
Datadog's MCP server at https://mcp.datadoghq.com is the reference OAuth 2.1 / RFC 7591 target.
Dependencies: None
Acceptance Criteria
Capability-level outcomes a ToolHive operator (or an e2e check) can observe once the work under this story has landed:
An operator can configure an OAuth2 upstream in the authserver using only a discovery_url (no pre-issued client_id / client_secret), and the authserver starts successfully.
On startup, the authserver performs RFC 8414 authorization server metadata discovery (with RFC 8414 §3.3 issuer-equality validation) and RFC 7591 Dynamic Client Registration against the upstream, then drives the standard upstream OAuth2 flow with the obtained credentials.
Configuration validation rejects invalid combinations at startup (e.g., both ClientID and DCRConfig set, or both DiscoveryURL and RegistrationEndpoint set inside DCRConfig) with a clear error.
After a successful registration, restarting the authserver does not re-register at the upstream — the cached credentials (in-memory in the first delivery, persistent afterwards) short-circuit the discovery + registration network calls.
Cached credentials survive process restart when the persistent store is configured (memory backend for single-process deployments, Redis backend for shared deployments).
Secrets (client_secret, registration_access_token, initial_access_token, refresh tokens) never appear in log records at any log level.
Successful DCR registration is observable via a structured slog Info record; a cache-hit with dcr_age_days > 90 emits a stale-age warning; an invalid_client response at the upstream token endpoint emits a warn-level remediation log.
An MCP client session can successfully proxy through vMCP to an OAuth 2.1 MCP server upstream (Datadog, or a high-fidelity mock) using the DCR-obtained credentials.
task build, task test, and task lint-fix all pass on the dcr branch once every sub-task has merged.
Out of Scope
The following are deliberately excluded from this story. The implementation details that realize the acceptance criteria above are tracked as sub-issues; longer-term follow-ups are deferred entirely.
Out of Scope of this story — tracked as sub-issues:
Deferred (not handled by this story or its sub-issues):
Authserver OTEL metrics and tracing — pkg/authserver/ has no OTEL / Prometheus integration today; adding it is separate work.
Automatic re-register on invalid_client at the upstream token endpoint (with singleflight + per-key rate limiter). This story ships only the warn-log tripwire; auto-recovery is a follow-up.
CLI / proxy persistence of registration_client_uri in pkg/auth/remote/config.go (candidate for a small side PR alongside Phase 1).
RFC 8707 resource indicators confirmation for the existing upstream flow.
User Story
As a ToolHive operator,
I want to use the authserver as a DCR client,
so that vMCP can proxy to upstream MCP servers (e.g. Datadog) that require OAuth 2.0 / RFC 7591 without hand-registering a client.
Context
Today the ToolHive authserver cannot configure an OAuth2 upstream without a pre-issued
client_id. RFC 7591 Dynamic Client Registration primitives already exist underpkg/auth/oauth/but are not reachable frompkg/authserver/without introducing an import cycle. This story closes that gap end-to-end across three phases: the primitives are relocated into a new leaf packagepkg/oauthproto/(alongside a rename of the existingpkg/oauth/), the authserver performs RFC 8414 metadata discovery and RFC 7591 registration at startup, credentials are cached (first in-memory, then persistently with memory and Redis backends) keyed on(issuer, redirect_uri, scopes_hash), and the standard upstream OAuth2 flow drives the session after that.The end-to-end flow this unlocks:
Datadog's MCP server at
https://mcp.datadoghq.comis the reference OAuth 2.1 / RFC 7591 target.Dependencies: None
Acceptance Criteria
Capability-level outcomes a ToolHive operator (or an e2e check) can observe once the work under this story has landed:
discovery_url(no pre-issuedclient_id/client_secret), and the authserver starts successfully.ClientIDandDCRConfigset, or bothDiscoveryURLandRegistrationEndpointset insideDCRConfig) with a clear error.client_secret,registration_access_token,initial_access_token, refresh tokens) never appear in log records at any log level.slogInfo record; a cache-hit withdcr_age_days > 90emits a stale-age warning; aninvalid_clientresponse at the upstream token endpoint emits a warn-level remediation log.task build,task test, andtask lint-fixall pass on thedcrbranch once every sub-task has merged.Out of Scope
The following are deliberately excluded from this story. The implementation details that realize the acceptance criteria above are tracked as sub-issues; longer-term follow-ups are deferred entirely.
Out of Scope of this story — tracked as sub-issues:
pkg/oauthtopkg/oauthprotoand relocate DCR primitives (Phase 1).DCRUpstreamConfig,resolveDCRCredentials, in-memoryDCRCredentialStorestub, RFC 7592 field capture, and structured-log upgrades (Phase 2).DCRCredentialStorebackends (memory + Redis), wiring the persistent store in place of the Phase 2 in-memory stub (Phase 3).Deferred (not handled by this story or its sub-issues):
pkg/authserver/has no OTEL / Prometheus integration today; adding it is separate work.invalid_clientat the upstream token endpoint (with singleflight + per-key rate limiter). This story ships only the warn-log tripwire; auto-recovery is a follow-up.registration_client_uriinpkg/auth/remote/config.go(candidate for a small side PR alongside Phase 1).client_secretrotation via PUT.login.microsoftonline.comresourceparam,mcp.zoho.comaccess_type).