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.
Files changed (160) hide show
  1. package/docs/mcp-gateway/auth/configuring-auth0.mdx +216 -0
  2. package/docs/mcp-gateway/auth/configuring-clerk.mdx +153 -0
  3. package/docs/mcp-gateway/auth/configuring-cognito.mdx +128 -0
  4. package/docs/mcp-gateway/auth/configuring-entra.mdx +134 -0
  5. package/docs/mcp-gateway/auth/configuring-generic-oidc.mdx +242 -0
  6. package/docs/mcp-gateway/auth/configuring-google.mdx +117 -0
  7. package/docs/mcp-gateway/auth/configuring-keycloak.mdx +125 -0
  8. package/docs/mcp-gateway/auth/configuring-logto.mdx +116 -0
  9. package/docs/mcp-gateway/auth/configuring-okta.mdx +199 -0
  10. package/docs/mcp-gateway/auth/configuring-onelogin.mdx +122 -0
  11. package/docs/mcp-gateway/auth/configuring-ping.mdx +157 -0
  12. package/docs/mcp-gateway/auth/configuring-workos.mdx +117 -0
  13. package/docs/mcp-gateway/auth/manual-oauth-testing.mdx +528 -0
  14. package/docs/mcp-gateway/auth/overview.mdx +314 -0
  15. package/docs/mcp-gateway/auth/upstream-oauth.mdx +221 -0
  16. package/docs/mcp-gateway/capability-filtering.mdx +162 -0
  17. package/docs/mcp-gateway/code-config/compatibility-dates.mdx +33 -0
  18. package/docs/mcp-gateway/code-config/local-development.mdx +198 -0
  19. package/docs/mcp-gateway/code-config/mcp-proxy-handler.mdx +186 -0
  20. package/docs/mcp-gateway/code-config/multi-upstream.mdx +293 -0
  21. package/docs/mcp-gateway/code-config/overview.mdx +210 -0
  22. package/docs/mcp-gateway/connect-clients/chatgpt.mdx +127 -0
  23. package/docs/mcp-gateway/connect-clients/claude-code.mdx +184 -0
  24. package/docs/mcp-gateway/connect-clients/claude-desktop.mdx +160 -0
  25. package/docs/mcp-gateway/connect-clients/cursor.mdx +100 -0
  26. package/docs/mcp-gateway/connect-clients/other-clients.mdx +207 -0
  27. package/docs/mcp-gateway/connect-clients/overview.mdx +137 -0
  28. package/docs/mcp-gateway/connect-clients/vs-code.mdx +128 -0
  29. package/docs/mcp-gateway/how-it-works.mdx +266 -0
  30. package/docs/mcp-gateway/how-to/connect-upstream-oauth.mdx +268 -0
  31. package/docs/mcp-gateway/how-to/curate-tools.mdx +278 -0
  32. package/docs/mcp-gateway/introduction.mdx +151 -0
  33. package/docs/mcp-gateway/observability/analytics.mdx +191 -0
  34. package/docs/mcp-gateway/observability/logging.mdx +191 -0
  35. package/docs/mcp-gateway/quickstart.mdx +266 -0
  36. package/docs/mcp-gateway/reference.mdx +148 -0
  37. package/docs/mcp-gateway/test-clients.mdx +130 -0
  38. package/docs/mcp-gateway/troubleshooting.mdx +228 -0
  39. package/docs/mcp-server/introduction.mdx +10 -0
  40. package/docs/mcp-server/openai-apps-sdk.mdx +12 -0
  41. package/docs/policies/_index.md +14 -0
  42. package/docs/policies/akamai-ai-firewall/schema.json +1 -0
  43. package/docs/policies/akamai-firewall-for-ai-inbound/schema.json +1 -0
  44. package/docs/policies/akamai-firewall-for-ai-outbound/schema.json +1 -0
  45. package/docs/policies/amberflo-metering-inbound/schema.json +1 -0
  46. package/docs/policies/api-key-inbound/schema.json +1 -0
  47. package/docs/policies/audit-log-inbound/schema.json +1 -0
  48. package/docs/policies/auth0-jwt-auth-inbound/schema.json +1 -0
  49. package/docs/policies/authzen-inbound/schema.json +1 -0
  50. package/docs/policies/axiomatics-authz-inbound/schema.json +1 -0
  51. package/docs/policies/basic-auth-inbound/schema.json +1 -0
  52. package/docs/policies/bot-detection-inbound/schema.json +1 -0
  53. package/docs/policies/brownout-inbound/schema.json +1 -0
  54. package/docs/policies/caching-inbound/schema.json +1 -0
  55. package/docs/policies/change-method-inbound/schema.json +1 -0
  56. package/docs/policies/clear-headers-inbound/schema.json +1 -0
  57. package/docs/policies/clear-headers-outbound/schema.json +1 -0
  58. package/docs/policies/clerk-jwt-auth-inbound/schema.json +1 -0
  59. package/docs/policies/cognito-jwt-auth-inbound/schema.json +1 -0
  60. package/docs/policies/comet-opik-tracing-inbound/schema.json +1 -0
  61. package/docs/policies/complex-rate-limit-inbound/schema.json +1 -0
  62. package/docs/policies/composite-inbound/schema.json +1 -0
  63. package/docs/policies/composite-outbound/schema.json +1 -0
  64. package/docs/policies/curity-phantom-token-inbound/schema.json +1 -0
  65. package/docs/policies/firebase-jwt-inbound/schema.json +1 -0
  66. package/docs/policies/formdata-to-json-inbound/schema.json +1 -0
  67. package/docs/policies/galileo-tracing-inbound/schema.json +1 -0
  68. package/docs/policies/geo-filter-inbound/schema.json +1 -0
  69. package/docs/policies/graphql-complexity-limit-inbound/schema.json +1 -0
  70. package/docs/policies/graphql-disable-introspection-inbound/schema.json +1 -0
  71. package/docs/policies/graphql-introspection-filter-outbound/schema.json +1 -0
  72. package/docs/policies/http-deprecation-outbound/schema.json +1 -0
  73. package/docs/policies/jwt-scopes-inbound/schema.json +1 -0
  74. package/docs/policies/ldap-auth-inbound/schema.json +1 -0
  75. package/docs/policies/mcp-auth0-oauth-inbound/doc.md +54 -0
  76. package/docs/policies/mcp-auth0-oauth-inbound/intro.md +7 -0
  77. package/docs/policies/mcp-auth0-oauth-inbound/schema.json +135 -0
  78. package/docs/policies/mcp-capability-filter-inbound/doc.md +58 -0
  79. package/docs/policies/mcp-capability-filter-inbound/intro.md +9 -0
  80. package/docs/policies/mcp-capability-filter-inbound/schema.json +212 -0
  81. package/docs/policies/mcp-clerk-oauth-inbound/doc.md +34 -0
  82. package/docs/policies/mcp-clerk-oauth-inbound/intro.md +1 -0
  83. package/docs/policies/mcp-clerk-oauth-inbound/schema.json +134 -0
  84. package/docs/policies/mcp-cognito-oauth-inbound/doc.md +52 -0
  85. package/docs/policies/mcp-cognito-oauth-inbound/intro.md +7 -0
  86. package/docs/policies/mcp-cognito-oauth-inbound/schema.json +152 -0
  87. package/docs/policies/mcp-entra-oauth-inbound/doc.md +51 -0
  88. package/docs/policies/mcp-entra-oauth-inbound/intro.md +6 -0
  89. package/docs/policies/mcp-entra-oauth-inbound/schema.json +131 -0
  90. package/docs/policies/mcp-google-oauth-inbound/doc.md +52 -0
  91. package/docs/policies/mcp-google-oauth-inbound/intro.md +6 -0
  92. package/docs/policies/mcp-google-oauth-inbound/schema.json +125 -0
  93. package/docs/policies/mcp-keycloak-oauth-inbound/doc.md +43 -0
  94. package/docs/policies/mcp-keycloak-oauth-inbound/intro.md +2 -0
  95. package/docs/policies/mcp-keycloak-oauth-inbound/schema.json +140 -0
  96. package/docs/policies/mcp-logto-oauth-inbound/doc.md +52 -0
  97. package/docs/policies/mcp-logto-oauth-inbound/intro.md +6 -0
  98. package/docs/policies/mcp-logto-oauth-inbound/schema.json +131 -0
  99. package/docs/policies/mcp-oauth-inbound/doc.md +70 -0
  100. package/docs/policies/mcp-oauth-inbound/intro.md +11 -0
  101. package/docs/policies/mcp-oauth-inbound/schema.json +177 -0
  102. package/docs/policies/mcp-okta-oauth-inbound/doc.md +61 -0
  103. package/docs/policies/mcp-okta-oauth-inbound/intro.md +7 -0
  104. package/docs/policies/mcp-okta-oauth-inbound/schema.json +137 -0
  105. package/docs/policies/mcp-onelogin-oauth-inbound/doc.md +50 -0
  106. package/docs/policies/mcp-onelogin-oauth-inbound/intro.md +6 -0
  107. package/docs/policies/mcp-onelogin-oauth-inbound/schema.json +131 -0
  108. package/docs/policies/mcp-ping-oauth-inbound/doc.md +80 -0
  109. package/docs/policies/mcp-ping-oauth-inbound/intro.md +7 -0
  110. package/docs/policies/mcp-ping-oauth-inbound/schema.json +151 -0
  111. package/docs/policies/mcp-token-exchange-inbound/doc.md +135 -0
  112. package/docs/policies/mcp-token-exchange-inbound/intro.md +6 -0
  113. package/docs/policies/mcp-token-exchange-inbound/schema.json +134 -0
  114. package/docs/policies/mcp-workos-oauth-inbound/doc.md +50 -0
  115. package/docs/policies/mcp-workos-oauth-inbound/intro.md +6 -0
  116. package/docs/policies/mcp-workos-oauth-inbound/schema.json +125 -0
  117. package/docs/policies/mock-api-inbound/schema.json +1 -0
  118. package/docs/policies/moesif-inbound/schema.json +1 -0
  119. package/docs/policies/monetization-inbound/schema.json +1 -0
  120. package/docs/policies/mtls-auth-inbound/schema.json +1 -0
  121. package/docs/policies/okta-fga-authz-inbound/schema.json +1 -0
  122. package/docs/policies/okta-jwt-auth-inbound/schema.json +1 -0
  123. package/docs/policies/open-id-jwt-auth-inbound/schema.json +1 -0
  124. package/docs/policies/openfga-authz-inbound/schema.json +1 -0
  125. package/docs/policies/openmeter-inbound/schema.json +1 -0
  126. package/docs/policies/prompt-injection-outbound/schema.json +1 -0
  127. package/docs/policies/propel-auth-jwt-inbound/schema.json +1 -0
  128. package/docs/policies/query-param-to-header-inbound/schema.json +1 -0
  129. package/docs/policies/quota-inbound/schema.json +1 -0
  130. package/docs/policies/rate-limit-inbound/schema.json +1 -0
  131. package/docs/policies/readme-metrics-inbound/schema.json +1 -0
  132. package/docs/policies/remove-headers-inbound/schema.json +1 -0
  133. package/docs/policies/remove-headers-outbound/schema.json +1 -0
  134. package/docs/policies/remove-query-params-inbound/schema.json +1 -0
  135. package/docs/policies/replace-string-outbound/schema.json +1 -0
  136. package/docs/policies/request-size-limit-inbound/schema.json +1 -0
  137. package/docs/policies/request-validation-inbound/schema.json +1 -0
  138. package/docs/policies/require-origin-inbound/schema.json +1 -0
  139. package/docs/policies/secret-masking-outbound/schema.json +1 -0
  140. package/docs/policies/semantic-cache-inbound/schema.json +1 -0
  141. package/docs/policies/set-body-inbound/schema.json +1 -0
  142. package/docs/policies/set-headers-inbound/schema.json +1 -0
  143. package/docs/policies/set-headers-outbound/schema.json +1 -0
  144. package/docs/policies/set-query-params-inbound/schema.json +1 -0
  145. package/docs/policies/set-status-outbound/schema.json +1 -0
  146. package/docs/policies/set-upstream-api-key-inbound/schema.json +1 -0
  147. package/docs/policies/sleep-inbound/schema.json +1 -0
  148. package/docs/policies/stripe-webhook-verification-inbound/schema.json +1 -0
  149. package/docs/policies/supabase-jwt-auth-inbound/schema.json +1 -0
  150. package/docs/policies/upstream-azure-ad-service-auth-inbound/schema.json +1 -0
  151. package/docs/policies/upstream-firebase-admin-auth-inbound/schema.json +1 -0
  152. package/docs/policies/upstream-firebase-user-auth-inbound/schema.json +1 -0
  153. package/docs/policies/upstream-gcp-federated-auth-inbound/schema.json +1 -0
  154. package/docs/policies/upstream-gcp-jwt-inbound/schema.json +1 -0
  155. package/docs/policies/upstream-gcp-service-auth-inbound/schema.json +1 -0
  156. package/docs/policies/upstream-zuplo-jwt-auth-inbound/schema.json +1 -0
  157. package/docs/policies/validate-json-schema-inbound/schema.json +1 -0
  158. package/docs/policies/web-bot-auth-inbound/schema.json +1 -0
  159. package/docs/policies/xml-to-json-outbound/schema.json +1 -0
  160. 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.