zuplo 7.6.8 → 7.6.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/ai-gateway/integrations/claude-code.mdx +62 -20
- package/docs/ai-gateway/integrations/claude-desktop.mdx +157 -0
- package/docs/articles/configuring-auth0-for-mcp-auth.mdx +5 -0
- package/docs/articles/configuring-okta-for-mcp-auth.mdx +48 -10
- package/docs/articles/oauth-authentication.mdx +34 -0
- package/docs/handlers/mcp-server.mdx +17 -1
- package/docs/mcp-gateway/connect-clients/claude-desktop.mdx +2 -0
- package/docs/policies/ai-gateway-smart-router-inbound/doc.md +198 -29
- package/docs/policies/ai-gateway-smart-router-inbound/intro.md +1 -2
- package/docs/policies/ai-gateway-smart-router-inbound/schema.json +10 -10
- package/docs/policies/auth0-jwt-auth-inbound/doc.md +5 -0
- package/docs/policies/auth0-jwt-auth-inbound/schema.json +1 -1
- package/docs/policies/clerk-jwt-auth-inbound/doc.md +5 -0
- package/docs/policies/clerk-jwt-auth-inbound/schema.json +1 -1
- package/docs/policies/cognito-jwt-auth-inbound/doc.md +17 -0
- package/docs/policies/cognito-jwt-auth-inbound/schema.json +1 -1
- package/docs/policies/firebase-jwt-inbound/doc.md +17 -0
- package/docs/policies/firebase-jwt-inbound/schema.json +1 -1
- package/docs/policies/okta-jwt-auth-inbound/doc.md +23 -0
- package/docs/policies/okta-jwt-auth-inbound/schema.json +1 -1
- package/docs/policies/open-id-jwt-auth-inbound/doc.md +18 -0
- package/docs/policies/open-id-jwt-auth-inbound/schema.json +1 -1
- package/docs/policies/propel-auth-jwt-inbound/doc.md +5 -0
- package/docs/policies/propel-auth-jwt-inbound/schema.json +1 -1
- package/docs/policies/supabase-jwt-auth-inbound/doc.md +5 -0
- package/docs/policies/supabase-jwt-auth-inbound/schema.json +1 -1
- package/docs/programmable-api/oauth-protected-resource-plugin.mdx +78 -6
- package/package.json +5 -5
|
@@ -6,10 +6,9 @@ description:
|
|
|
6
6
|
authenticated, metered, and routed by the gateway.
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
the AI Gateway.
|
|
9
|
+
You can configure [Claude Code](https://www.claude.com/product/claude-code) to
|
|
10
|
+
send its model requests through the Zuplo AI Gateway, so the gateway
|
|
11
|
+
authenticates, meters, and routes every Claude Code session.
|
|
13
12
|
|
|
14
13
|
## Claude Code Setup
|
|
15
14
|
|
|
@@ -25,25 +24,48 @@ the AI Gateway.
|
|
|
25
24
|
|
|
26
25
|
4. Copy the **API URL** and **API Key** shown at the top of the app page
|
|
27
26
|
|
|
28
|
-
5. Add the API key
|
|
29
|
-
using either approach below
|
|
27
|
+
5. Add the API key, gateway URL, and model mappings to `.claude/settings.json`
|
|
30
28
|
|
|
31
29
|
</Stepper>
|
|
32
30
|
|
|
33
|
-
###
|
|
31
|
+
### Before copying: replace the provider name
|
|
34
32
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
:::warning
|
|
34
|
+
|
|
35
|
+
The examples below assume your provider is named `anthropic`. Replace that
|
|
36
|
+
prefix with the name of **your** provider before copying the configuration. For
|
|
37
|
+
example, a provider named `my-anthropic` routes `my-anthropic/claude-sonnet-5`,
|
|
38
|
+
while a Bedrock provider named `bedrock` routes
|
|
39
|
+
`bedrock/anthropic.claude-sonnet-5`. The model part must be an id that provider
|
|
40
|
+
understands.
|
|
41
|
+
|
|
42
|
+
:::
|
|
43
|
+
|
|
44
|
+
The gateway routes every request using `providerName/model`. The provider prefix
|
|
45
|
+
picks which of your providers handles the request — a gateway can contain
|
|
46
|
+
several providers that serve Anthropic models.
|
|
39
47
|
|
|
40
48
|
### Using settings.json
|
|
41
49
|
|
|
50
|
+
You can configure Claude Code either globally or per project by editing the
|
|
51
|
+
settings file:
|
|
52
|
+
|
|
53
|
+
- **Global:** `~/.claude/settings.json`
|
|
54
|
+
- **Project-specific:** `.claude/settings.json` in your project directory
|
|
55
|
+
|
|
56
|
+
Add the following environment configuration to the settings file:
|
|
57
|
+
|
|
42
58
|
```json
|
|
43
59
|
{
|
|
44
60
|
"env": {
|
|
45
61
|
"ANTHROPIC_AUTH_TOKEN": "<your-ai-gateway-app-api-key>",
|
|
46
|
-
"ANTHROPIC_BASE_URL": "https://my-gateway-main-2e18f50.zuplo.app/config_fe0a04972d2848e0a94ae4b8bcd1497e"
|
|
62
|
+
"ANTHROPIC_BASE_URL": "https://my-gateway-main-2e18f50.zuplo.app/config_fe0a04972d2848e0a94ae4b8bcd1497e",
|
|
63
|
+
"ANTHROPIC_MODEL": "anthropic/claude-sonnet-5",
|
|
64
|
+
"ANTHROPIC_SMALL_FAST_MODEL": "anthropic/claude-haiku-4-5",
|
|
65
|
+
"ANTHROPIC_DEFAULT_OPUS_MODEL": "anthropic/claude-opus-5",
|
|
66
|
+
"ANTHROPIC_DEFAULT_SONNET_MODEL": "anthropic/claude-sonnet-5",
|
|
67
|
+
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "anthropic/claude-haiku-4-5",
|
|
68
|
+
"ANTHROPIC_DEFAULT_FABLE_MODEL": "anthropic/claude-fable-5"
|
|
47
69
|
}
|
|
48
70
|
}
|
|
49
71
|
```
|
|
@@ -58,16 +80,36 @@ Claude Code appends `/v1/messages` to the base URL itself, so
|
|
|
58
80
|
Restart Claude and it will switch to using your new AI Gateway configuration and
|
|
59
81
|
all your Claude Code LLM requests will route through the AI Gateway.
|
|
60
82
|
|
|
61
|
-
###
|
|
83
|
+
### Configure every Claude Code model variable
|
|
62
84
|
|
|
63
|
-
The
|
|
64
|
-
|
|
65
|
-
|
|
85
|
+
The model ids in these examples reflect what was current when this page was
|
|
86
|
+
written, and ids differ across providers — a Bedrock provider serves different
|
|
87
|
+
ids than Anthropic directly, and availability can vary by region. Check
|
|
88
|
+
[your provider's page](../managing-providers.mdx) in the portal for the models
|
|
89
|
+
it currently serves, and use those ids.
|
|
66
90
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
91
|
+
None of Claude Code's built-in model names carry a provider prefix, so each of
|
|
92
|
+
the model variables above must be set to a prefixed reference:
|
|
93
|
+
|
|
94
|
+
- `ANTHROPIC_MODEL` — the main model. Required: without it Claude Code sends its
|
|
95
|
+
built-in default model unprefixed and the first request fails.
|
|
96
|
+
- `ANTHROPIC_SMALL_FAST_MODEL` — the model for background tasks.
|
|
97
|
+
- `ANTHROPIC_DEFAULT_OPUS_MODEL`, `ANTHROPIC_DEFAULT_SONNET_MODEL`,
|
|
98
|
+
`ANTHROPIC_DEFAULT_HAIKU_MODEL`, `ANTHROPIC_DEFAULT_FABLE_MODEL` — what the
|
|
99
|
+
`opus`/`sonnet`/`haiku`/`fable` aliases resolve to, so `claude --model opus`
|
|
100
|
+
and `/model sonnet` keep working. In these examples,
|
|
101
|
+
`ANTHROPIC_DEFAULT_FABLE_MODEL` maps `fable` to `anthropic/claude-fable-5`.
|
|
102
|
+
|
|
103
|
+
:::caution{title='HTTP 400: model must use "providerName/model"'}
|
|
104
|
+
|
|
105
|
+
If Claude Code fails immediately with
|
|
106
|
+
`The request body model must use "providerName/model"`, the request carried a
|
|
107
|
+
model name without a provider prefix. The tier alias variables
|
|
108
|
+
(`ANTHROPIC_DEFAULT_*_MODEL`) alone don't prevent this — they don't cover Claude
|
|
109
|
+
Code's built-in default model. Always set `ANTHROPIC_MODEL` to a prefixed model
|
|
110
|
+
reference.
|
|
111
|
+
|
|
112
|
+
:::
|
|
71
113
|
|
|
72
114
|
The app's
|
|
73
115
|
[Model Filtering](../../policies/ai-gateway-model-filtering-v2-inbound.mdx)
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Claude Desktop
|
|
3
|
+
sidebar_label: Claude Desktop
|
|
4
|
+
description:
|
|
5
|
+
Point Claude Desktop at an AI Gateway app so every Chat, Cowork, and Code
|
|
6
|
+
session is authenticated, metered, and routed by the gateway.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
[Claude Desktop](https://claude.com/download) can use the Zuplo AI Gateway as
|
|
10
|
+
its inference provider, so the gateway authenticates, meters, and routes every
|
|
11
|
+
Chat, Cowork, and Code session started from the desktop app.
|
|
12
|
+
|
|
13
|
+
This page covers routing Claude Desktop's model requests through the AI Gateway.
|
|
14
|
+
To give Claude Desktop tools from a Zuplo MCP route instead, see
|
|
15
|
+
[Connect Claude Desktop and Claude.ai](../../mcp-gateway/connect-clients/claude-desktop.mdx).
|
|
16
|
+
|
|
17
|
+
## Claude Desktop Setup
|
|
18
|
+
|
|
19
|
+
<Stepper>
|
|
20
|
+
|
|
21
|
+
1. Create a [new provider](../managing-providers.mdx) in the AI Gateway for
|
|
22
|
+
Anthropic
|
|
23
|
+
|
|
24
|
+
2. [Set up a new team](../managing-teams.mdx)
|
|
25
|
+
|
|
26
|
+
3. Create a [new app](../managing-apps.mdx) to use with Claude Desktop and
|
|
27
|
+
assign it to the team you created
|
|
28
|
+
|
|
29
|
+
4. Copy the **API URL** and **API Key** shown at the top of the app page
|
|
30
|
+
|
|
31
|
+
5. Enter the API URL, API key, and model list in Claude Desktop's third-party
|
|
32
|
+
inference settings
|
|
33
|
+
|
|
34
|
+
</Stepper>
|
|
35
|
+
|
|
36
|
+
### Before copying: replace the provider name
|
|
37
|
+
|
|
38
|
+
:::warning
|
|
39
|
+
|
|
40
|
+
The examples below assume your provider is named `anthropic`. Replace that
|
|
41
|
+
prefix with the name of **your** provider before copying the configuration. For
|
|
42
|
+
example, a provider named `my-anthropic` routes `my-anthropic/claude-sonnet-5`,
|
|
43
|
+
while a Bedrock provider named `bedrock` routes
|
|
44
|
+
`bedrock/anthropic.claude-sonnet-5`. The model part must be an id that provider
|
|
45
|
+
understands.
|
|
46
|
+
|
|
47
|
+
:::
|
|
48
|
+
|
|
49
|
+
The gateway routes every request using `providerName/model`. The provider prefix
|
|
50
|
+
picks which of your providers handles the request — a gateway can contain
|
|
51
|
+
several providers that serve Anthropic models.
|
|
52
|
+
|
|
53
|
+
### Configure third-party inference
|
|
54
|
+
|
|
55
|
+
Configure Claude Desktop's inference provider in its in-app configuration
|
|
56
|
+
window, available on macOS and Windows:
|
|
57
|
+
|
|
58
|
+
<Stepper>
|
|
59
|
+
|
|
60
|
+
1. From the menu bar (macOS) or the application menu (Windows), select **Help →
|
|
61
|
+
Troubleshooting → Enable Developer Mode**.
|
|
62
|
+
|
|
63
|
+
2. Open **Developer → Configure Third-Party Inference**.
|
|
64
|
+
|
|
65
|
+
3. In the **Connection** section, set **Inference provider** to **Gateway**.
|
|
66
|
+
|
|
67
|
+
4. In the **Gateway credentials** card, set **Gateway base URL** to the app's
|
|
68
|
+
API URL, for example
|
|
69
|
+
`https://my-gateway-main-2e18f50.zuplo.app/config_fe0a04972d2848e0a94ae4b8bcd1497e`,
|
|
70
|
+
and **Gateway API key** to the app's API key.
|
|
71
|
+
|
|
72
|
+
5. Leave **Credential kind** set to **Static API key** and **Gateway auth
|
|
73
|
+
scheme** set to **Bearer**.
|
|
74
|
+
|
|
75
|
+
6. In the **Models** section, add the models the app may use to the **Model
|
|
76
|
+
list**. See [Add models to the Model list](#add-models-to-the-model-list).
|
|
77
|
+
|
|
78
|
+
7. Click **Apply locally**. Claude Desktop writes the configuration and
|
|
79
|
+
relaunches into it.
|
|
80
|
+
|
|
81
|
+
</Stepper>
|
|
82
|
+
|
|
83
|
+
:::note
|
|
84
|
+
|
|
85
|
+
Claude Desktop appends `/v1/messages` to the base URL itself, so **Gateway base
|
|
86
|
+
URL** is the app's URL _without_ the `/v1` suffix.
|
|
87
|
+
|
|
88
|
+
:::
|
|
89
|
+
|
|
90
|
+
:::caution{title="Keep the auth scheme set to Bearer"}
|
|
91
|
+
|
|
92
|
+
The gateway reads the API key from the `Authorization: Bearer` header, so keep
|
|
93
|
+
**Gateway auth scheme** set to **Bearer**. Likewise, keep **Credential kind**
|
|
94
|
+
set to **Static API key** — AI Gateway apps authenticate with their own API
|
|
95
|
+
keys, not identity-provider tokens.
|
|
96
|
+
|
|
97
|
+
:::
|
|
98
|
+
|
|
99
|
+
### Add models to the Model list
|
|
100
|
+
|
|
101
|
+
The **Model list** in the **Models** section overrides the model list Claude
|
|
102
|
+
Desktop discovers on its own. Click **Add** once per model the app may use, and
|
|
103
|
+
fill in each entry:
|
|
104
|
+
|
|
105
|
+
- **Model ID** — the full `providerName/model` reference, for example
|
|
106
|
+
`anthropic/claude-sonnet-5`. A bare id such as `claude-sonnet-5` fails to
|
|
107
|
+
route through the gateway.
|
|
108
|
+
- **Display name** — the name shown in the model picker, for example
|
|
109
|
+
`Claude Sonnet 5`. Leave it blank and Claude Desktop formats a name from the
|
|
110
|
+
id.
|
|
111
|
+
- **Offer 1M-context variant** — leave this off unless your provider's
|
|
112
|
+
deployment accepts 1M-token context for the model.
|
|
113
|
+
- **Tier alias** — the Claude tier (Opus, Sonnet, Haiku, or Fable) this model
|
|
114
|
+
stands in for. Setting it pins what the bare alias (for example `opus`)
|
|
115
|
+
resolves to, so tier names route to your prefixed model reference.
|
|
116
|
+
|
|
117
|
+
The first entry is the default selection in the model picker. Include a
|
|
118
|
+
Haiku-tier model — Claude Desktop runs background and sub-agent tasks on a
|
|
119
|
+
small, fast model.
|
|
120
|
+
|
|
121
|
+
The model ids in these examples reflect what was current when this page was
|
|
122
|
+
written, and ids differ across providers — a Bedrock provider serves different
|
|
123
|
+
ids than Anthropic directly, and availability can vary by region. Check
|
|
124
|
+
[your provider's page](../managing-providers.mdx) in the portal for the models
|
|
125
|
+
it currently serves, and use those ids.
|
|
126
|
+
|
|
127
|
+
The app's
|
|
128
|
+
[Model Filtering](../../policies/ai-gateway-model-filtering-v2-inbound.mdx)
|
|
129
|
+
policy controls which models the app may use.
|
|
130
|
+
|
|
131
|
+
### Verify the configuration
|
|
132
|
+
|
|
133
|
+
After Claude Desktop relaunches, send a message in a Chat conversation and start
|
|
134
|
+
a Cowork session. Both requests route through the gateway and count against the
|
|
135
|
+
[app](../managing-apps.mdx) in the Zuplo Portal, as do Code sessions started
|
|
136
|
+
from the desktop app. Terminal Claude Code sessions outside the desktop app use
|
|
137
|
+
a separate configuration — see [Claude Code](./claude-code.mdx).
|
|
138
|
+
|
|
139
|
+
:::caution{title='HTTP 400: model must use "providerName/model"'}
|
|
140
|
+
|
|
141
|
+
If a request fails with `The request body model must use "providerName/model"`,
|
|
142
|
+
the selected model reference is missing its provider prefix. Give every entry in
|
|
143
|
+
the model list a full `providerName/model` reference.
|
|
144
|
+
|
|
145
|
+
:::
|
|
146
|
+
|
|
147
|
+
### Deploy to a fleet
|
|
148
|
+
|
|
149
|
+
To roll the configuration out to many devices, use the configuration window's
|
|
150
|
+
**Export** menu instead of **Apply locally**. It generates a `.mobileconfig`
|
|
151
|
+
profile for macOS MDM tools such as Jamf, a `.reg` policy file for Intune or
|
|
152
|
+
Group Policy, and related deployment artifacts. Managed configuration takes
|
|
153
|
+
precedence over local settings, so users can't point the app at a different
|
|
154
|
+
endpoint. For the deployment workflow and the full configuration key reference,
|
|
155
|
+
see Anthropic's
|
|
156
|
+
[Deploy Claude Desktop with an LLM gateway](https://claude.com/docs/third-party/claude-desktop/gateway)
|
|
157
|
+
and [Deploy with MDM](https://claude.com/docs/third-party/claude-desktop/mdm).
|
|
@@ -174,11 +174,16 @@ will need to do the following:
|
|
|
174
174
|
new OAuthProtectedResourcePlugin({
|
|
175
175
|
authorizationServers: ["https://your-auth0-domain.us.auth0.com"],
|
|
176
176
|
resourceName: "My MCP OAuth Resource",
|
|
177
|
+
scopesSupported: ["openid", "profile", "email", "offline_access"],
|
|
177
178
|
}),
|
|
178
179
|
);
|
|
179
180
|
}
|
|
180
181
|
```
|
|
181
182
|
|
|
183
|
+
`scopesSupported` lists the scopes MCP clients request. Replace the example
|
|
184
|
+
values with the permissions your Auth0 API defines; keep `offline_access` if
|
|
185
|
+
clients need refresh tokens.
|
|
186
|
+
|
|
182
187
|
See the
|
|
183
188
|
[OAuth Protected Resource Plugin docs](../programmable-api/oauth-protected-resource-plugin)
|
|
184
189
|
for more details.
|
|
@@ -50,6 +50,19 @@ Next, you'll need to configure the scopes for your authorization server.
|
|
|
50
50
|
tools").
|
|
51
51
|
5. Check **Set as a default scope** and click **Create**.
|
|
52
52
|
|
|
53
|
+
:::caution{title="Reserved Okta scopes break MCP clients"}
|
|
54
|
+
|
|
55
|
+
Every Okta custom authorization server also publishes `device_sso` and
|
|
56
|
+
`interclient_access` in its `scopes_supported` metadata. The **Include in public
|
|
57
|
+
metadata** toggle for those two scopes is disabled, so you can't remove them. An
|
|
58
|
+
MCP client that builds its scope list from the authorization server's metadata
|
|
59
|
+
requests them together with `mcp:access`, and Okta rejects the authorization
|
|
60
|
+
request with `illegal_scope_combination`. To keep clients off that list, set
|
|
61
|
+
`scopesSupported` on the `OAuthProtectedResourcePlugin` in
|
|
62
|
+
[Configure OAuth on Zuplo](#configure-oauth-on-zuplo) below.
|
|
63
|
+
|
|
64
|
+
:::
|
|
65
|
+
|
|
53
66
|
## Create an OAuth Client Application
|
|
54
67
|
|
|
55
68
|
Next, you'll need to create an OAuth client application for your MCP server.
|
|
@@ -102,7 +115,10 @@ Now create a rule for this policy:
|
|
|
102
115
|
**Device Authorization**
|
|
103
116
|
- **User is**: Select **Any user assigned the app**
|
|
104
117
|
- **Scopes requested**: Select **The following scopes** and choose the scope
|
|
105
|
-
you created for the authorization server (that is, `mcp:access`)
|
|
118
|
+
you created for the authorization server (that is, `mcp:access`) and
|
|
119
|
+
`offline_access`, so clients can obtain refresh tokens. Okta rejects a
|
|
120
|
+
request that includes a scope no rule allows, so this list must cover every
|
|
121
|
+
scope you put in `scopesSupported` later in this guide.
|
|
106
122
|
4. In the **THEN AND** section:
|
|
107
123
|
- **Use this inline hook**: None (disabled)
|
|
108
124
|
- **Access token lifetime is**: Set to desired value (for example, 1 hour)
|
|
@@ -128,7 +144,7 @@ will need to do the following:
|
|
|
128
144
|
"options": {
|
|
129
145
|
"oAuthResourceMetadataEnabled": true,
|
|
130
146
|
"audience": "https://my-gateway.zuplo.dev/mcp",
|
|
131
|
-
"
|
|
147
|
+
"issuerUrl": "https://your-okta-domain.okta.com/oauth2/your-auth-server-id"
|
|
132
148
|
}
|
|
133
149
|
}
|
|
134
150
|
}
|
|
@@ -136,10 +152,10 @@ will need to do the following:
|
|
|
136
152
|
|
|
137
153
|
- Replace `my-gateway.zuplo.dev/mcp` with the audience you defined in your
|
|
138
154
|
authorization server.
|
|
139
|
-
- Replace `your-okta-domain` in the `
|
|
155
|
+
- Replace `your-okta-domain` in the `issuerUrl` field with your actual Okta
|
|
140
156
|
domain.
|
|
141
|
-
- Replace `your-auth-server-id` in the `
|
|
142
|
-
your Okta authorization server.
|
|
157
|
+
- Replace `your-auth-server-id` in the `issuerUrl` field with the actual ID
|
|
158
|
+
of your Okta authorization server.
|
|
143
159
|
|
|
144
160
|
2. Add the OAuth policy to the MCP Server route. For example:
|
|
145
161
|
|
|
@@ -179,19 +195,24 @@ will need to do the following:
|
|
|
179
195
|
"https://your-okta-domain.okta.com/oauth2/your-auth-server-id",
|
|
180
196
|
],
|
|
181
197
|
resourceName: "My MCP OAuth Resource",
|
|
198
|
+
scopesSupported: ["mcp:access", "offline_access"],
|
|
182
199
|
}),
|
|
183
200
|
);
|
|
184
201
|
}
|
|
185
202
|
```
|
|
186
203
|
|
|
187
|
-
- Replace `your-okta-domain` in the `
|
|
188
|
-
domain.
|
|
189
|
-
- Replace `your-auth-server-id` in the `
|
|
190
|
-
your Okta authorization server.
|
|
204
|
+
- Replace `your-okta-domain` in the `authorizationServers` value with your
|
|
205
|
+
actual Okta domain.
|
|
206
|
+
- Replace `your-auth-server-id` in the `authorizationServers` value with the
|
|
207
|
+
actual ID of your Okta authorization server.
|
|
208
|
+
- Set `scopesSupported` to the scopes you created in
|
|
209
|
+
[Configure Scopes](#configure-scopes). Keep `offline_access` if clients
|
|
210
|
+
need refresh tokens. Add `openid`, `profile`, or `email` only if your
|
|
211
|
+
tokens need those claims.
|
|
191
212
|
|
|
192
213
|
This plugin populates the `.well-known` routes for the MCP server auth
|
|
193
214
|
metadata discovery. This enables MCP clients to automatically discover the
|
|
194
|
-
authorization issuer endpoint. See the
|
|
215
|
+
authorization issuer endpoint and the scopes to request. See the
|
|
195
216
|
[OAuth Protected Resource Plugin docs](../programmable-api/oauth-protected-resource-plugin)
|
|
196
217
|
for more details on this runtime plugin.
|
|
197
218
|
|
|
@@ -206,3 +227,20 @@ further instructions on testing your MCP server with `curl`.
|
|
|
206
227
|
|
|
207
228
|
If you need more help debugging, see
|
|
208
229
|
[Testing OAuth on Zuplo](../handlers/mcp-server.mdx#oauth-testing).
|
|
230
|
+
|
|
231
|
+
## Troubleshooting
|
|
232
|
+
|
|
233
|
+
### `illegal_scope_combination` in the Okta system log
|
|
234
|
+
|
|
235
|
+
**Cause:** The MCP client requested `device_sso` or `interclient_access`
|
|
236
|
+
together with your custom scopes. Okta publishes those two scopes in every
|
|
237
|
+
custom authorization server's metadata and can't hide them, and a client that
|
|
238
|
+
finds no scopes in the 401 challenge or the protected resource metadata falls
|
|
239
|
+
back to that list. Okta rejects the combination and grants no scopes.
|
|
240
|
+
|
|
241
|
+
**Fix:** Set `scopesSupported` on the `OAuthProtectedResourcePlugin` to the
|
|
242
|
+
scopes you created, as shown in
|
|
243
|
+
[Configure OAuth on Zuplo](#configure-oauth-on-zuplo), and redeploy. The gateway
|
|
244
|
+
then advertises exactly those scopes in both the 401 challenge and the metadata
|
|
245
|
+
document, so the client never consults the authorization server's list.
|
|
246
|
+
Reconnect the client so it repeats discovery.
|
|
@@ -52,3 +52,37 @@ If you would like to remove the authorization header after you use one of the
|
|
|
52
52
|
authorization policies, simply add the
|
|
53
53
|
[Remove Request Headers](/docs/policies/remove-headers-inbound) policy after the
|
|
54
54
|
authorization policy and set it to remove the `Authorization` header.
|
|
55
|
+
|
|
56
|
+
## OAuth 2.0 Protected Resource Metadata
|
|
57
|
+
|
|
58
|
+
OAuth clients, including MCP clients, discover how to obtain a token for your
|
|
59
|
+
API through
|
|
60
|
+
[OAuth 2.0 Protected Resource Metadata (RFC 9728)](https://datatracker.ietf.org/doc/html/rfc9728).
|
|
61
|
+
Zuplo implements it in two parts:
|
|
62
|
+
|
|
63
|
+
- The
|
|
64
|
+
[`OAuthProtectedResourcePlugin`](../programmable-api/oauth-protected-resource-plugin.mdx)
|
|
65
|
+
serves the metadata document at `/.well-known/oauth-protected-resource` and
|
|
66
|
+
every path beneath it. The document lists your authorization servers, a
|
|
67
|
+
human-readable resource name, and, when you set `scopesSupported`, the scopes
|
|
68
|
+
clients should request.
|
|
69
|
+
- The `oAuthResourceMetadataEnabled` option on the JWT authentication policies
|
|
70
|
+
makes the policy answer requests without a bearer token with a 401 response
|
|
71
|
+
that carries a `WWW-Authenticate` header. The header's `resource_metadata`
|
|
72
|
+
parameter points at the metadata document for that route, and its `scope`
|
|
73
|
+
parameter repeats `scopesSupported` when the plugin sets it.
|
|
74
|
+
|
|
75
|
+
```http
|
|
76
|
+
HTTP/1.1 401 Unauthorized
|
|
77
|
+
WWW-Authenticate: Bearer resource_metadata="https://api.example.com/.well-known/oauth-protected-resource/mcp", scope="mcp:access offline_access"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The
|
|
81
|
+
[MCP authorization specification](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization)
|
|
82
|
+
requires clients to discover the authorization server this way, and tells them
|
|
83
|
+
to take the scopes they request from the challenge first and from the metadata
|
|
84
|
+
document second. Set `scopesSupported` whenever a policy protects an MCP server,
|
|
85
|
+
so clients request the scopes your resource expects instead of every scope the
|
|
86
|
+
authorization server advertises. See
|
|
87
|
+
[Advertising supported scopes](../programmable-api/oauth-protected-resource-plugin.mdx#advertising-supported-scopes)
|
|
88
|
+
for the full order of precedence and an Okta-specific pitfall.
|
|
@@ -412,14 +412,24 @@ enable OAuth authentication on your MCP Server:
|
|
|
412
412
|
new OAuthProtectedResourcePlugin({
|
|
413
413
|
authorizationServers: ["https://your-auth0-domain.us.auth0.com"],
|
|
414
414
|
resourceName: "My MCP OAuth Resource",
|
|
415
|
+
scopesSupported: ["mcp:access", "offline_access"],
|
|
415
416
|
}),
|
|
416
417
|
);
|
|
417
418
|
}
|
|
418
419
|
```
|
|
419
420
|
|
|
421
|
+
`scopesSupported` lists the scopes MCP clients should request. The plugin
|
|
422
|
+
emits it as `scopes_supported` in the metadata document, and the OAuth policy
|
|
423
|
+
adds it as the `scope` parameter of its 401 `WWW-Authenticate` challenge.
|
|
424
|
+
Without it, some clients request every scope the authorization server
|
|
425
|
+
advertises. Replace the example values with the scopes your authorization
|
|
426
|
+
server defines for this resource.
|
|
427
|
+
|
|
420
428
|
See the
|
|
421
429
|
[OAuth Protected Resource Plugin docs](../programmable-api/oauth-protected-resource-plugin.mdx)
|
|
422
|
-
for
|
|
430
|
+
for all options and
|
|
431
|
+
[Advertising supported scopes](../programmable-api/oauth-protected-resource-plugin.mdx#advertising-supported-scopes)
|
|
432
|
+
for how clients pick scopes.
|
|
423
433
|
|
|
424
434
|
### API Key Auth
|
|
425
435
|
|
|
@@ -697,6 +707,12 @@ in the MCP Inspector UI to move to the next step.
|
|
|
697
707
|
canonical URL of your Authorization server, and registered an OAuth policy to
|
|
698
708
|
the route of your MCP server.
|
|
699
709
|
|
|
710
|
+
If the authorization server later rejects the authorization request with a
|
|
711
|
+
scope error (Okta reports `illegal_scope_combination`), the client built its
|
|
712
|
+
scope list from the authorization server's metadata. Set `scopesSupported` on
|
|
713
|
+
the plugin so the client requests only those scopes. See
|
|
714
|
+
[Advertising supported scopes](../programmable-api/oauth-protected-resource-plugin.mdx#advertising-supported-scopes).
|
|
715
|
+
|
|
700
716
|
2. **Client Registration**: The MCP Inspector will try to use
|
|
701
717
|
[Dynamic Client Registration](https://modelcontextprotocol.io/specification/draft/basic/authorization#dynamic-client-registration)
|
|
702
718
|
to register a new client with the Authorization server. Note that not all MCP
|
|
@@ -153,6 +153,8 @@ file-based configuration.
|
|
|
153
153
|
## Related
|
|
154
154
|
|
|
155
155
|
- [Connect MCP clients overview](./overview.mdx)
|
|
156
|
+
- To route Claude Desktop's model requests through Zuplo instead, see the
|
|
157
|
+
[Claude Desktop AI Gateway integration](../../ai-gateway/integrations/claude-desktop.mdx)
|
|
156
158
|
- Anthropic's official guide:
|
|
157
159
|
[Connect to remote MCP servers](https://modelcontextprotocol.io/docs/develop/connect-remote-servers)
|
|
158
160
|
- Anthropic's setup article:
|
|
@@ -1,21 +1,14 @@
|
|
|
1
1
|
# AI Gateway Smart Router
|
|
2
2
|
|
|
3
3
|
Use this policy to classify the last user prompt on Chat Completions, Responses,
|
|
4
|
-
and Anthropic Messages requests.
|
|
5
|
-
|
|
6
|
-
`AIGatewaySmartRouter` for later policies in the same request.
|
|
4
|
+
and Anthropic Messages requests. Using the classification results, configure
|
|
5
|
+
where to route the request based on its complexity.
|
|
7
6
|
|
|
8
|
-
When `smartRoutingEnabled` is true, it overwrites completions routing
|
|
9
|
-
`modelsByComplexity`.
|
|
10
|
-
model is still rejected before classification runs. The classified model then
|
|
11
|
-
replaces that selection.
|
|
7
|
+
When `smartRoutingEnabled` is true, it overwrites completions routing based on
|
|
8
|
+
the configuration in `modelsByComplexity`.
|
|
12
9
|
|
|
13
|
-
Classification
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
> **Loop prevention.** The classifier hop is an `invokeRoute` sub-request. The
|
|
17
|
-
> policy no-ops when `context.parentContext` is set, so the classifier app can
|
|
18
|
-
> share the same route chain without classifying its own request.
|
|
10
|
+
> **Classification failure handling.** If the message classification fails or
|
|
11
|
+
> times out, the request is forwarded to the original model.
|
|
19
12
|
|
|
20
13
|
## Required options
|
|
21
14
|
|
|
@@ -27,10 +20,9 @@ invalid options fail open: the original request is forwarded.
|
|
|
27
20
|
- `modelsByComplexity` — `providerName/model` for each of `low`, `medium`, and
|
|
28
21
|
`high`. Used for routing when `smartRoutingEnabled` is true.
|
|
29
22
|
|
|
30
|
-
Omit `intents` and `classifierPrompt` to use the built-in
|
|
23
|
+
Omit `intents` and `classifierPrompt` to use the built-in dictionary (code,
|
|
31
24
|
summarization, translation, qa, conversation, classification, creative_writing,
|
|
32
|
-
agentic, document_qa, other) and the built-in
|
|
33
|
-
`{{intents}}` in a custom prompt to inject the configured intent list.
|
|
25
|
+
agentic, document_qa, other) and the built-in classification prompt.
|
|
34
26
|
|
|
35
27
|
## Example
|
|
36
28
|
|
|
@@ -67,30 +59,207 @@ filtering already selected a model. Filtering skips when routing is already set,
|
|
|
67
59
|
so putting this policy first would also skip allow-list checks on the client's
|
|
68
60
|
original model.
|
|
69
61
|
|
|
70
|
-
##
|
|
62
|
+
## How classification drives routing
|
|
63
|
+
|
|
64
|
+
Complexity is classified independently of intent, as `low`, `medium`, or `high`.
|
|
65
|
+
Smart Router looks up `modelsByComplexity[complexity]` and applies it only when
|
|
66
|
+
**all** of the following hold:
|
|
67
|
+
|
|
68
|
+
- `smartRoutingEnabled` is `true`.
|
|
69
|
+
- The classified intent is a known one (not capped as an unknown intent).
|
|
70
|
+
- `modelsByComplexity` has a model configured for that complexity.
|
|
71
|
+
- `profile.confidence >= minConfidenceForRouting` (default `0.5`).
|
|
72
|
+
|
|
73
|
+
When routing isn't applied, read `smartRouting.reason` from the result (see
|
|
74
|
+
[Using classification results in custom code](#using-classification-results-in-custom-code))
|
|
75
|
+
to see why: `disabled`, `unknown-intent`, `no-model`, `low-confidence`,
|
|
76
|
+
`internal-error`, or `applied`.
|
|
77
|
+
|
|
78
|
+
### Other advanced options
|
|
79
|
+
|
|
80
|
+
| Option | Default | Purpose |
|
|
81
|
+
| ------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
82
|
+
| `minConfidenceForRouting` | `0.5` | Raise it to route only on confident classifications; lower it to route more aggressively. Unknown intents are always capped just below this value, so they never qualify regardless of the setting. |
|
|
83
|
+
| `classifierTimeoutMs` | `8000` | How long to wait for the classifier before giving up and forwarding the request unclassified. |
|
|
84
|
+
| `maxPromptChars` | `8000` | Truncates the prompt sent to the classifier, to keep classifier cost and latency bounded on very long prompts. |
|
|
85
|
+
|
|
86
|
+
## Advanced configuration
|
|
87
|
+
|
|
88
|
+
Use these options to control what the classifier evaluates and how strongly its
|
|
89
|
+
result influences routing.
|
|
90
|
+
|
|
91
|
+
### Custom intents
|
|
92
|
+
|
|
93
|
+
`intents` replaces the built-in dictionary entirely — it's not additive. Provide
|
|
94
|
+
a non-empty list of `{ id, description }` pairs:
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"intents": [
|
|
99
|
+
{
|
|
100
|
+
"id": "billing",
|
|
101
|
+
"description": "Questions about invoices, payments, or subscription plans."
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"id": "support",
|
|
105
|
+
"description": "Troubleshooting or how-to questions about the product."
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The `id` values become the enum the classifier model must return — the
|
|
112
|
+
classifier calls a strict JSON-schema chat completion, so it can only return one
|
|
113
|
+
of your configured ids. The `description` values are only shown to the
|
|
114
|
+
classifier if your prompt includes `{{intents}}` (see below).
|
|
115
|
+
|
|
116
|
+
If the classifier returns an `id` outside this list, Smart Router keeps it as an
|
|
117
|
+
"unknown intent" and caps its confidence just below `minConfidenceForRouting`,
|
|
118
|
+
so it's never eligible for routing.
|
|
119
|
+
|
|
120
|
+
### Custom classifier prompt
|
|
121
|
+
|
|
122
|
+
`classifierPrompt` replaces the built-in classifier prompt. It accepts either a
|
|
123
|
+
string or an array of lines:
|
|
124
|
+
|
|
125
|
+
```json
|
|
126
|
+
{
|
|
127
|
+
"classifierPrompt": [
|
|
128
|
+
"You are an intent classifier for a billing support bot.",
|
|
129
|
+
"Return JSON only that matches the schema.",
|
|
130
|
+
"Intent (pick the single most specific match):",
|
|
131
|
+
"{{intents}}"
|
|
132
|
+
]
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Include the literal placeholder `{{intents}}` anywhere in the prompt to have it
|
|
137
|
+
replaced with a `- id: description` line for each configured intent (or the
|
|
138
|
+
built-in ones, if `intents` is also omitted). Pairing a custom `intents` list
|
|
139
|
+
with the built-in `classifierPrompt` (by omitting `classifierPrompt` entirely)
|
|
140
|
+
works out of the box, because the built-in prompt already contains
|
|
141
|
+
`{{intents}}`.
|
|
142
|
+
|
|
143
|
+
## Using classification results in custom code
|
|
144
|
+
|
|
145
|
+
`AIGatewaySmartRouter.get(context)` returns the result Smart Router stored on
|
|
146
|
+
the request, or `undefined` if it didn't run (non-AI request, unreadable body,
|
|
147
|
+
or a fail-open error):
|
|
71
148
|
|
|
72
149
|
```typescript
|
|
73
150
|
import { AIGatewaySmartRouter } from "@zuplo/runtime";
|
|
74
151
|
|
|
75
152
|
const result = AIGatewaySmartRouter.get(context);
|
|
76
|
-
|
|
77
|
-
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`result` has this shape:
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
interface AIGatewaySmartRouterResult {
|
|
159
|
+
profile: {
|
|
160
|
+
intent: string;
|
|
161
|
+
complexity: "low" | "medium" | "high";
|
|
162
|
+
confidence: number;
|
|
163
|
+
reasons: string[];
|
|
164
|
+
};
|
|
165
|
+
usage: {
|
|
166
|
+
promptTokens: number;
|
|
167
|
+
completionTokens: number;
|
|
168
|
+
totalTokens: number;
|
|
169
|
+
};
|
|
170
|
+
classifierModel: string;
|
|
171
|
+
routing: { model?: string };
|
|
172
|
+
durationMs: number;
|
|
173
|
+
promptSource: "user" | "prior-user";
|
|
174
|
+
promptLength: number;
|
|
175
|
+
promptTruncated: boolean;
|
|
176
|
+
unknownIntent: boolean;
|
|
177
|
+
smartRouting: {
|
|
178
|
+
enabled: boolean;
|
|
179
|
+
applied: boolean;
|
|
180
|
+
reason:
|
|
181
|
+
| "applied"
|
|
182
|
+
| "disabled"
|
|
183
|
+
| "low-confidence"
|
|
184
|
+
| "no-model"
|
|
185
|
+
| "internal-error"
|
|
186
|
+
| "unknown-intent";
|
|
187
|
+
minConfidence: number;
|
|
188
|
+
};
|
|
78
189
|
}
|
|
79
190
|
```
|
|
80
191
|
|
|
81
|
-
|
|
192
|
+
Read it from any policy placed after Smart Router in the chain. Common uses:
|
|
193
|
+
|
|
194
|
+
- **Branch on intent or complexity** — apply a stricter rate limit, a different
|
|
195
|
+
DLP policy, or a longer timeout for `high` complexity or an `agentic` intent:
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
const result = AIGatewaySmartRouter.get(context);
|
|
199
|
+
if (result?.profile.complexity === "high") {
|
|
200
|
+
// e.g. apply a stricter rate limit or route to a review queue
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
- **Layer business logic on top of `modelsByComplexity`** — for example, cap the
|
|
205
|
+
model for free-tier callers regardless of classified complexity. Read the plan
|
|
206
|
+
from the caller's API key metadata, not from a raw request header (the caller
|
|
207
|
+
controls headers and could set or omit them to bypass the cap). The built-in
|
|
208
|
+
API Key Auth policy puts a key's metadata on `request.user.data` — set `plan`
|
|
209
|
+
there when you create the key, and it lands on every request that key makes:
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
import { AIGatewayModelRouting, AIGatewaySmartRouter } from "@zuplo/runtime";
|
|
213
|
+
|
|
214
|
+
const result = AIGatewaySmartRouter.get(context);
|
|
215
|
+
const isFreeTier = request.user?.data.plan === "free";
|
|
216
|
+
if (result?.profile.complexity === "high" && isFreeTier) {
|
|
217
|
+
await AIGatewayModelRouting.set(context, {
|
|
218
|
+
completions: "openai/gpt-4o-mini",
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
- **Observe why routing wasn't applied** — log when `smartRouting.reason` is
|
|
224
|
+
`low-confidence` or `unknown-intent` to tune `minConfidenceForRouting` or the
|
|
225
|
+
intent taxonomy:
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
const result = AIGatewaySmartRouter.get(context);
|
|
229
|
+
if (result && !result.smartRouting.applied) {
|
|
230
|
+
context.log.info(
|
|
231
|
+
{ reason: result.smartRouting.reason },
|
|
232
|
+
"Smart routing skipped"
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
- **Surface classification for debugging** — add response headers in a
|
|
238
|
+
non-production environment to see what the classifier returned. This runs in
|
|
239
|
+
an outbound policy, so return a new `Response` carrying the headers — mutating
|
|
240
|
+
a cloned `Headers` object alone has no effect on what the caller receives:
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
const result = AIGatewaySmartRouter.get(context);
|
|
244
|
+
if (!result) {
|
|
245
|
+
return response;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const headers = new Headers(response.headers);
|
|
249
|
+
headers.set("x-classified-intent", result.profile.intent);
|
|
250
|
+
headers.set("x-classified-complexity", result.profile.complexity);
|
|
82
251
|
|
|
83
|
-
|
|
84
|
-
|
|
252
|
+
return new Response(response.body, {
|
|
253
|
+
headers,
|
|
254
|
+
status: response.status,
|
|
255
|
+
statusText: response.statusText,
|
|
256
|
+
});
|
|
257
|
+
```
|
|
85
258
|
|
|
86
|
-
|
|
87
|
-
provider is Anthropic or Google (Zuplo translates below the policy chain).
|
|
88
|
-
- `/v1/responses` — OpenAI Responses `input`.
|
|
89
|
-
- `/v1/messages` — native Anthropic `messages[]`.
|
|
259
|
+
## What content is evaluated
|
|
90
260
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
instead. Embeddings and other non-AI paths are skipped.
|
|
261
|
+
The policy reads the last user message in order to classify its intent and
|
|
262
|
+
complexity. Embeddings, tool messages and other non-AI paths are skipped.
|
|
94
263
|
|
|
95
264
|
## Fail-open behavior
|
|
96
265
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
1
|
Classifies the last user prompt by calling a dedicated classifier AI Gateway
|
|
2
2
|
app, stores the result on `AIGatewaySmartRouter` for later policies, and
|
|
3
|
-
optionally routes completions by classified complexity.
|
|
4
|
-
open so the original request still reaches the model.
|
|
3
|
+
optionally routes completions by classified complexity.
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"classifierAppID": {
|
|
59
59
|
"type": "string",
|
|
60
60
|
"title": "Classifier App ID",
|
|
61
|
-
"description": "The AI Gateway application id
|
|
61
|
+
"description": "The AI Gateway application id used to run the classifier prompt and evaluate the user's request.",
|
|
62
62
|
"examples": ["config_1234"]
|
|
63
63
|
},
|
|
64
64
|
"classifierAppApiKey": {
|
|
@@ -70,20 +70,20 @@
|
|
|
70
70
|
"classifierModel": {
|
|
71
71
|
"type": "string",
|
|
72
72
|
"title": "Classifier Model",
|
|
73
|
-
"description": "The `providerName/model`
|
|
73
|
+
"description": "The model (`providerName/model`) the classifier uses to evaluate the user's request. E.g. `openai/gpt-4o-mini`.",
|
|
74
74
|
"pattern": "^[^/\\s]+/.+$",
|
|
75
75
|
"examples": ["openai/gpt-4o-mini"]
|
|
76
76
|
},
|
|
77
77
|
"smartRoutingEnabled": {
|
|
78
78
|
"type": "boolean",
|
|
79
79
|
"title": "Smart Routing Enabled",
|
|
80
|
-
"description": "When true, apply model routing
|
|
80
|
+
"description": "When true, apply model routing based on the models set in `modelsByComplexity` when confidence score meets the `minConfidenceForRouting` threshold. When set to false, classification still runs but model routing is not applied, useful for debugging or testing the classifier prompt.",
|
|
81
81
|
"default": false
|
|
82
82
|
},
|
|
83
83
|
"modelsByComplexity": {
|
|
84
84
|
"type": "object",
|
|
85
85
|
"title": "Models By Complexity",
|
|
86
|
-
"description": "
|
|
86
|
+
"description": "Enables which model will be used based on the classifier results (`low`, `medium` and `high`). This configuration is applied only when `smartRoutingEnabled` is true and confidence score meets the `minConfidenceForRouting` threshold.",
|
|
87
87
|
"additionalProperties": false,
|
|
88
88
|
"required": ["low", "medium", "high"],
|
|
89
89
|
"properties": {
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
"intents": {
|
|
114
114
|
"type": "array",
|
|
115
115
|
"title": "Intents",
|
|
116
|
-
"description": "
|
|
116
|
+
"description": "Dictionary of intents used to classify the user message being evaluated. Omit to use the built-in dictionary (code, summarization, translation, qa, conversation, classification, creative\\_writing, agentic, document\\_qa, other).",
|
|
117
117
|
"minItems": 1,
|
|
118
118
|
"items": {
|
|
119
119
|
"type": "object",
|
|
@@ -123,14 +123,14 @@
|
|
|
123
123
|
"id": {
|
|
124
124
|
"type": "string",
|
|
125
125
|
"title": "Intent ID",
|
|
126
|
-
"description": "
|
|
126
|
+
"description": "Label used to classify the intent of the user message being evaluated.",
|
|
127
127
|
"minLength": 1,
|
|
128
128
|
"examples": ["code"]
|
|
129
129
|
},
|
|
130
130
|
"description": {
|
|
131
131
|
"type": "string",
|
|
132
132
|
"title": "Intent Description",
|
|
133
|
-
"description": "Short description
|
|
133
|
+
"description": "Short description of the intent label.",
|
|
134
134
|
"examples": [
|
|
135
135
|
"Write, edit, refactor, debug, or review source code."
|
|
136
136
|
]
|
|
@@ -140,7 +140,7 @@
|
|
|
140
140
|
},
|
|
141
141
|
"classifierPrompt": {
|
|
142
142
|
"title": "Classifier Prompt",
|
|
143
|
-
"description": "
|
|
143
|
+
"description": "Prompt used to analyze and classify the user message. Omit to use the built-in classifier prompt.",
|
|
144
144
|
"oneOf": [
|
|
145
145
|
{
|
|
146
146
|
"type": "string",
|
|
@@ -167,7 +167,7 @@
|
|
|
167
167
|
"classifierTimeoutMs": {
|
|
168
168
|
"type": "integer",
|
|
169
169
|
"title": "Classifier Timeout (ms)",
|
|
170
|
-
"description": "
|
|
170
|
+
"description": "Timeout configured for the classifier task. When exceeded, the request is forwarded without classification to the original model.",
|
|
171
171
|
"minimum": 1,
|
|
172
172
|
"maximum": 120000,
|
|
173
173
|
"default": 8000,
|
|
@@ -176,7 +176,7 @@
|
|
|
176
176
|
"maxPromptChars": {
|
|
177
177
|
"type": "integer",
|
|
178
178
|
"title": "Max Prompt Characters",
|
|
179
|
-
"description": "Maximum characters of user
|
|
179
|
+
"description": "Maximum characters of user message sent to the classifier. Longer messages get truncated.",
|
|
180
180
|
"minimum": 1,
|
|
181
181
|
"default": 8000,
|
|
182
182
|
"x-advanced": true
|
|
@@ -121,5 +121,10 @@ When configured, this enables OAuth clients to find metadata information about
|
|
|
121
121
|
how to interact with your OAuth 2.0 protected resources according to
|
|
122
122
|
[`RFC 9728`](https://datatracker.ietf.org/doc/html/rfc9728).
|
|
123
123
|
|
|
124
|
+
When the plugin is configured with `scopesSupported`, the 401 response also
|
|
125
|
+
lists those scopes in the `scope` parameter of its `WWW-Authenticate` header, so
|
|
126
|
+
MCP clients request exactly the scopes your resource expects instead of every
|
|
127
|
+
scope the authorization server advertises.
|
|
128
|
+
|
|
124
129
|
See [this document](/docs/articles/oauth-authentication) for more information
|
|
125
130
|
about OAuth authorization in Zuplo.
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"type": "boolean",
|
|
57
57
|
"default": false,
|
|
58
58
|
"x-advanced": true,
|
|
59
|
-
"description": "
|
|
59
|
+
"description": "Enables OAuth 2.0 Protected Resource Metadata discovery (RFC 9728). When `true`, requests without a bearer token receive a 401 whose `WWW-Authenticate` header points `resource_metadata` at the `/.well-known/oauth-protected-resource` document for the request path and, when the `OAuthProtectedResourcePlugin` declares `scopesSupported`, lists those scopes in `scope`. Requires the `OAuthProtectedResourcePlugin` in `zuplo.runtime.ts` or a user-defined route at that path."
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
}
|
|
@@ -81,5 +81,10 @@ When configured, this enables OAuth clients to find metadata information about
|
|
|
81
81
|
how to interact with your OAuth 2.0 protected resources according to
|
|
82
82
|
[`RFC 9728`](https://datatracker.ietf.org/doc/html/rfc9728).
|
|
83
83
|
|
|
84
|
+
When the plugin is configured with `scopesSupported`, the 401 response also
|
|
85
|
+
lists those scopes in the `scope` parameter of its `WWW-Authenticate` header, so
|
|
86
|
+
MCP clients request exactly the scopes your resource expects instead of every
|
|
87
|
+
scope the authorization server advertises.
|
|
88
|
+
|
|
84
89
|
See [this document](/docs/articles/oauth-authentication) for more information
|
|
85
90
|
about OAuth authorization in Zuplo.
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"type": "boolean",
|
|
52
52
|
"default": false,
|
|
53
53
|
"x-advanced": true,
|
|
54
|
-
"description": "
|
|
54
|
+
"description": "Enables OAuth 2.0 Protected Resource Metadata discovery (RFC 9728). When `true`, requests without a bearer token receive a 401 whose `WWW-Authenticate` header points `resource_metadata` at the `/.well-known/oauth-protected-resource` document for the request path and, when the `OAuthProtectedResourcePlugin` declares `scopesSupported`, lists those scopes in `scope`. Requires the `OAuthProtectedResourcePlugin` in `zuplo.runtime.ts` or a user-defined route at that path."
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
## OAuth 2.0 Protected Resource Metadata
|
|
2
|
+
|
|
3
|
+
The Cognito JWT Auth policy supports OAuth protected resource metadata
|
|
4
|
+
discovery. To enable this feature, set the `oAuthResourceMetadataEnabled` option
|
|
5
|
+
to `true` and add the
|
|
6
|
+
[`OAuthProtectedResourcePlugin` to `modules/zuplo.runtime.ts`](/docs/programmable-api/oauth-protected-resource-plugin).
|
|
7
|
+
When configured, this enables OAuth clients to find metadata information about
|
|
8
|
+
how to interact with your OAuth 2.0 protected resources according to
|
|
9
|
+
[`RFC 9728`](https://datatracker.ietf.org/doc/html/rfc9728).
|
|
10
|
+
|
|
11
|
+
When the plugin is configured with `scopesSupported`, the 401 response also
|
|
12
|
+
lists those scopes in the `scope` parameter of its `WWW-Authenticate` header, so
|
|
13
|
+
MCP clients request exactly the scopes your resource expects instead of every
|
|
14
|
+
scope the authorization server advertises.
|
|
15
|
+
|
|
16
|
+
See [this document](/docs/articles/oauth-authentication) for more information
|
|
17
|
+
about OAuth authorization in Zuplo.
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"type": "boolean",
|
|
57
57
|
"default": false,
|
|
58
58
|
"x-advanced": true,
|
|
59
|
-
"description": "
|
|
59
|
+
"description": "Enables OAuth 2.0 Protected Resource Metadata discovery (RFC 9728). When `true`, requests without a bearer token receive a 401 whose `WWW-Authenticate` header points `resource_metadata` at the `/.well-known/oauth-protected-resource` document for the request path and, when the `OAuthProtectedResourcePlugin` declares `scopesSupported`, lists those scopes in `scope`. Requires the `OAuthProtectedResourcePlugin` in `zuplo.runtime.ts` or a user-defined route at that path."
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
## OAuth 2.0 Protected Resource Metadata
|
|
2
|
+
|
|
3
|
+
The Firebase JWT Auth policy supports OAuth protected resource metadata
|
|
4
|
+
discovery. To enable this feature, set the `oAuthResourceMetadataEnabled` option
|
|
5
|
+
to `true` and add the
|
|
6
|
+
[`OAuthProtectedResourcePlugin` to `modules/zuplo.runtime.ts`](/docs/programmable-api/oauth-protected-resource-plugin).
|
|
7
|
+
When configured, this enables OAuth clients to find metadata information about
|
|
8
|
+
how to interact with your OAuth 2.0 protected resources according to
|
|
9
|
+
[`RFC 9728`](https://datatracker.ietf.org/doc/html/rfc9728).
|
|
10
|
+
|
|
11
|
+
When the plugin is configured with `scopesSupported`, the 401 response also
|
|
12
|
+
lists those scopes in the `scope` parameter of its `WWW-Authenticate` header, so
|
|
13
|
+
MCP clients request exactly the scopes your resource expects instead of every
|
|
14
|
+
scope the authorization server advertises.
|
|
15
|
+
|
|
16
|
+
See [this document](/docs/articles/oauth-authentication) for more information
|
|
17
|
+
about OAuth authorization in Zuplo.
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"type": "boolean",
|
|
52
52
|
"default": false,
|
|
53
53
|
"x-advanced": true,
|
|
54
|
-
"description": "
|
|
54
|
+
"description": "Enables OAuth 2.0 Protected Resource Metadata discovery (RFC 9728). When `true`, requests without a bearer token receive a 401 whose `WWW-Authenticate` header points `resource_metadata` at the `/.well-known/oauth-protected-resource` document for the request path and, when the `OAuthProtectedResourcePlugin` declares `scopesSupported`, lists those scopes in `scope`. Requires the `OAuthProtectedResourcePlugin` in `zuplo.runtime.ts` or a user-defined route at that path."
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
## OAuth 2.0 Protected Resource Metadata
|
|
2
|
+
|
|
3
|
+
The Okta JWT Auth policy supports OAuth protected resource metadata discovery.
|
|
4
|
+
To enable this feature, set the `oAuthResourceMetadataEnabled` option to `true`
|
|
5
|
+
and add the
|
|
6
|
+
[`OAuthProtectedResourcePlugin` to `modules/zuplo.runtime.ts`](/docs/programmable-api/oauth-protected-resource-plugin).
|
|
7
|
+
When configured, this enables OAuth clients to find metadata information about
|
|
8
|
+
how to interact with your OAuth 2.0 protected resources according to
|
|
9
|
+
[`RFC 9728`](https://datatracker.ietf.org/doc/html/rfc9728).
|
|
10
|
+
|
|
11
|
+
When the plugin is configured with `scopesSupported`, the 401 response also
|
|
12
|
+
lists those scopes in the `scope` parameter of its `WWW-Authenticate` header, so
|
|
13
|
+
MCP clients request exactly the scopes your resource expects instead of every
|
|
14
|
+
scope the authorization server advertises.
|
|
15
|
+
|
|
16
|
+
Okta custom authorization servers always advertise the `device_sso` and
|
|
17
|
+
`interclient_access` scopes in their own metadata and reject authorization
|
|
18
|
+
requests that combine them with your scopes (`illegal_scope_combination`). Set
|
|
19
|
+
`scopesSupported` on the plugin when this policy protects an MCP server, so
|
|
20
|
+
clients never fall back to that list.
|
|
21
|
+
|
|
22
|
+
See [this document](/docs/articles/oauth-authentication) for more information
|
|
23
|
+
about OAuth authorization in Zuplo.
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"type": "boolean",
|
|
57
57
|
"default": false,
|
|
58
58
|
"x-advanced": true,
|
|
59
|
-
"description": "
|
|
59
|
+
"description": "Enables OAuth 2.0 Protected Resource Metadata discovery (RFC 9728). When `true`, requests without a bearer token receive a 401 whose `WWW-Authenticate` header points `resource_metadata` at the `/.well-known/oauth-protected-resource` document for the request path and, when the `OAuthProtectedResourcePlugin` declares `scopesSupported`, lists those scopes in `scope`. Requires the `OAuthProtectedResourcePlugin` in `zuplo.runtime.ts` or a user-defined route at that path."
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
}
|
|
@@ -56,3 +56,21 @@ export async function myHandler(request: ZuploRequest, context: ZuploContext) {
|
|
|
56
56
|
For a complete example of using the user object in a
|
|
57
57
|
[RequestHandler](../handlers/custom-handler.md), see
|
|
58
58
|
[Setting up JWT auth with Auth0](../policies/auth0-jwt-auth-inbound.md).
|
|
59
|
+
|
|
60
|
+
## OAuth 2.0 Protected Resource Metadata
|
|
61
|
+
|
|
62
|
+
The OpenID JWT Auth policy supports OAuth protected resource metadata discovery.
|
|
63
|
+
To enable this feature, set the `oAuthResourceMetadataEnabled` option to `true`
|
|
64
|
+
and add the
|
|
65
|
+
[`OAuthProtectedResourcePlugin` to `modules/zuplo.runtime.ts`](/docs/programmable-api/oauth-protected-resource-plugin).
|
|
66
|
+
When configured, this enables OAuth clients to find metadata information about
|
|
67
|
+
how to interact with your OAuth 2.0 protected resources according to
|
|
68
|
+
[`RFC 9728`](https://datatracker.ietf.org/doc/html/rfc9728).
|
|
69
|
+
|
|
70
|
+
When the plugin is configured with `scopesSupported`, the 401 response also
|
|
71
|
+
lists those scopes in the `scope` parameter of its `WWW-Authenticate` header, so
|
|
72
|
+
MCP clients request exactly the scopes your resource expects instead of every
|
|
73
|
+
scope the authorization server advertises.
|
|
74
|
+
|
|
75
|
+
See [this document](/docs/articles/oauth-authentication) for more information
|
|
76
|
+
about OAuth authorization in Zuplo.
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
"type": "boolean",
|
|
90
90
|
"default": false,
|
|
91
91
|
"x-advanced": true,
|
|
92
|
-
"description": "
|
|
92
|
+
"description": "Enables OAuth 2.0 Protected Resource Metadata discovery (RFC 9728). When `true`, requests without a bearer token receive a 401 whose `WWW-Authenticate` header points `resource_metadata` at the `/.well-known/oauth-protected-resource` document for the request path and, when the `OAuthProtectedResourcePlugin` declares `scopesSupported`, lists those scopes in `scope`. Requires the `OAuthProtectedResourcePlugin` in `zuplo.runtime.ts` or a user-defined route at that path."
|
|
93
93
|
}
|
|
94
94
|
},
|
|
95
95
|
"examples": [
|
|
@@ -84,5 +84,10 @@ When configured, this enables OAuth clients to find metadata information about
|
|
|
84
84
|
how to interact with your OAuth 2.0 protected resources according to
|
|
85
85
|
[`RFC 9728`](https://datatracker.ietf.org/doc/html/rfc9728).
|
|
86
86
|
|
|
87
|
+
When the plugin is configured with `scopesSupported`, the 401 response also
|
|
88
|
+
lists those scopes in the `scope` parameter of its `WWW-Authenticate` header, so
|
|
89
|
+
MCP clients request exactly the scopes your resource expects instead of every
|
|
90
|
+
scope the authorization server advertises.
|
|
91
|
+
|
|
87
92
|
See [this document](/docs/articles/oauth-authentication) for more information
|
|
88
93
|
about OAuth authorization in Zuplo.
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"type": "boolean",
|
|
57
57
|
"default": false,
|
|
58
58
|
"x-advanced": true,
|
|
59
|
-
"description": "
|
|
59
|
+
"description": "Enables OAuth 2.0 Protected Resource Metadata discovery (RFC 9728). When `true`, requests without a bearer token receive a 401 whose `WWW-Authenticate` header points `resource_metadata` at the `/.well-known/oauth-protected-resource` document for the request path and, when the `OAuthProtectedResourcePlugin` declares `scopesSupported`, lists those scopes in `scope`. Requires the `OAuthProtectedResourcePlugin` in `zuplo.runtime.ts` or a user-defined route at that path."
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
}
|
|
@@ -27,3 +27,8 @@ to `true` and add the
|
|
|
27
27
|
When configured, this enables OAuth clients to find metadata information about
|
|
28
28
|
how to interact with your OAuth 2.0 protected resources according to
|
|
29
29
|
[`RFC 9728`](https://datatracker.ietf.org/doc/html/rfc9728).
|
|
30
|
+
|
|
31
|
+
When the plugin is configured with `scopesSupported`, the 401 response also
|
|
32
|
+
lists those scopes in the `scope` parameter of its `WWW-Authenticate` header, so
|
|
33
|
+
MCP clients request exactly the scopes your resource expects instead of every
|
|
34
|
+
scope the authorization server advertises.
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"type": "boolean",
|
|
67
67
|
"default": false,
|
|
68
68
|
"x-advanced": true,
|
|
69
|
-
"description": "
|
|
69
|
+
"description": "Enables OAuth 2.0 Protected Resource Metadata discovery (RFC 9728). When `true`, requests without a bearer token receive a 401 whose `WWW-Authenticate` header points `resource_metadata` at the `/.well-known/oauth-protected-resource` document for the request path and, when the `OAuthProtectedResourcePlugin` declares `scopesSupported`, lists those scopes in `scope`. Requires the `OAuthProtectedResourcePlugin` in `zuplo.runtime.ts` or a user-defined route at that path."
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
}
|
|
@@ -13,13 +13,16 @@ more details.
|
|
|
13
13
|
|
|
14
14
|
## Usage
|
|
15
15
|
|
|
16
|
-
This runtime plugin
|
|
17
|
-
|
|
16
|
+
This runtime plugin registers the `.well-known/oauth-protected-resource` route
|
|
17
|
+
on your behalf, along with every path beneath it, such as
|
|
18
|
+
`/.well-known/oauth-protected-resource/mcp`. If you configure an
|
|
18
19
|
[OAuth Policy](../articles/oauth-authentication.mdx) on a route with the
|
|
19
|
-
`oAuthResourceMetadataEnabled` option set to `true`,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
`.well-known/oauth-protected-resource` endpoint.
|
|
20
|
+
`oAuthResourceMetadataEnabled` option set to `true`, the policy answers requests
|
|
21
|
+
that carry no bearer token with a 401 response and a `WWW-Authenticate` header.
|
|
22
|
+
The header's `resource_metadata` parameter is the URL of the
|
|
23
|
+
`.well-known/oauth-protected-resource` endpoint for that route. When you set
|
|
24
|
+
`scopesSupported`, the header also carries a `scope` parameter that lists those
|
|
25
|
+
scopes.
|
|
23
26
|
|
|
24
27
|
```ts
|
|
25
28
|
import {
|
|
@@ -32,6 +35,7 @@ export function runtimeInit(runtime: RuntimeExtensions) {
|
|
|
32
35
|
new OAuthProtectedResourcePlugin({
|
|
33
36
|
authorizationServers: ["https://your-auth0-domain.us.auth0.com"],
|
|
34
37
|
resourceName: "My MCP OAuth Resource",
|
|
38
|
+
scopesSupported: ["mcp:access", "offline_access"],
|
|
35
39
|
}),
|
|
36
40
|
);
|
|
37
41
|
}
|
|
@@ -44,3 +48,71 @@ a human readable name for the resource.
|
|
|
44
48
|
Note that the `.well-known/oauth-protected-resource` endpoint explicitly has a
|
|
45
49
|
CORS policy of `anything-goes` since this is a public endpoint that should be
|
|
46
50
|
accessible to anyone to check the server's OAuth configuration.
|
|
51
|
+
|
|
52
|
+
## Options
|
|
53
|
+
|
|
54
|
+
Construct the plugin inside `runtimeInit`. The plugin validates its options when
|
|
55
|
+
you construct it, so an invalid value fails at startup with a
|
|
56
|
+
`ConfigurationError` instead of failing a client's first login.
|
|
57
|
+
|
|
58
|
+
| Option | Type | Description |
|
|
59
|
+
| ---------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
60
|
+
| `authorizationServers` | `string[]` | Canonical issuer URLs of the authorization servers that issue tokens for this resource. Each should comply with [RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414). Emitted as `authorization_servers`. |
|
|
61
|
+
| `resourceName` | `string` | Human-readable name of the resource, intended for display to end users. RFC 9728 recommends setting it. Emitted as `resource_name`. |
|
|
62
|
+
| `scopesSupported` | `string[]` | Scopes that clients should request when they obtain an access token for this resource. Emitted as `scopes_supported`, and as the `scope` parameter of the 401 `WWW-Authenticate` header by OAuth policies that have `oAuthResourceMetadataEnabled` set. Each entry must be one OAuth scope token ([RFC 6749 section 3.3](https://datatracker.ietf.org/doc/html/rfc6749#section-3.3)): printable ASCII with no spaces, double quotes, or backslashes. Use one entry per scope, never a space-delimited string. An empty array is rejected; omit the option to advertise no scopes. |
|
|
63
|
+
|
|
64
|
+
## Advertising supported scopes
|
|
65
|
+
|
|
66
|
+
An MCP client has to decide which scopes to request from the authorization
|
|
67
|
+
server. The
|
|
68
|
+
[MCP authorization specification](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization#scope-selection-strategy)
|
|
69
|
+
tells clients to consider these sources in order:
|
|
70
|
+
|
|
71
|
+
1. **The `scope` parameter of the `WWW-Authenticate` challenge** on the 401
|
|
72
|
+
response. Clients treat these scopes as authoritative.
|
|
73
|
+
2. **The `scopes_supported` array in the protected resource metadata** document,
|
|
74
|
+
when the challenge has no `scope` parameter.
|
|
75
|
+
3. **The `scopes_supported` array in the authorization server's own metadata.**
|
|
76
|
+
The specification doesn't list this source, but some clients fall back to it
|
|
77
|
+
when the first two are empty. It usually contains every scope the server
|
|
78
|
+
knows about.
|
|
79
|
+
4. **No `scope` parameter at all.**
|
|
80
|
+
|
|
81
|
+
Setting `scopesSupported` fills the first two sources with the same list, so no
|
|
82
|
+
client reaches the third. For example, a plugin configured with an Okta
|
|
83
|
+
authorization server, `resourceName: "Acme MCP"`, and
|
|
84
|
+
`scopesSupported: ["mcp:access", "offline_access"]` serves this document at
|
|
85
|
+
`/.well-known/oauth-protected-resource/mcp`:
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"resource": "https://api.example.com/mcp",
|
|
90
|
+
"authorization_servers": ["https://acme.okta.com/oauth2/aus1a2b3c"],
|
|
91
|
+
"resource_name": "Acme MCP",
|
|
92
|
+
"scopes_supported": ["mcp:access", "offline_access"]
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
A request to `/mcp` without a bearer token receives:
|
|
97
|
+
|
|
98
|
+
```http
|
|
99
|
+
HTTP/1.1 401 Unauthorized
|
|
100
|
+
WWW-Authenticate: Bearer resource_metadata="https://api.example.com/.well-known/oauth-protected-resource/mcp", scope="mcp:access offline_access"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
If you don't set `scopesSupported`, the document has no `scopes_supported` key
|
|
104
|
+
and the header has no `scope` parameter. Requests that carry an invalid or
|
|
105
|
+
expired token receive a 401 without the `WWW-Authenticate` header in either
|
|
106
|
+
case.
|
|
107
|
+
|
|
108
|
+
:::caution{title="Okta rejects its own default scopes"}
|
|
109
|
+
|
|
110
|
+
Okta custom authorization servers always publish `device_sso` and
|
|
111
|
+
`interclient_access` in their `scopes_supported` metadata. The **Include in
|
|
112
|
+
public metadata** toggle for those two scopes is disabled, so you can't remove
|
|
113
|
+
them. A client that falls back to that list requests them together with your
|
|
114
|
+
scopes, and Okta rejects the authorization request with
|
|
115
|
+
`illegal_scope_combination` and grants no scopes. Set `scopesSupported` whenever
|
|
116
|
+
Okta is your authorization server.
|
|
117
|
+
|
|
118
|
+
:::
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zuplo",
|
|
3
|
-
"version": "7.6.
|
|
3
|
+
"version": "7.6.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The official Zuplo CLI for local development and platform management",
|
|
6
6
|
"homepage": "https://zuplo.com/docs/cli/overview",
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
"zuplo": "zuplo.js"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@zuplo/cli": "7.6.
|
|
36
|
-
"@zuplo/core": "7.6.
|
|
37
|
-
"@zuplo/runtime": "7.6.
|
|
38
|
-
"@zuplo/test": "7.6.
|
|
35
|
+
"@zuplo/cli": "7.6.10",
|
|
36
|
+
"@zuplo/core": "7.6.10",
|
|
37
|
+
"@zuplo/runtime": "7.6.10",
|
|
38
|
+
"@zuplo/test": "7.6.10"
|
|
39
39
|
}
|
|
40
40
|
}
|