Skip to content

Commit 01ade9d

Browse files
renovate[bot]github-actions[bot]jhrozek
authored
Update stacklok/toolhive to v0.27.2 (#867)
* Update stacklok/toolhive to v0.27.2 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * Refresh reference assets for toolhive v0.27.2 * Document baselineClientScopes for embedded auth server * Polish baselineClientScopes editorial pass * Fix baselineClientScopes validation description Clarify that when scopesSupported is omitted, the embedded auth server validates baselineClientScopes against its default scope set (openid, profile, email, offline_access) rather than failing to start. This behavior shipped in toolhive v0.27.2 as part of #5233. --------- Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Jakub Hrozek <jakub@stacklok.com>
1 parent 993f956 commit 01ade9d

7 files changed

Lines changed: 124 additions & 47 deletions

File tree

.github/upstream-projects.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ projects:
3535

3636
- id: toolhive
3737
repo: stacklok/toolhive
38-
version: v0.27.1
38+
version: v0.27.2
3939
# toolhive is a monorepo covering the CLI, the Kubernetes
4040
# operator, and the vMCP gateway. It also introduces cross-
4141
# cutting features that land in concepts/, integrations/,

docs/toolhive/concepts/embedded-auth-server.mdx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,27 @@ the OAuth flow.
145145
- **Configurable token lifespans:** Access tokens, refresh tokens, and
146146
authorization codes have configurable durations with sensible defaults.
147147

148+
## Baseline scopes for DCR clients
149+
150+
Some MCP clients (for example, Claude Code) register via DCR with a narrowed
151+
`scope` value, then request a wider set of scopes at `/oauth/authorize`. By
152+
default, the embedded authorization server rejects those requests with
153+
`invalid_scope` because the registered client's scope set does not include the
154+
scopes being requested.
155+
156+
To support this pattern, configure `baselineClientScopes` on the embedded auth
157+
server. The server merges these scopes into every DCR-registered client's scope
158+
set, so clients can request them at `/oauth/authorize` regardless of what they
159+
originally registered with. If `scopesSupported` is set explicitly on the
160+
embedded auth server, all baseline values must appear in it; if
161+
`scopesSupported` is omitted, the server validates against its default scope set
162+
(`openid`, `profile`, `email`, `offline_access`).
163+
164+
Keep the baseline narrow (typically `openid` and `offline_access`). Every
165+
DCR-registered client gains the ability to request these scopes, including
166+
public clients like Claude Code, Cursor, and VS Code, so privileged scopes do
167+
not belong in the baseline.
168+
148169
## Session storage
149170

150171
By default, session storage is in-memory. Upstream tokens are lost when pods

docs/toolhive/guides-k8s/auth-k8s.mdx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -544,13 +544,14 @@ kubectl apply -f embedded-auth-config.yaml
544544

545545
**Configuration reference:**
546546

547-
| Field | Description |
548-
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
549-
| `issuer` | HTTPS URL identifying this authorization server. Appears in the `iss` claim of issued JWTs. |
550-
| `signingKeySecretRefs` | References to Secrets containing JWT signing keys. First key is active; additional keys support rotation. |
551-
| `hmacSecretRefs` | References to Secrets with symmetric keys for signing authorization codes and refresh tokens. |
552-
| `tokenLifespans` | Configurable durations for access tokens (default: 1h), refresh tokens (default: 168h), and auth codes (default: 10m). |
553-
| `upstreamProviders` | Configuration for upstream identity providers. MCPServer and MCPRemoteProxy support one provider; VirtualMCPServer supports multiple providers for sequential authentication. |
547+
| Field | Description |
548+
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
549+
| `issuer` | HTTPS URL identifying this authorization server. Appears in the `iss` claim of issued JWTs. |
550+
| `signingKeySecretRefs` | References to Secrets containing JWT signing keys. First key is active; additional keys support rotation. |
551+
| `hmacSecretRefs` | References to Secrets with symmetric keys for signing authorization codes and refresh tokens. |
552+
| `tokenLifespans` | Configurable durations for access tokens (default: 1h), refresh tokens (default: 168h), and auth codes (default: 10m). |
553+
| `upstreamProviders` | Configuration for upstream identity providers. MCPServer and MCPRemoteProxy support one provider; VirtualMCPServer supports multiple providers for sequential authentication. |
554+
| `baselineClientScopes` | Optional list of OAuth 2.0 scopes merged into every DCR-registered client's scope set. Use this when MCP clients register with a narrowed `scope` field but then request wider scopes at `/oauth/authorize`. See [Baseline scopes for DCR clients](../concepts/embedded-auth-server.mdx#baseline-scopes-for-dcr-clients). |
554555

555556
**Step 5: Create the MCPOIDCConfig and MCPServer resources**
556557

docs/toolhive/guides-vmcp/authentication.mdx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,25 @@ spec:
451451
authorizationEndpointBaseUrl: https://auth.example.com
452452
```
453453

454+
If your MCP clients register via DCR with a narrowed `scope` value and then
455+
request additional scopes at `/oauth/authorize` (a pattern used by Claude Code,
456+
among others), set `baselineClientScopes` so the embedded auth server merges
457+
those scopes into every registered client's scope set. If `scopesSupported` is
458+
set explicitly, all baseline values must appear in it; otherwise the server
459+
validates against its default scope set (`openid`, `profile`, `email`,
460+
`offline_access`). See
461+
[Baseline scopes for DCR clients](../concepts/embedded-auth-server.mdx#baseline-scopes-for-dcr-clients)
462+
for guidance on which scopes to include.
463+
464+
```yaml
465+
spec:
466+
authServerConfig:
467+
issuer: https://auth.example.com
468+
baselineClientScopes:
469+
- openid
470+
- offline_access
471+
```
472+
454473
Each upstream provider `name` must be a valid DNS label (lowercase alphanumeric
455474
and hyphens, max 63 characters). This name is what
456475
[upstream token injection](#upstream-token-injection) and

static/api-specs/crds/mcpexternalauthconfigs.schema.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,17 @@
132132
"pattern": "^https?://[^\\s?#]+[^/\\s?#]$",
133133
"type": "string"
134134
},
135+
"baselineClientScopes": {
136+
"description": "BaselineClientScopes is a baseline set of OAuth 2.0 scopes guaranteed to be\nincluded in every client registration. The embedded auth server unions these\nscopes into the registered set returned by RFC 7591 Dynamic Client\nRegistration, so a client that narrows the `scope` field at /oauth/register\ncan still request the baseline scopes at /oauth/authorize. All values must\nbe present in the upstream-derived scopesSupported set; the auth server\nfails to start if any value is missing.\n\nSecurity: every client registered via /oauth/register will gain the\nability to request these scopes at /oauth/authorize, regardless of what\nthe client itself requested. Keep the baseline narrow (typically\n\"openid\" and \"offline_access\"). Adding a privileged scope here — e.g.\n\"admin:read\" — would grant it to every DCR-registered client, including\npublic clients like Claude Code, Cursor, and VS Code.",
137+
"items": {
138+
"minLength": 1,
139+
"pattern": "^[\\x21\\x23-\\x5B\\x5D-\\x7E]+$",
140+
"type": "string"
141+
},
142+
"maxItems": 10,
143+
"type": "array",
144+
"x-kubernetes-list-type": "atomic"
145+
},
135146
"hmacSecretRefs": {
136147
"description": "HMACSecretRefs references Kubernetes Secrets containing symmetric secrets for signing\nauthorization codes and refresh tokens (opaque tokens).\nCurrent secret must be at least 32 bytes and cryptographically random.\nSupports secret rotation via multiple entries (first is current, rest are for verification).\nIf not specified, an ephemeral secret will be auto-generated (development only -\nauth codes and refresh tokens will be invalid after restart).",
137148
"items": {

static/api-specs/crds/virtualmcpservers.schema.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,17 @@
2323
"pattern": "^https?://[^\\s?#]+[^/\\s?#]$",
2424
"type": "string"
2525
},
26+
"baselineClientScopes": {
27+
"description": "BaselineClientScopes is a baseline set of OAuth 2.0 scopes guaranteed to be\nincluded in every client registration. The embedded auth server unions these\nscopes into the registered set returned by RFC 7591 Dynamic Client\nRegistration, so a client that narrows the `scope` field at /oauth/register\ncan still request the baseline scopes at /oauth/authorize. All values must\nbe present in the upstream-derived scopesSupported set; the auth server\nfails to start if any value is missing.\n\nSecurity: every client registered via /oauth/register will gain the\nability to request these scopes at /oauth/authorize, regardless of what\nthe client itself requested. Keep the baseline narrow (typically\n\"openid\" and \"offline_access\"). Adding a privileged scope here — e.g.\n\"admin:read\" — would grant it to every DCR-registered client, including\npublic clients like Claude Code, Cursor, and VS Code.",
28+
"items": {
29+
"minLength": 1,
30+
"pattern": "^[\\x21\\x23-\\x5B\\x5D-\\x7E]+$",
31+
"type": "string"
32+
},
33+
"maxItems": 10,
34+
"type": "array",
35+
"x-kubernetes-list-type": "atomic"
36+
},
2637
"hmacSecretRefs": {
2738
"description": "HMACSecretRefs references Kubernetes Secrets containing symmetric secrets for signing\nauthorization codes and refresh tokens (opaque tokens).\nCurrent secret must be at least 32 bytes and cryptographically random.\nSupports secret rotation via multiple entries (first is current, rest are for verification).\nIf not specified, an ephemeral secret will be auto-generated (development only -\nauth codes and refresh tokens will be invalid after restart).",
2839
"items": {

static/api-specs/toolhive-api.yaml

Lines changed: 53 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -355,44 +355,6 @@ components:
355355
use_pkce:
356356
type: boolean
357357
type: object
358-
github_com_stacklok_toolhive_pkg_auth_tokenexchange.Config:
359-
description: TokenExchangeConfig contains token exchange configuration for external
360-
authentication
361-
properties:
362-
audience:
363-
description: Audience is the target audience for the exchanged token
364-
type: string
365-
client_id:
366-
description: ClientID is the OAuth 2.0 client identifier
367-
type: string
368-
client_secret:
369-
description: ClientSecret is the OAuth 2.0 client secret
370-
type: string
371-
external_token_header_name:
372-
description: ExternalTokenHeaderName is the name of the custom header to
373-
use when HeaderStrategy is "custom"
374-
type: string
375-
header_strategy:
376-
description: |-
377-
HeaderStrategy determines how to inject the token
378-
Valid values: HeaderStrategyReplace (default), HeaderStrategyCustom
379-
type: string
380-
scopes:
381-
description: Scopes is the list of scopes to request for the exchanged token
382-
items:
383-
type: string
384-
type: array
385-
uniqueItems: false
386-
subject_token_type:
387-
description: |-
388-
SubjectTokenType specifies the type of the subject token being exchanged.
389-
Common values: oauthproto.TokenTypeAccessToken (default), oauthproto.TokenTypeIDToken, oauthproto.TokenTypeJWT.
390-
If empty, defaults to oauthproto.TokenTypeAccessToken.
391-
type: string
392-
token_url:
393-
description: TokenURL is the OAuth 2.0 token endpoint URL
394-
type: string
395-
type: object
396358
github_com_stacklok_toolhive_pkg_auth_upstreamswap.Config:
397359
description: |-
398360
UpstreamSwapConfig contains configuration for upstream token swap middleware.
@@ -604,6 +566,20 @@ components:
604566
`{authorization_endpoint_base_url}/oauth/authorize` instead of `{issuer}/oauth/authorize`.
605567
All other endpoints remain derived from the issuer.
606568
type: string
569+
baseline_client_scopes:
570+
description: |-
571+
BaselineClientScopes is a baseline set of OAuth 2.0 scopes unioned into every
572+
DCR registration. All values must appear in ScopesSupported; the auth server
573+
rejects this RunConfig at startup otherwise. Empty means current behavior is
574+
preserved (registered scope = client-requested, or DefaultScopes if empty).
575+
When ScopesSupported is empty, the subset check uses registration.DefaultScopes
576+
(the same set applyDefaults would substitute at startup) — so
577+
BaselineClientScopes containing standard OIDC scopes works without enumerating
578+
ScopesSupported explicitly.
579+
items:
580+
type: string
581+
type: array
582+
uniqueItems: false
607583
hmac_secret_files:
608584
description: |-
609585
HMACSecretFiles contains file paths to HMAC secrets for signing authorization codes
@@ -1143,6 +1119,44 @@ components:
11431119
description: Whether to print resolved overlay paths for debugging
11441120
type: boolean
11451121
type: object
1122+
github_com_stacklok_toolhive_pkg_oauthproto_tokenexchange.Config:
1123+
description: TokenExchangeConfig contains token exchange configuration for external
1124+
authentication
1125+
properties:
1126+
audience:
1127+
description: Audience is the target audience for the exchanged token
1128+
type: string
1129+
client_id:
1130+
description: ClientID is the OAuth 2.0 client identifier
1131+
type: string
1132+
client_secret:
1133+
description: ClientSecret is the OAuth 2.0 client secret
1134+
type: string
1135+
external_token_header_name:
1136+
description: ExternalTokenHeaderName is the name of the custom header to
1137+
use when HeaderStrategy is "custom"
1138+
type: string
1139+
header_strategy:
1140+
description: |-
1141+
HeaderStrategy determines how to inject the token
1142+
Valid values: HeaderStrategyReplace (default), HeaderStrategyCustom
1143+
type: string
1144+
scopes:
1145+
description: Scopes is the list of scopes to request for the exchanged token
1146+
items:
1147+
type: string
1148+
type: array
1149+
uniqueItems: false
1150+
subject_token_type:
1151+
description: |-
1152+
SubjectTokenType specifies the type of the subject token being exchanged.
1153+
Common values: oauthproto.TokenTypeAccessToken (default), oauthproto.TokenTypeIDToken, oauthproto.TokenTypeJWT.
1154+
If empty, defaults to oauthproto.TokenTypeAccessToken.
1155+
type: string
1156+
token_url:
1157+
description: TokenURL is the OAuth 2.0 token endpoint URL
1158+
type: string
1159+
type: object
11461160
github_com_stacklok_toolhive_pkg_registry.OAuthPublicConfig:
11471161
description: |-
11481162
AuthConfig contains the non-secret OAuth configuration when auth is configured.
@@ -1379,7 +1393,7 @@ components:
13791393
ThvCABundle is the path to the CA certificate bundle for ToolHive HTTP operations
13801394
type: string
13811395
token_exchange_config:
1382-
$ref: '#/components/schemas/github_com_stacklok_toolhive_pkg_auth_tokenexchange.Config'
1396+
$ref: '#/components/schemas/github_com_stacklok_toolhive_pkg_oauthproto_tokenexchange.Config'
13831397
tools_filter:
13841398
description: |-
13851399
DEPRECATED: Middleware configuration.

0 commit comments

Comments
 (0)