zuplo 6.70.53 → 6.70.55
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/mcp-gateway/auth/configuring-auth0.mdx +216 -0
- package/docs/mcp-gateway/auth/configuring-clerk.mdx +153 -0
- package/docs/mcp-gateway/auth/configuring-cognito.mdx +128 -0
- package/docs/mcp-gateway/auth/configuring-entra.mdx +134 -0
- package/docs/mcp-gateway/auth/configuring-generic-oidc.mdx +242 -0
- package/docs/mcp-gateway/auth/configuring-google.mdx +117 -0
- package/docs/mcp-gateway/auth/configuring-keycloak.mdx +125 -0
- package/docs/mcp-gateway/auth/configuring-logto.mdx +116 -0
- package/docs/mcp-gateway/auth/configuring-okta.mdx +199 -0
- package/docs/mcp-gateway/auth/configuring-onelogin.mdx +122 -0
- package/docs/mcp-gateway/auth/configuring-ping.mdx +157 -0
- package/docs/mcp-gateway/auth/configuring-workos.mdx +117 -0
- package/docs/mcp-gateway/auth/manual-oauth-testing.mdx +528 -0
- package/docs/mcp-gateway/auth/overview.mdx +314 -0
- package/docs/mcp-gateway/auth/upstream-oauth.mdx +221 -0
- package/docs/mcp-gateway/capability-filtering.mdx +162 -0
- package/docs/mcp-gateway/code-config/compatibility-dates.mdx +33 -0
- package/docs/mcp-gateway/code-config/local-development.mdx +198 -0
- package/docs/mcp-gateway/code-config/mcp-proxy-handler.mdx +186 -0
- package/docs/mcp-gateway/code-config/multi-upstream.mdx +293 -0
- package/docs/mcp-gateway/code-config/overview.mdx +210 -0
- package/docs/mcp-gateway/connect-clients/chatgpt.mdx +127 -0
- package/docs/mcp-gateway/connect-clients/claude-code.mdx +184 -0
- package/docs/mcp-gateway/connect-clients/claude-desktop.mdx +160 -0
- package/docs/mcp-gateway/connect-clients/cursor.mdx +100 -0
- package/docs/mcp-gateway/connect-clients/other-clients.mdx +207 -0
- package/docs/mcp-gateway/connect-clients/overview.mdx +137 -0
- package/docs/mcp-gateway/connect-clients/vs-code.mdx +128 -0
- package/docs/mcp-gateway/how-it-works.mdx +266 -0
- package/docs/mcp-gateway/how-to/connect-upstream-oauth.mdx +268 -0
- package/docs/mcp-gateway/how-to/curate-tools.mdx +278 -0
- package/docs/mcp-gateway/introduction.mdx +151 -0
- package/docs/mcp-gateway/observability/analytics.mdx +191 -0
- package/docs/mcp-gateway/observability/logging.mdx +191 -0
- package/docs/mcp-gateway/quickstart.mdx +266 -0
- package/docs/mcp-gateway/reference.mdx +148 -0
- package/docs/mcp-gateway/test-clients.mdx +130 -0
- package/docs/mcp-gateway/troubleshooting.mdx +228 -0
- package/docs/mcp-server/introduction.mdx +10 -0
- package/docs/mcp-server/openai-apps-sdk.mdx +12 -0
- package/docs/policies/_index.md +14 -0
- package/docs/policies/akamai-ai-firewall/schema.json +1 -0
- package/docs/policies/akamai-firewall-for-ai-inbound/schema.json +1 -0
- package/docs/policies/akamai-firewall-for-ai-outbound/schema.json +1 -0
- package/docs/policies/amberflo-metering-inbound/schema.json +1 -0
- package/docs/policies/api-key-inbound/schema.json +1 -0
- package/docs/policies/audit-log-inbound/schema.json +1 -0
- package/docs/policies/auth0-jwt-auth-inbound/schema.json +1 -0
- package/docs/policies/authzen-inbound/schema.json +1 -0
- package/docs/policies/axiomatics-authz-inbound/schema.json +1 -0
- package/docs/policies/basic-auth-inbound/schema.json +1 -0
- package/docs/policies/bot-detection-inbound/schema.json +1 -0
- package/docs/policies/brownout-inbound/schema.json +1 -0
- package/docs/policies/caching-inbound/schema.json +1 -0
- package/docs/policies/change-method-inbound/schema.json +1 -0
- package/docs/policies/clear-headers-inbound/schema.json +1 -0
- package/docs/policies/clear-headers-outbound/schema.json +1 -0
- package/docs/policies/clerk-jwt-auth-inbound/schema.json +1 -0
- package/docs/policies/cognito-jwt-auth-inbound/schema.json +1 -0
- package/docs/policies/comet-opik-tracing-inbound/schema.json +1 -0
- package/docs/policies/complex-rate-limit-inbound/schema.json +1 -0
- package/docs/policies/composite-inbound/schema.json +1 -0
- package/docs/policies/composite-outbound/schema.json +1 -0
- package/docs/policies/curity-phantom-token-inbound/schema.json +1 -0
- package/docs/policies/firebase-jwt-inbound/schema.json +1 -0
- package/docs/policies/formdata-to-json-inbound/schema.json +1 -0
- package/docs/policies/galileo-tracing-inbound/schema.json +1 -0
- package/docs/policies/geo-filter-inbound/schema.json +1 -0
- package/docs/policies/graphql-complexity-limit-inbound/schema.json +1 -0
- package/docs/policies/graphql-disable-introspection-inbound/schema.json +1 -0
- package/docs/policies/graphql-introspection-filter-outbound/schema.json +1 -0
- package/docs/policies/http-deprecation-outbound/schema.json +1 -0
- package/docs/policies/jwt-scopes-inbound/schema.json +1 -0
- package/docs/policies/ldap-auth-inbound/schema.json +1 -0
- package/docs/policies/mcp-auth0-oauth-inbound/doc.md +54 -0
- package/docs/policies/mcp-auth0-oauth-inbound/intro.md +7 -0
- package/docs/policies/mcp-auth0-oauth-inbound/schema.json +135 -0
- package/docs/policies/mcp-capability-filter-inbound/doc.md +58 -0
- package/docs/policies/mcp-capability-filter-inbound/intro.md +9 -0
- package/docs/policies/mcp-capability-filter-inbound/schema.json +212 -0
- package/docs/policies/mcp-clerk-oauth-inbound/doc.md +34 -0
- package/docs/policies/mcp-clerk-oauth-inbound/intro.md +1 -0
- package/docs/policies/mcp-clerk-oauth-inbound/schema.json +134 -0
- package/docs/policies/mcp-cognito-oauth-inbound/doc.md +52 -0
- package/docs/policies/mcp-cognito-oauth-inbound/intro.md +7 -0
- package/docs/policies/mcp-cognito-oauth-inbound/schema.json +152 -0
- package/docs/policies/mcp-entra-oauth-inbound/doc.md +51 -0
- package/docs/policies/mcp-entra-oauth-inbound/intro.md +6 -0
- package/docs/policies/mcp-entra-oauth-inbound/schema.json +131 -0
- package/docs/policies/mcp-google-oauth-inbound/doc.md +52 -0
- package/docs/policies/mcp-google-oauth-inbound/intro.md +6 -0
- package/docs/policies/mcp-google-oauth-inbound/schema.json +125 -0
- package/docs/policies/mcp-keycloak-oauth-inbound/doc.md +43 -0
- package/docs/policies/mcp-keycloak-oauth-inbound/intro.md +2 -0
- package/docs/policies/mcp-keycloak-oauth-inbound/schema.json +140 -0
- package/docs/policies/mcp-logto-oauth-inbound/doc.md +52 -0
- package/docs/policies/mcp-logto-oauth-inbound/intro.md +6 -0
- package/docs/policies/mcp-logto-oauth-inbound/schema.json +131 -0
- package/docs/policies/mcp-oauth-inbound/doc.md +70 -0
- package/docs/policies/mcp-oauth-inbound/intro.md +11 -0
- package/docs/policies/mcp-oauth-inbound/schema.json +177 -0
- package/docs/policies/mcp-okta-oauth-inbound/doc.md +61 -0
- package/docs/policies/mcp-okta-oauth-inbound/intro.md +7 -0
- package/docs/policies/mcp-okta-oauth-inbound/schema.json +137 -0
- package/docs/policies/mcp-onelogin-oauth-inbound/doc.md +50 -0
- package/docs/policies/mcp-onelogin-oauth-inbound/intro.md +6 -0
- package/docs/policies/mcp-onelogin-oauth-inbound/schema.json +131 -0
- package/docs/policies/mcp-ping-oauth-inbound/doc.md +80 -0
- package/docs/policies/mcp-ping-oauth-inbound/intro.md +7 -0
- package/docs/policies/mcp-ping-oauth-inbound/schema.json +151 -0
- package/docs/policies/mcp-token-exchange-inbound/doc.md +135 -0
- package/docs/policies/mcp-token-exchange-inbound/intro.md +6 -0
- package/docs/policies/mcp-token-exchange-inbound/schema.json +134 -0
- package/docs/policies/mcp-workos-oauth-inbound/doc.md +50 -0
- package/docs/policies/mcp-workos-oauth-inbound/intro.md +6 -0
- package/docs/policies/mcp-workos-oauth-inbound/schema.json +125 -0
- package/docs/policies/mock-api-inbound/schema.json +1 -0
- package/docs/policies/moesif-inbound/schema.json +1 -0
- package/docs/policies/monetization-inbound/schema.json +1 -0
- package/docs/policies/mtls-auth-inbound/schema.json +1 -0
- package/docs/policies/okta-fga-authz-inbound/schema.json +1 -0
- package/docs/policies/okta-jwt-auth-inbound/schema.json +1 -0
- package/docs/policies/open-id-jwt-auth-inbound/schema.json +1 -0
- package/docs/policies/openfga-authz-inbound/schema.json +1 -0
- package/docs/policies/openmeter-inbound/schema.json +1 -0
- package/docs/policies/prompt-injection-outbound/schema.json +1 -0
- package/docs/policies/propel-auth-jwt-inbound/schema.json +1 -0
- package/docs/policies/query-param-to-header-inbound/schema.json +1 -0
- package/docs/policies/quota-inbound/schema.json +1 -0
- package/docs/policies/rate-limit-inbound/schema.json +1 -0
- package/docs/policies/readme-metrics-inbound/schema.json +1 -0
- package/docs/policies/remove-headers-inbound/schema.json +1 -0
- package/docs/policies/remove-headers-outbound/schema.json +1 -0
- package/docs/policies/remove-query-params-inbound/schema.json +1 -0
- package/docs/policies/replace-string-outbound/schema.json +1 -0
- package/docs/policies/request-size-limit-inbound/schema.json +1 -0
- package/docs/policies/request-validation-inbound/schema.json +1 -0
- package/docs/policies/require-origin-inbound/schema.json +1 -0
- package/docs/policies/secret-masking-outbound/schema.json +1 -0
- package/docs/policies/semantic-cache-inbound/schema.json +1 -0
- package/docs/policies/set-body-inbound/schema.json +1 -0
- package/docs/policies/set-headers-inbound/schema.json +1 -0
- package/docs/policies/set-headers-outbound/schema.json +1 -0
- package/docs/policies/set-query-params-inbound/schema.json +1 -0
- package/docs/policies/set-status-outbound/schema.json +1 -0
- package/docs/policies/set-upstream-api-key-inbound/schema.json +1 -0
- package/docs/policies/sleep-inbound/schema.json +1 -0
- package/docs/policies/stripe-webhook-verification-inbound/schema.json +1 -0
- package/docs/policies/supabase-jwt-auth-inbound/schema.json +1 -0
- package/docs/policies/upstream-azure-ad-service-auth-inbound/schema.json +1 -0
- package/docs/policies/upstream-firebase-admin-auth-inbound/schema.json +1 -0
- package/docs/policies/upstream-firebase-user-auth-inbound/schema.json +1 -0
- package/docs/policies/upstream-gcp-federated-auth-inbound/schema.json +1 -0
- package/docs/policies/upstream-gcp-jwt-inbound/schema.json +1 -0
- package/docs/policies/upstream-gcp-service-auth-inbound/schema.json +1 -0
- package/docs/policies/upstream-zuplo-jwt-auth-inbound/schema.json +1 -0
- package/docs/policies/validate-json-schema-inbound/schema.json +1 -0
- package/docs/policies/web-bot-auth-inbound/schema.json +1 -0
- package/docs/policies/xml-to-json-outbound/schema.json +1 -0
- package/package.json +4 -4
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Authentication overview"
|
|
3
|
+
sidebar_label: "Overview"
|
|
4
|
+
description:
|
|
5
|
+
How authentication works in the Zuplo MCP Gateway — the gateway as an OAuth
|
|
6
|
+
2.1 server for MCP clients, and as an OAuth client to each upstream MCP
|
|
7
|
+
server.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
The Zuplo MCP Gateway sits in the middle of two independent OAuth relationships.
|
|
11
|
+
MCP clients connect to the gateway and authenticate against it. The gateway, in
|
|
12
|
+
turn, connects to each upstream MCP server and authenticates against it on the
|
|
13
|
+
user's behalf. Both halves follow the same spec — the
|
|
14
|
+
[MCP authorization model](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization)
|
|
15
|
+
at revision `2025-11-25` — but they're configured separately and use different
|
|
16
|
+
policies.
|
|
17
|
+
|
|
18
|
+
This page explains both layers, the standards involved, and the moving parts
|
|
19
|
+
(sessions, scopes, token lifetimes) you'll see throughout the gateway. The
|
|
20
|
+
[identity provider catalog](#identity-providers) below points at per-IdP setup
|
|
21
|
+
guides; the upstream side has its own pages:
|
|
22
|
+
|
|
23
|
+
- [Per-user OAuth to upstream MCP servers](./upstream-oauth.mdx) — the upstream
|
|
24
|
+
side, conceptually
|
|
25
|
+
- [Connect a gateway to an upstream OAuth provider](../how-to/connect-upstream-oauth.mdx)
|
|
26
|
+
— how-to
|
|
27
|
+
- [Manual OAuth testing](./manual-oauth-testing.mdx) — how-to
|
|
28
|
+
|
|
29
|
+
## The two layers
|
|
30
|
+
|
|
31
|
+
Every authenticated MCP request involves two distinct OAuth surfaces.
|
|
32
|
+
|
|
33
|
+
### Downstream: gateway as OAuth server
|
|
34
|
+
|
|
35
|
+
When a client like Claude Desktop, Cursor, or Claude Code connects to a
|
|
36
|
+
`/mcp/{slug}` route on the gateway, the client is the OAuth client and the
|
|
37
|
+
gateway is both the **OAuth 2.1 Resource Server (RS)** and the **OAuth 2.1
|
|
38
|
+
Authorization Server (AS)**.
|
|
39
|
+
|
|
40
|
+
The gateway publishes everything an MCP client needs to discover and complete an
|
|
41
|
+
OAuth flow:
|
|
42
|
+
|
|
43
|
+
- An RFC 9728 Protected Resource Metadata document per route.
|
|
44
|
+
- An RFC 8414 Authorization Server Metadata document, both gateway-wide and per
|
|
45
|
+
route.
|
|
46
|
+
- An RFC 7591 Dynamic Client Registration endpoint.
|
|
47
|
+
- An OAuth Client ID Metadata Document (CIMD) acceptor.
|
|
48
|
+
- `/oauth/authorize`, `/oauth/token`, `/oauth/revoke`, and `/oauth/callback`
|
|
49
|
+
endpoints.
|
|
50
|
+
|
|
51
|
+
Browser identity is delegated to an OIDC identity provider you configure —
|
|
52
|
+
Auth0, Okta, or any OIDC discovery-compatible IdP. The IdP authenticates the
|
|
53
|
+
user; the gateway then issues its own bearer access token to the MCP client.
|
|
54
|
+
**The IdP's token never reaches the MCP client.**
|
|
55
|
+
|
|
56
|
+
### Upstream: gateway as OAuth client
|
|
57
|
+
|
|
58
|
+
When the gateway forwards a request to an upstream MCP server (Linear, Stripe,
|
|
59
|
+
Notion, GitHub, your internal service, and so on), the gateway is the OAuth
|
|
60
|
+
client and the upstream MCP server is the resource server. On behalf of each
|
|
61
|
+
user, the gateway runs through the upstream provider's OAuth discovery,
|
|
62
|
+
registers itself (preferring OIDC Client ID Metadata Documents, falling back to
|
|
63
|
+
RFC 7591 Dynamic Client Registration), redirects the user through the upstream
|
|
64
|
+
`/authorize`, captures the upstream tokens, and stores them encrypted at rest.
|
|
65
|
+
|
|
66
|
+
On subsequent MCP requests, the gateway resolves the stored upstream credential
|
|
67
|
+
per user, refreshes it if necessary, and injects it as an
|
|
68
|
+
`Authorization: Bearer ...` header when proxying to the upstream.
|
|
69
|
+
|
|
70
|
+
:::caution{title="Token passthrough is forbidden"}
|
|
71
|
+
|
|
72
|
+
The MCP authorization spec
|
|
73
|
+
[explicitly forbids](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices)
|
|
74
|
+
forwarding an inbound bearer token to an upstream API. Inbound auth headers
|
|
75
|
+
don't leak to the upstream — the gateway always uses an independent upstream
|
|
76
|
+
credential. The gateway-issued token a client presents and the upstream token
|
|
77
|
+
the gateway forwards are never the same token.
|
|
78
|
+
|
|
79
|
+
:::
|
|
80
|
+
|
|
81
|
+
## Standards observed
|
|
82
|
+
|
|
83
|
+
The gateway implements the following standards in their MCP-mandated subsets.
|
|
84
|
+
|
|
85
|
+
| Standard | Purpose |
|
|
86
|
+
| ---------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
87
|
+
| [OAuth 2.1 (draft)](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13) | Core authorization framework. |
|
|
88
|
+
| [RFC 7636 — PKCE](https://datatracker.ietf.org/doc/html/rfc7636) | Required on every authorization code flow. `S256` is required when technically capable. |
|
|
89
|
+
| [RFC 8414 — Authorization Server Metadata](https://datatracker.ietf.org/doc/html/rfc8414) | Published at `/.well-known/oauth-authorization-server[/{routePath}]`. |
|
|
90
|
+
| [OpenID Connect Discovery 1.0](https://openid.net/specs/openid-connect-discovery-1_0.html) | Accepted alongside RFC 8414 as authorization-server discovery (added in the `2025-11-25` MCP revision). |
|
|
91
|
+
| [RFC 9728 — Protected Resource Metadata](https://datatracker.ietf.org/doc/html/rfc9728) | Published at `/.well-known/oauth-protected-resource/{routePath}` per MCP route. |
|
|
92
|
+
| [RFC 7591 — Dynamic Client Registration](https://datatracker.ietf.org/doc/html/rfc7591) | Accepted at `/oauth/register`. |
|
|
93
|
+
| [OAuth Client ID Metadata Documents (CIMD)](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-client-id-metadata-document-00) | Recommended client identification path per the `2025-11-25` MCP revision. The gateway advertises `client_id_metadata_document_supported: true` and accepts URLs as `client_id` values when CIMD is enabled. |
|
|
94
|
+
| [RFC 8707 — Resource Indicators](https://datatracker.ietf.org/doc/html/rfc8707) | MCP clients **MUST** include the `resource` parameter on every authorization and token request. The gateway validates that incoming bearer tokens were minted for the route's canonical resource URI. |
|
|
95
|
+
| [RFC 6750 — Bearer tokens](https://datatracker.ietf.org/doc/html/rfc6750) | `Authorization: Bearer ...` only, header position only — tokens in query strings are rejected. |
|
|
96
|
+
| [RFC 7009 — Token Revocation](https://datatracker.ietf.org/doc/html/rfc7009) | Published at `/oauth/revoke`. |
|
|
97
|
+
|
|
98
|
+
CIMD is the recommended client identification path going forward; DCR is
|
|
99
|
+
retained for backwards compatibility with older MCP clients. Both work against
|
|
100
|
+
the same `/oauth/register` and AS metadata surface — clients that support either
|
|
101
|
+
are accommodated.
|
|
102
|
+
|
|
103
|
+
## Downstream flow
|
|
104
|
+
|
|
105
|
+
The downstream OAuth flow follows the spec's authorization-code grant with PKCE
|
|
106
|
+
plus the MCP `resource` parameter binding.
|
|
107
|
+
|
|
108
|
+
<Diagram height="h-72">
|
|
109
|
+
<DiagramNode id="client">MCP Client</DiagramNode>
|
|
110
|
+
<DiagramGroup id="gateway" label="Zuplo Gateway">
|
|
111
|
+
<DiagramNode id="endpoints" variant="zuplo">
|
|
112
|
+
OAuth endpoints
|
|
113
|
+
</DiagramNode>
|
|
114
|
+
<DiagramNode id="route" variant="zuplo">
|
|
115
|
+
MCP route
|
|
116
|
+
</DiagramNode>
|
|
117
|
+
</DiagramGroup>
|
|
118
|
+
<DiagramNode id="idp">Identity Provider</DiagramNode>
|
|
119
|
+
<DiagramEdge from="client" to="endpoints" label="OAuth flow" />
|
|
120
|
+
<DiagramEdge from="endpoints" to="idp" label="Browser login" />
|
|
121
|
+
<DiagramEdge from="client" to="route" label="MCP request" />
|
|
122
|
+
</Diagram>
|
|
123
|
+
|
|
124
|
+
The flow is the standard MCP authorization handshake. The
|
|
125
|
+
[`McpGatewayPlugin`](../code-config/overview.mdx) registers the
|
|
126
|
+
`/.well-known/...` and `/oauth/...` endpoints automatically.
|
|
127
|
+
|
|
128
|
+
## Upstream flow
|
|
129
|
+
|
|
130
|
+
The first request to a route whose upstream needs OAuth produces a
|
|
131
|
+
**connect-required** error. The MCP client is expected to surface the returned
|
|
132
|
+
URL to the user; the user completes upstream OAuth in a browser; the next MCP
|
|
133
|
+
request succeeds.
|
|
134
|
+
|
|
135
|
+
<Diagram height="h-72">
|
|
136
|
+
<DiagramNode id="client">MCP Client</DiagramNode>
|
|
137
|
+
<DiagramGroup id="gateway" label="Zuplo Gateway">
|
|
138
|
+
<DiagramNode id="connect" variant="zuplo">
|
|
139
|
+
Upstream connect
|
|
140
|
+
</DiagramNode>
|
|
141
|
+
<DiagramNode id="route" variant="zuplo">
|
|
142
|
+
MCP route
|
|
143
|
+
</DiagramNode>
|
|
144
|
+
</DiagramGroup>
|
|
145
|
+
<DiagramNode id="provider">Upstream Provider</DiagramNode>
|
|
146
|
+
<DiagramNode id="upstream">Upstream MCP server</DiagramNode>
|
|
147
|
+
<DiagramEdge from="client" to="route" label="MCP request" />
|
|
148
|
+
<DiagramEdge from="connect" to="provider" label="Upstream OAuth" />
|
|
149
|
+
<DiagramEdge from="route" to="upstream" label="Proxied request" />
|
|
150
|
+
</Diagram>
|
|
151
|
+
|
|
152
|
+
The `connect-required` JSON-RPC error wraps an MCP
|
|
153
|
+
[`UrlElicitationRequiredError`](https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation),
|
|
154
|
+
so clients that implement the URL-elicitation extension open the URL in a
|
|
155
|
+
browser automatically. Older clients surface the URL as text for the user to
|
|
156
|
+
open manually.
|
|
157
|
+
|
|
158
|
+
When the gateway has a stored upstream connection for the user, no
|
|
159
|
+
connect-required error is returned — the proxy forwards transparently.
|
|
160
|
+
|
|
161
|
+
## Identity providers
|
|
162
|
+
|
|
163
|
+
The gateway ships a generic OIDC policy plus first-class wrappers for the
|
|
164
|
+
identity providers most teams use. You configure exactly one of these policies
|
|
165
|
+
per project — the gateway rejects projects that declare more than one MCP OAuth
|
|
166
|
+
policy.
|
|
167
|
+
|
|
168
|
+
Each wrapper has the same shape: it takes a small set of provider-specific
|
|
169
|
+
fields (a domain, a tenant ID, a subdomain, and so on) plus `clientId` and
|
|
170
|
+
`clientSecret`, and derives the OIDC issuer, JWKS URL, and browser-login
|
|
171
|
+
endpoints automatically. Under the hood every wrapper composes the generic
|
|
172
|
+
`mcp-oauth-inbound` policy with provider-flavored URLs.
|
|
173
|
+
|
|
174
|
+
| Provider | Policy | Required options | Setup guide |
|
|
175
|
+
| -------------------------------------------------- | ---------------------------- | --------------------------------------------------------- | ---------------------------------------------- |
|
|
176
|
+
| **Auth0** | `mcp-auth0-oauth-inbound` | `auth0Domain`, `clientId`, `clientSecret` | [Configuring Auth0](./configuring-auth0.mdx) |
|
|
177
|
+
| **Amazon Cognito** | `mcp-cognito-oauth-inbound` | `awsRegion`, `userPoolId`, `userPoolDomain`, client creds | [Cognito](./configuring-cognito.mdx) |
|
|
178
|
+
| **Clerk** | `mcp-clerk-oauth-inbound` | `frontendApiUrl`, `clientId`, `clientSecret` | [Clerk](./configuring-clerk.mdx) |
|
|
179
|
+
| **Google** | `mcp-google-oauth-inbound` | `clientId`, `clientSecret` | [Google](./configuring-google.mdx) |
|
|
180
|
+
| **Keycloak** | `mcp-keycloak-oauth-inbound` | `keycloakBaseUrl`, `realm`, client creds | [Keycloak](./configuring-keycloak.mdx) |
|
|
181
|
+
| **Logto** | `mcp-logto-oauth-inbound` | `logtoEndpoint`, `clientId`, `clientSecret` | [Logto](./configuring-logto.mdx) |
|
|
182
|
+
| **Microsoft Entra ID** | `mcp-entra-oauth-inbound` | `tenantId`, `clientId`, `clientSecret` | [Microsoft Entra](./configuring-entra.mdx) |
|
|
183
|
+
| **Okta** | `mcp-okta-oauth-inbound` | `oktaDomain`, client creds | [Okta](./configuring-okta.mdx) |
|
|
184
|
+
| **OneLogin** | `mcp-onelogin-oauth-inbound` | `oneLoginSubdomain`, client creds | [OneLogin](./configuring-onelogin.mdx) |
|
|
185
|
+
| **PingOne** | `mcp-ping-oauth-inbound` | `environmentId` (or `customDomain`), client creds | [PingOne](./configuring-ping.mdx) |
|
|
186
|
+
| **WorkOS** | `mcp-workos-oauth-inbound` | `clientId`, `clientSecret` | [WorkOS](./configuring-workos.mdx) |
|
|
187
|
+
| Any other OIDC IdP (Ory Hydra, Authentik, custom…) | `mcp-oauth-inbound` | `oidc.issuer`, `oidc.jwksUrl`, `browserLogin.url` | [Generic OIDC](./configuring-generic-oidc.mdx) |
|
|
188
|
+
|
|
189
|
+
The upstream side uses a separate policy, `mcp-token-exchange-inbound`, one per
|
|
190
|
+
upstream MCP route. The downstream OAuth policy and the upstream token-exchange
|
|
191
|
+
policy are usually paired on the same route.
|
|
192
|
+
|
|
193
|
+
### Which wrapper should I pick?
|
|
194
|
+
|
|
195
|
+
- **Use a first-class wrapper** when one exists for your IdP. The wrappers
|
|
196
|
+
validate provider-specific inputs at boot (Entra rejects multi-tenant aliases
|
|
197
|
+
like `common`, Cognito separates the IDP service domain from the hosted UI
|
|
198
|
+
domain, OneLogin asks for the bare subdomain) so most misconfigurations fail
|
|
199
|
+
fast with an obvious error instead of a confusing OIDC failure later.
|
|
200
|
+
- **Use `mcp-oauth-inbound`** for any OIDC provider that doesn't ship a
|
|
201
|
+
dedicated wrapper. Ory Hydra, Authentik, ZITADEL, FusionAuth, custom OIDC
|
|
202
|
+
servers, and PingFederate (as opposed to PingOne) all work this way. You
|
|
203
|
+
provide the OIDC URLs explicitly.
|
|
204
|
+
|
|
205
|
+
## Sessions, scopes, and TTLs
|
|
206
|
+
|
|
207
|
+
The defaults below affect user-visible behavior and come up often in
|
|
208
|
+
configuration.
|
|
209
|
+
|
|
210
|
+
### Browser session
|
|
211
|
+
|
|
212
|
+
After the user completes browser login, the gateway sets a `zuplo_mcp_session`
|
|
213
|
+
cookie. The cookie persists for **8 hours** by default
|
|
214
|
+
(`browserLogin.sessionTtlSeconds`). During that window, the user doesn't need to
|
|
215
|
+
re-authenticate against the IdP for additional OAuth grants — the consent page
|
|
216
|
+
renders immediately.
|
|
217
|
+
|
|
218
|
+
### Gateway-issued tokens
|
|
219
|
+
|
|
220
|
+
The gateway-issued bearer access token defaults to **15 minutes** of lifetime
|
|
221
|
+
(`gateway.accessTokenTtlSeconds = 900`). MCP clients refresh as needed.
|
|
222
|
+
|
|
223
|
+
Refresh tokens default to roughly **10 years**
|
|
224
|
+
(`gateway.refreshTokenTtlSeconds`). This is intentional: the gateway is not the
|
|
225
|
+
system of record for the user's session — the upstream IdP is. Imposing a
|
|
226
|
+
shorter refresh-token lifetime than the IdP's own session policy forces the user
|
|
227
|
+
back through browser login when the IdP would still accept a silent renewal.
|
|
228
|
+
Customers who want a tighter ceiling can override the default in policy options.
|
|
229
|
+
|
|
230
|
+
Refresh tokens rotate on every use, and presenting a previously rotated refresh
|
|
231
|
+
token revokes the entire grant (with a short grace window to handle concurrent
|
|
232
|
+
refreshes).
|
|
233
|
+
|
|
234
|
+
### Gateway scope
|
|
235
|
+
|
|
236
|
+
There is exactly one downstream OAuth scope today: `mcp:tools`. The gateway
|
|
237
|
+
issues every access token with this scope, and the PRM advertises it as the only
|
|
238
|
+
entry in `scopes_supported`. Future capability scopes will appear alongside
|
|
239
|
+
`mcp:tools` rather than replacing it.
|
|
240
|
+
|
|
241
|
+
Upstream OAuth scopes are independent — they're whatever the upstream provider
|
|
242
|
+
requires, configured per upstream on `mcp-token-exchange-inbound`.
|
|
243
|
+
|
|
244
|
+
### Authentication binding
|
|
245
|
+
|
|
246
|
+
Every gateway-issued access token is bound to:
|
|
247
|
+
|
|
248
|
+
- The **canonical resource URI** of the MCP route the user authorized for,
|
|
249
|
+
derived from the request origin and the route path.
|
|
250
|
+
- The **`operationId`** of the route, set in `routes.oas.json`.
|
|
251
|
+
|
|
252
|
+
The gateway rejects a token presented at a different route or a different
|
|
253
|
+
canonical resource. A token issued for `/mcp/linear-v1` cannot be reused on
|
|
254
|
+
`/mcp/stripe-v1`.
|
|
255
|
+
|
|
256
|
+
The canonical resource URI is constructed from the incoming request origin. If
|
|
257
|
+
you front the gateway with a custom domain or a proxy, the gateway derives its
|
|
258
|
+
origin from the `Host` or `X-Forwarded-Host` header. A misconfigured proxy that
|
|
259
|
+
strips or overwrites these headers makes the gateway advertise the wrong issuer
|
|
260
|
+
in AS metadata. See [Troubleshooting](../troubleshooting.mdx) for symptoms.
|
|
261
|
+
|
|
262
|
+
## Custom domain caveat
|
|
263
|
+
|
|
264
|
+
The gateway's issuer URL — the value that appears as `issuer` in AS metadata and
|
|
265
|
+
as the authority of all generated endpoint URLs — is derived from the incoming
|
|
266
|
+
request's origin. The gateway honors the `Host` and `X-Forwarded-Host` headers
|
|
267
|
+
in that order.
|
|
268
|
+
|
|
269
|
+
When you put the gateway behind a custom domain (`gateway.example.com`), ensure
|
|
270
|
+
your fronting proxy or CDN forwards the original `Host` (or sets
|
|
271
|
+
`X-Forwarded-Host`) so the issuer in AS metadata matches the URL the MCP client
|
|
272
|
+
connected to. A mismatch makes OAuth clients reject the gateway's metadata.
|
|
273
|
+
|
|
274
|
+
## Endpoints reference
|
|
275
|
+
|
|
276
|
+
The gateway exposes the following authorization endpoints automatically once an
|
|
277
|
+
MCP OAuth policy is configured. See the [reference](../reference.mdx) for the
|
|
278
|
+
full URL catalog.
|
|
279
|
+
|
|
280
|
+
| Path | Method | Purpose |
|
|
281
|
+
| ----------------------------------------------------- | --------- | ------------------------------------------------------------------------------ |
|
|
282
|
+
| `/.well-known/oauth-authorization-server` | GET | RFC 8414 AS metadata (gateway-wide). |
|
|
283
|
+
| `/.well-known/oauth-authorization-server/{routePath}` | GET | RFC 8414 AS metadata (per route, rebinds `issuer`). |
|
|
284
|
+
| `/.well-known/oauth-protected-resource/{routePath}` | GET | RFC 9728 PRM (per route). |
|
|
285
|
+
| `/oauth/register` | POST | RFC 7591 Dynamic Client Registration. |
|
|
286
|
+
| `/oauth/authorize` | GET | Gateway-wide authorize endpoint. Requires the `resource` parameter. |
|
|
287
|
+
| `/oauth/authorize/{routePath}` | GET | Per-route authorize endpoint. |
|
|
288
|
+
| `/oauth/callback` | GET | Browser-login callback from the IdP. |
|
|
289
|
+
| `/oauth/setup` | GET, POST | Consent and multi-upstream connect page. |
|
|
290
|
+
| `/oauth/token` | POST | Token endpoint. Accepts `authorization_code` and `refresh_token` grants. |
|
|
291
|
+
| `/oauth/revoke` | POST | RFC 7009 revocation. |
|
|
292
|
+
| `/.well-known/oauth-client/{connection}` | GET | OIDC Client ID Metadata Document for the upstream OAuth client (per upstream). |
|
|
293
|
+
| `/auth/connections/{connection}/connect` | GET | Start the upstream OAuth flow. |
|
|
294
|
+
| `/auth/connections/{connection}/callback` | GET | Upstream OAuth callback. |
|
|
295
|
+
|
|
296
|
+
The well-known metadata endpoints serve CORS-permissive responses
|
|
297
|
+
(`Access-Control-Allow-Origin: *`) because browser-resident MCP clients fetch
|
|
298
|
+
them cross-origin. The token, register, revoke, callback, setup, and connect
|
|
299
|
+
endpoints reject ambient credentials.
|
|
300
|
+
|
|
301
|
+
## Related
|
|
302
|
+
|
|
303
|
+
- [Per-user OAuth to upstream MCP servers](./upstream-oauth.mdx) — the upstream
|
|
304
|
+
side, conceptually: discovery, client registration modes, per-user storage,
|
|
305
|
+
refresh, and reconsent.
|
|
306
|
+
- [Connect a gateway to an upstream OAuth provider](../how-to/connect-upstream-oauth.mdx)
|
|
307
|
+
— how to attach the `mcp-token-exchange-inbound` policy.
|
|
308
|
+
- [Manual OAuth testing](./manual-oauth-testing.mdx) — verify the gateway's
|
|
309
|
+
OAuth surface end to end with `curl` and `openssl`.
|
|
310
|
+
- [Identity provider setup guides](#identity-providers) — Auth0, Cognito, Clerk,
|
|
311
|
+
Google, Keycloak, Logto, Microsoft Entra, Okta, OneLogin, PingOne, WorkOS, and
|
|
312
|
+
any other OIDC provider.
|
|
313
|
+
- [Reference](../reference.mdx) — full URL catalog, default TTLs, and OAuth
|
|
314
|
+
metadata extensions.
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Per-user OAuth to upstream MCP servers"
|
|
3
|
+
sidebar_label: "Upstream OAuth"
|
|
4
|
+
description:
|
|
5
|
+
How the Zuplo MCP Gateway acts as an OAuth client to upstream MCP servers —
|
|
6
|
+
the two auth modes, client registration, the consent flow users experience,
|
|
7
|
+
connect-required states, and token refresh.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
The gateway sits between an MCP client and the upstream MCP servers a team
|
|
11
|
+
relies on. The inbound OAuth surface is the one MCP clients connect to; the
|
|
12
|
+
outbound surface is where the gateway authenticates to each upstream on the
|
|
13
|
+
user's behalf. This page is about the outbound surface — the one the
|
|
14
|
+
`mcp-token-exchange-inbound` policy controls.
|
|
15
|
+
|
|
16
|
+
For the policy steps, options reference, and worked examples, see
|
|
17
|
+
[Connect a gateway to an upstream OAuth provider](../how-to/connect-upstream-oauth.mdx).
|
|
18
|
+
|
|
19
|
+
## Why the gateway acts as an OAuth client
|
|
20
|
+
|
|
21
|
+
Modern MCP servers — Linear, Notion, Stripe, GitHub, Grafana Cloud, and many
|
|
22
|
+
others — are OAuth-protected resources. They expect a `Bearer` token that
|
|
23
|
+
represents a specific user (or service identity) granted by their own OAuth
|
|
24
|
+
authorization server.
|
|
25
|
+
|
|
26
|
+
When an MCP client connects to a Zuplo MCP Gateway route, it presents the
|
|
27
|
+
gateway's bearer token. That token authenticates the user to the gateway but
|
|
28
|
+
isn't valid against the upstream. The spec
|
|
29
|
+
[explicitly forbids](https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices)
|
|
30
|
+
forwarding the inbound token to an upstream, so the gateway must mint an
|
|
31
|
+
independent upstream credential and attach it to the upstream request.
|
|
32
|
+
|
|
33
|
+
The gateway does that by acting as a standard OAuth client to each upstream —
|
|
34
|
+
discovering the upstream's authorization server, registering itself as a client,
|
|
35
|
+
redirecting the user through the upstream's authorization flow, capturing the
|
|
36
|
+
resulting tokens, and storing them encrypted at rest. On subsequent requests,
|
|
37
|
+
the gateway resolves the stored credential, refreshes it if necessary, and
|
|
38
|
+
applies it to the upstream request.
|
|
39
|
+
|
|
40
|
+
## The two auth modes
|
|
41
|
+
|
|
42
|
+
`authMode` is the central knob. It decides who owns the upstream credential.
|
|
43
|
+
|
|
44
|
+
### user-oauth
|
|
45
|
+
|
|
46
|
+
Per-user is the default and the right choice for most upstreams. Each user has
|
|
47
|
+
their own per-upstream OAuth connection. The first time a user hits the route,
|
|
48
|
+
the gateway returns a connect-required error; the user completes the upstream
|
|
49
|
+
provider's OAuth flow in a browser; the gateway stores the resulting tokens
|
|
50
|
+
encrypted, keyed by the user's subject ID. Subsequent requests from that user
|
|
51
|
+
are transparent.
|
|
52
|
+
|
|
53
|
+
This mode is what Linear, Notion, Stripe, GitHub, and most SaaS MCP servers use.
|
|
54
|
+
It preserves per-user attribution end to end — the upstream sees the specific
|
|
55
|
+
user making the call, and the gateway's analytics record the same subject ID
|
|
56
|
+
against every event.
|
|
57
|
+
|
|
58
|
+
### shared-oauth
|
|
59
|
+
|
|
60
|
+
Shared mode uses a single gateway-wide OAuth grant. There's no per-user connect
|
|
61
|
+
flow. An administrator completes a one-time connection through the upstream's
|
|
62
|
+
OAuth provider, and every authenticated user reuses that credential when calling
|
|
63
|
+
the upstream. If no shared connection exists yet, the gateway returns an
|
|
64
|
+
`admin_connect_required` error to let the client know an administrator action is
|
|
65
|
+
needed.
|
|
66
|
+
|
|
67
|
+
Shared mode is appropriate when the upstream uses a service account that
|
|
68
|
+
represents the organization rather than individual users, or when auditing
|
|
69
|
+
happens at the gateway level (per user) rather than at the upstream (where every
|
|
70
|
+
call looks like the same service account).
|
|
71
|
+
|
|
72
|
+
## Client registration
|
|
73
|
+
|
|
74
|
+
The gateway needs to identify itself to the upstream OAuth provider before it
|
|
75
|
+
can request tokens. The `clientRegistration` option controls how:
|
|
76
|
+
|
|
77
|
+
- **CIMD with DCR fallback (`{ "mode": "auto" }`)** — the default. The gateway
|
|
78
|
+
publishes a per-upstream
|
|
79
|
+
[OAuth Client ID Metadata Document](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-client-id-metadata-document-00)
|
|
80
|
+
at `/.well-known/oauth-client/{connection}?authProfileId=...` and tells the
|
|
81
|
+
upstream that URL is the client ID. If the upstream doesn't accept CIMD, the
|
|
82
|
+
gateway falls back to
|
|
83
|
+
[RFC 7591 Dynamic Client Registration](https://datatracker.ietf.org/doc/html/rfc7591).
|
|
84
|
+
Auto mode requires nothing from the upstream provider beyond standard MCP
|
|
85
|
+
authorization spec support and has no client secrets to rotate.
|
|
86
|
+
- **Manual** — the gateway uses a pre-registered `clientId` (and optional
|
|
87
|
+
`clientSecret`) and authenticates to the upstream token endpoint with a
|
|
88
|
+
configured method. Manual mode is the right choice when an organization
|
|
89
|
+
manages OAuth client lifecycle centrally, the upstream provider requires an
|
|
90
|
+
approved client, or one OAuth client should be shared across multiple routes.
|
|
91
|
+
|
|
92
|
+
Both modes are first-class. CIMD documents are accessible to the upstream
|
|
93
|
+
provider over HTTPS — the upstream fetches them as part of its OAuth
|
|
94
|
+
registration flow. The CIMD URL includes the `authProfileId` query parameter so
|
|
95
|
+
the gateway can scope client identity per `(upstream, authMode)` pair.
|
|
96
|
+
|
|
97
|
+
## How the gateway picks scopes
|
|
98
|
+
|
|
99
|
+
The gateway needs to know which OAuth scopes to request from the upstream. It
|
|
100
|
+
considers three sources in order:
|
|
101
|
+
|
|
102
|
+
1. **An explicit `scopes` array on the policy.** When set, the gateway uses
|
|
103
|
+
exactly those values on every upstream authorization request.
|
|
104
|
+
2. **The `scope=` value from the upstream's most recent `WWW-Authenticate`
|
|
105
|
+
challenge.** Used when no explicit scopes are configured.
|
|
106
|
+
3. **The `scopes_supported` array in the upstream's Protected Resource
|
|
107
|
+
Metadata.** Used as the final fallback before falling through to no `scope`
|
|
108
|
+
parameter at all.
|
|
109
|
+
|
|
110
|
+
Explicit scopes always win. Microsoft 365, Slack, PostHog, Stripe, and Grafana
|
|
111
|
+
Cloud are examples of upstreams that need explicit scopes — their PRM either
|
|
112
|
+
lists too many scopes or none at all, so deferring to discovery alone isn't
|
|
113
|
+
enough.
|
|
114
|
+
|
|
115
|
+
## What the user sees
|
|
116
|
+
|
|
117
|
+
The browser flow runs the first time a user hits an OAuth-protected upstream
|
|
118
|
+
they haven't connected, and again whenever the upstream revokes the gateway's
|
|
119
|
+
client. Modern MCP clients implement the URL-elicitation extension and open the
|
|
120
|
+
URL automatically. Older clients surface the URL as part of the JSON-RPC error
|
|
121
|
+
message; the user copies it into a browser.
|
|
122
|
+
|
|
123
|
+
<Diagram height="h-72">
|
|
124
|
+
<DiagramNode id="client">MCP Client</DiagramNode>
|
|
125
|
+
<DiagramGroup id="gateway" label="Zuplo Gateway">
|
|
126
|
+
<DiagramNode id="connect" variant="zuplo">
|
|
127
|
+
Upstream connect
|
|
128
|
+
</DiagramNode>
|
|
129
|
+
<DiagramNode id="route" variant="zuplo">
|
|
130
|
+
/mcp/linear-v1
|
|
131
|
+
</DiagramNode>
|
|
132
|
+
</DiagramGroup>
|
|
133
|
+
<DiagramNode id="oauth">Linear OAuth</DiagramNode>
|
|
134
|
+
<DiagramNode id="upstream">Linear MCP</DiagramNode>
|
|
135
|
+
<DiagramEdge from="client" to="route" label="MCP request" />
|
|
136
|
+
<DiagramEdge from="connect" to="oauth" label="Upstream OAuth" />
|
|
137
|
+
<DiagramEdge from="route" to="upstream" label="Proxied request" />
|
|
138
|
+
</Diagram>
|
|
139
|
+
|
|
140
|
+
Each MCP route proxies to exactly one upstream, so the consent page typically
|
|
141
|
+
shows one upstream to connect. The consent page is part of the gateway and
|
|
142
|
+
renders automatically whenever a user lands at `/oauth/setup` mid-flow.
|
|
143
|
+
|
|
144
|
+
## Connect-required states
|
|
145
|
+
|
|
146
|
+
When the gateway needs the user to act, it returns a JSON-RPC error with a
|
|
147
|
+
`state` field that distinguishes the three reasons.
|
|
148
|
+
|
|
149
|
+
| State | Meaning | Typical UI message |
|
|
150
|
+
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ |
|
|
151
|
+
| `authenticating` | First-time connection. User hasn't authorized the upstream yet. | "Connect to `{provider}` to continue." |
|
|
152
|
+
| `reconsent_required` | Existing connection but the upstream revoked the client or invalidated the refresh token. The user needs to reauthorize. | "`{provider}` authorization must be renewed." |
|
|
153
|
+
| `admin_connect_required` | `authMode: shared-oauth` and no shared connection exists yet. Only an administrator can complete the flow. | "An administrator must connect `{provider}` before this service is available." |
|
|
154
|
+
|
|
155
|
+
The full JSON-RPC error payload looks like:
|
|
156
|
+
|
|
157
|
+
```jsonc
|
|
158
|
+
{
|
|
159
|
+
"jsonrpc": "2.0",
|
|
160
|
+
"id": "1",
|
|
161
|
+
"error": {
|
|
162
|
+
"code": -32042,
|
|
163
|
+
"message": "Connect Linear to continue.",
|
|
164
|
+
"data": {
|
|
165
|
+
"state": "authenticating",
|
|
166
|
+
"upstreamServerId": "linear",
|
|
167
|
+
"operationId": "linear-mcp-server",
|
|
168
|
+
"authUrl": "https://gateway.example.com/auth/connections/linear/connect?browserTicket=eyJ...&operationId=linear-mcp-server",
|
|
169
|
+
"nextAction": "redirect",
|
|
170
|
+
"authProfileId": "linear:user-oauth",
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
The `-32042` error code is MCP's `URLElicitationRequiredError`. Clients that
|
|
177
|
+
support URL elicitation open `authUrl` directly; others render the message and
|
|
178
|
+
let the user open the URL manually.
|
|
179
|
+
|
|
180
|
+
## Refresh and 401 retry
|
|
181
|
+
|
|
182
|
+
The gateway transparently refreshes the upstream access token from the stored
|
|
183
|
+
refresh token. When the upstream returns a 401 mid-request — for example,
|
|
184
|
+
because the upstream's session-bound token expired — the gateway refreshes the
|
|
185
|
+
upstream credential and retries the upstream fetch once. If the refresh fails or
|
|
186
|
+
produces another connect-required state, the gateway returns the JSON-RPC
|
|
187
|
+
connect-required to the client and the user sees the reconsent flow.
|
|
188
|
+
|
|
189
|
+
Stored refresh tokens stay valid as long as the upstream provider honors them.
|
|
190
|
+
When an upstream's policy revokes a refresh token — for example, because the
|
|
191
|
+
user revoked the connection from the upstream's dashboard — the next request
|
|
192
|
+
surfaces `reconsent_required` and the user re-authorizes through the same
|
|
193
|
+
browser flow.
|
|
194
|
+
|
|
195
|
+
## Where the metadata URL comes from
|
|
196
|
+
|
|
197
|
+
By default, the gateway derives the upstream Protected Resource Metadata URL
|
|
198
|
+
from the route's `rewritePattern`:
|
|
199
|
+
|
|
200
|
+
```text
|
|
201
|
+
rewritePattern: https://mcp.linear.app/mcp
|
|
202
|
+
default PRM URL: https://mcp.linear.app/.well-known/oauth-protected-resource/mcp
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
When the upstream serves PRM at a non-default path (Linear's PRM lives at the
|
|
206
|
+
origin's root, not under `/mcp`), the policy's `protectedResourceMetadataUrl`
|
|
207
|
+
option overrides the default. The canonical source of truth is the
|
|
208
|
+
`resource_metadata=` parameter on the upstream's `WWW-Authenticate` challenge to
|
|
209
|
+
an unauthenticated request.
|
|
210
|
+
|
|
211
|
+
## Related
|
|
212
|
+
|
|
213
|
+
- [Connect a gateway to an upstream OAuth provider](../how-to/connect-upstream-oauth.mdx)
|
|
214
|
+
— how to attach the policy, pick modes, and verify the connect flow.
|
|
215
|
+
- [Authentication overview](./overview.mdx) — the two-layer model and how
|
|
216
|
+
inbound and outbound OAuth fit together.
|
|
217
|
+
- [Manual OAuth testing](./manual-oauth-testing.mdx) — verify the gateway's
|
|
218
|
+
OAuth surface end to end with `curl` and `openssl`.
|
|
219
|
+
- [Compatibility dates](../code-config/compatibility-dates.mdx) — the
|
|
220
|
+
`2026-03-01` requirement for upstream 401 retries and other MCP Gateway
|
|
221
|
+
behaviors.
|