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,293 @@
1
+ ---
2
+ title: "Add multiple upstream MCP servers"
3
+ sidebar_label: "Multi-upstream"
4
+ description:
5
+ Front many upstream MCP servers from one Zuplo project. Share a single OAuth
6
+ policy across every route, add one token exchange policy per upstream, and let
7
+ each user maintain independent per-upstream connections.
8
+ ---
9
+
10
+ A single Zuplo deployment can front any number of upstream MCP servers. One
11
+ OAuth policy authenticates inbound MCP clients across every route; one
12
+ `mcp-token-exchange-inbound` policy lives per upstream; one route per upstream
13
+ wires them together.
14
+
15
+ This page is a worked example: a single gateway project that exposes Linear and
16
+ Stripe as two separate MCP endpoints, with the full `zuplo.jsonc`,
17
+ `policies.json`, `routes.oas.json`, and runtime-init files you can copy into
18
+ your own project.
19
+
20
+ ## The pattern
21
+
22
+ Three rules form the pattern:
23
+
24
+ 1. **One MCP OAuth policy, project-wide.** The gateway allows exactly one MCP
25
+ OAuth policy per project, regardless of which
26
+ [IdP wrapper](../auth/overview.mdx#identity-providers) you pick. Every MCP
27
+ route attaches the same policy.
28
+ 2. **One `mcp-token-exchange-*` policy per upstream.** Each upstream MCP server
29
+ gets its own policy with its own `displayName`, `authMode`, `scopes`, and
30
+ optional `protectedResourceMetadataUrl`. The policy's `id` (or the `id`
31
+ inferred from its name) identifies the upstream — pick it once and don't
32
+ change it.
33
+ 3. **One `/mcp/<slug>` route per upstream.** Each route uses
34
+ [`McpProxyHandler`](./mcp-proxy-handler.mdx) with the upstream URL as
35
+ `rewritePattern`, and lists the shared OAuth policy plus the matching token
36
+ exchange policy in its inbound chain.
37
+
38
+ A typical path convention is `/mcp/<provider>-v<n>`. The `-v<n>` suffix lets you
39
+ publish a v2 alongside a v1 without breaking existing client configs.
40
+
41
+ ## Worked example: Linear and Stripe
42
+
43
+ The configuration below exposes two upstream MCP servers — Linear and Stripe —
44
+ behind one Auth0-protected gateway. Each user authenticates once to the gateway,
45
+ then connects to Linear and Stripe independently the first time they call each.
46
+
47
+ ### `zuplo.jsonc`
48
+
49
+ ```jsonc
50
+ {
51
+ "version": 1,
52
+ "compatibilityDate": "2026-03-01",
53
+ }
54
+ ```
55
+
56
+ ### `modules/zuplo.runtime.ts`
57
+
58
+ ```ts
59
+ import { RuntimeExtensions } from "@zuplo/runtime";
60
+ import { McpGatewayPlugin } from "@zuplo/runtime/mcp-gateway";
61
+
62
+ export function runtimeInit(runtime: RuntimeExtensions) {
63
+ runtime.addPlugin(new McpGatewayPlugin());
64
+ }
65
+ ```
66
+
67
+ ### `config/policies.json`
68
+
69
+ ```jsonc
70
+ {
71
+ "policies": [
72
+ {
73
+ "name": "auth0-managed-oauth",
74
+ "policyType": "mcp-auth0-oauth-inbound",
75
+ "handler": {
76
+ "module": "$import(@zuplo/runtime/mcp-gateway)",
77
+ "export": "McpAuth0OAuthInboundPolicy",
78
+ "options": {
79
+ "auth0Domain": "$env(AUTH0_DOMAIN)",
80
+ "clientId": "$env(AUTH0_CLIENT_ID)",
81
+ "clientSecret": "$env(AUTH0_CLIENT_SECRET)",
82
+ },
83
+ },
84
+ },
85
+ {
86
+ "name": "mcp-token-exchange-linear",
87
+ "policyType": "mcp-token-exchange-inbound",
88
+ "handler": {
89
+ "module": "$import(@zuplo/runtime/mcp-gateway)",
90
+ "export": "McpTokenExchangeInboundPolicy",
91
+ "options": {
92
+ "displayName": "Linear",
93
+ "summary": "Linear MCP upstream, per-user OAuth.",
94
+ "protectedResourceMetadataUrl": "https://mcp.linear.app/.well-known/oauth-protected-resource",
95
+ "authMode": "user-oauth",
96
+ "scopes": [],
97
+ "clientRegistration": { "mode": "auto" },
98
+ },
99
+ },
100
+ },
101
+ {
102
+ "name": "mcp-token-exchange-stripe",
103
+ "policyType": "mcp-token-exchange-inbound",
104
+ "handler": {
105
+ "module": "$import(@zuplo/runtime/mcp-gateway)",
106
+ "export": "McpTokenExchangeInboundPolicy",
107
+ "options": {
108
+ "displayName": "Stripe",
109
+ "summary": "Stripe MCP upstream, per-user OAuth.",
110
+ "authMode": "user-oauth",
111
+ "scopes": ["mcp"],
112
+ "clientRegistration": { "mode": "auto" },
113
+ },
114
+ },
115
+ },
116
+ ],
117
+ }
118
+ ```
119
+
120
+ A few notes on what's set per upstream:
121
+
122
+ - **`protectedResourceMetadataUrl`** is explicit for Linear because Linear
123
+ publishes its PRM at the root well-known path
124
+ (`/.well-known/oauth-protected-resource`) instead of the per-route default
125
+ (`/.well-known/oauth-protected-resource/mcp`). For Stripe the default works,
126
+ so the option is omitted.
127
+ - **`scopes: []`** for Linear means the gateway falls back to the upstream's
128
+ `WWW-Authenticate` `scope` value, then to the PRM's `scopes_supported`, then
129
+ to no scope parameter. For Stripe the explicit `["mcp"]` is what the provider
130
+ expects.
131
+ - **`clientRegistration: { mode: "auto" }`** lets the gateway register a client
132
+ with each upstream on demand using OIDC Client ID Metadata Document discovery
133
+ first, then RFC 7591 Dynamic Client Registration as a fallback. No client
134
+ credentials need to live in source control.
135
+
136
+ ### `config/routes.oas.json`
137
+
138
+ ```jsonc
139
+ {
140
+ "openapi": "3.1.0",
141
+ "info": { "title": "MCP Gateway", "version": "0.1.0" },
142
+ "paths": {
143
+ "/mcp/linear-v1": {
144
+ "get,post": {
145
+ "operationId": "linear-mcp-server",
146
+ "summary": "Linear MCP Proxy",
147
+ "x-zuplo-route": {
148
+ "corsPolicy": "none",
149
+ "handler": {
150
+ "module": "$import(@zuplo/runtime/mcp-gateway)",
151
+ "export": "McpProxyHandler",
152
+ "options": { "rewritePattern": "https://mcp.linear.app/mcp" },
153
+ },
154
+ "policies": {
155
+ "inbound": ["auth0-managed-oauth", "mcp-token-exchange-linear"],
156
+ },
157
+ },
158
+ },
159
+ },
160
+ "/mcp/stripe-v1": {
161
+ "get,post": {
162
+ "operationId": "stripe-mcp-server",
163
+ "summary": "Stripe MCP Proxy",
164
+ "x-zuplo-route": {
165
+ "corsPolicy": "none",
166
+ "handler": {
167
+ "module": "$import(@zuplo/runtime/mcp-gateway)",
168
+ "export": "McpProxyHandler",
169
+ "options": { "rewritePattern": "https://mcp.stripe.com/mcp" },
170
+ },
171
+ "policies": {
172
+ "inbound": ["auth0-managed-oauth", "mcp-token-exchange-stripe"],
173
+ },
174
+ },
175
+ },
176
+ },
177
+ },
178
+ }
179
+ ```
180
+
181
+ Once deployed (or running locally via `zuplo dev`), this gives clients two MCP
182
+ server URLs to add to their config:
183
+
184
+ - `https://<your-gateway>/mcp/linear-v1`
185
+ - `https://<your-gateway>/mcp/stripe-v1`
186
+
187
+ Both authenticate against the same Auth0 tenant; both produce one set of
188
+ analytics events distinguishable by `virtualServerName` and
189
+ `upstreamServerName`.
190
+
191
+ ## What each user sees on first connect
192
+
193
+ A user only signs in to the gateway once. From there, each upstream needs its
194
+ own one-time connect:
195
+
196
+ - The first time the user calls `/mcp/linear-v1`, the client opens a browser to
197
+ authorize Linear. The next call succeeds.
198
+ - Calling `/mcp/stripe-v1` for the first time produces a separate browser prompt
199
+ for Stripe. Authorizing Linear doesn't grant access to Stripe.
200
+
201
+ Each user's connection to each upstream is independent — one user authorizing
202
+ Linear has no effect on any other user.
203
+
204
+ ## Adding a per-route capability filter
205
+
206
+ To curate the tools a specific upstream exposes — say, restrict Linear to four
207
+ read tools — add a `mcp-capability-filter-inbound` policy and attach it to one
208
+ route's inbound chain:
209
+
210
+ ```jsonc
211
+ // config/policies.json — add to the policies array
212
+ {
213
+ "name": "filter-linear-read-only",
214
+ "policyType": "mcp-capability-filter-inbound",
215
+ "handler": {
216
+ "module": "$import(@zuplo/runtime/mcp-gateway)",
217
+ "export": "McpCapabilityFilterInboundPolicy",
218
+ "options": {
219
+ "tools": ["list_issues", "get_issue", "list_projects", "list_teams"],
220
+ },
221
+ },
222
+ }
223
+ ```
224
+
225
+ Then update the Linear route's policy chain so the filter runs **after** the
226
+ token exchange policy:
227
+
228
+ ```jsonc
229
+ "/mcp/linear-v1": {
230
+ "get,post": {
231
+ "operationId": "linear-mcp-server",
232
+ "x-zuplo-route": {
233
+ "policies": {
234
+ "inbound": [
235
+ "auth0-managed-oauth",
236
+ "mcp-token-exchange-linear",
237
+ "filter-linear-read-only"
238
+ ]
239
+ }
240
+ }
241
+ }
242
+ }
243
+ ```
244
+
245
+ Only the four named tools appear in `tools/list` responses on `/mcp/linear-v1`.
246
+ Any `tools/call` for an unlisted tool returns a JSON-RPC `MethodNotFound` error
247
+ before the request reaches the upstream. The Stripe route is unaffected —
248
+ capability filters are per-route.
249
+
250
+ ## Path and id conventions
251
+
252
+ The corp dogfood deployment uses these conventions, and they generalize well:
253
+
254
+ - **Route path**: `/mcp/<provider>-v<n>` — e.g., `/mcp/linear-v1`,
255
+ `/mcp/stripe-v1`, `/mcp/notion-v1`.
256
+ - **`operationId`**: `<provider>-mcp-server` — e.g., `linear-mcp-server`,
257
+ `stripe-mcp-server`.
258
+ - **Token-exchange policy name**: `mcp-token-exchange-<provider>` — the
259
+ `<provider>` portion is what becomes the upstream `id` (and the
260
+ `upstreamServerName` in analytics).
261
+ - **OAuth policy name**: pick one and reuse it; `auth0-managed-oauth` or
262
+ `oidc-managed-oauth` are clear choices.
263
+
264
+ The `-v<n>` suffix on the route path matters more than it looks: it gives you a
265
+ clean upgrade path when an upstream provider releases a new MCP server URL with
266
+ breaking changes. Add a new `/mcp/linear-v2` route with a new token exchange
267
+ policy (and a new id), publish the v2 endpoint, migrate clients, then retire v1
268
+ once the last client is off it.
269
+
270
+ ## Don't share an upstream id
271
+
272
+ The upstream `id` (either set explicitly via `options.id` or inferred from the
273
+ policy name) identifies each user's upstream connection. Two policies sharing
274
+ one id is a configuration error, and **changing** an id on a policy that already
275
+ has stored connections silently disconnects every existing user.
276
+
277
+ Pick the id once, document it, and treat it as part of the public contract of
278
+ the upstream just like the route path is part of the public contract of the
279
+ gateway.
280
+
281
+ ## Related
282
+
283
+ - [`McpProxyHandler` reference](./mcp-proxy-handler.mdx) — the full handler
284
+ contract.
285
+ - [Local development](./local-development.mdx) — run the multi-upstream
286
+ configuration locally without setting up Auth0.
287
+ - [Connect a gateway to an upstream OAuth provider](../how-to/connect-upstream-oauth.mdx)
288
+ — every per-upstream option, including manual client registration and
289
+ shared-OAuth mode.
290
+ - [Curate the tools an upstream exposes](../how-to/curate-tools.mdx) — add a
291
+ capability filter to one of the routes.
292
+ - [Connect MCP clients](../connect-clients/overview.mdx) — add multiple gateway
293
+ routes to a single client config.
@@ -0,0 +1,210 @@
1
+ ---
2
+ title: "Set up an MCP Gateway"
3
+ sidebar_label: "Set up the gateway"
4
+ description:
5
+ Wire up the Zuplo MCP Gateway in routes.oas.json and policies.json — pin the
6
+ compatibility date, register the runtime plugin, configure one OAuth policy
7
+ and one token-exchange policy per upstream, and add an MCP route.
8
+ ---
9
+
10
+ To turn any Zuplo project into an MCP Gateway, configure five things in source
11
+ control: the compatibility date in `zuplo.jsonc`, the runtime plugin in
12
+ `modules/zuplo.runtime.ts`, one MCP OAuth policy in `config/policies.json`, one
13
+ `mcp-token-exchange-inbound` policy per OAuth-protected upstream, and one route
14
+ per upstream in `config/routes.oas.json`. This guide walks through each piece
15
+ for a single-upstream gateway.
16
+
17
+ For the conceptual model — what each piece does and why the pieces are split the
18
+ way they are — see [How the MCP Gateway works](../how-it-works.mdx).
19
+
20
+ ## 1. Pin the compatibility date
21
+
22
+ MCP Gateway features require `compatibilityDate >= 2026-03-01` in `zuplo.jsonc`:
23
+
24
+ ```jsonc title="zuplo.jsonc"
25
+ {
26
+ "version": 1,
27
+ "compatibilityDate": "2026-03-01",
28
+ }
29
+ ```
30
+
31
+ New Zuplo projects default to a recent compatibility date, so this only applies
32
+ to existing projects being upgraded to use the MCP Gateway. See
33
+ [Compatibility dates](./compatibility-dates.mdx) for details.
34
+
35
+ ## 2. Register the MCP Gateway plugin
36
+
37
+ Add a `modules/zuplo.runtime.ts` file that registers `McpGatewayPlugin`:
38
+
39
+ ```ts title="modules/zuplo.runtime.ts"
40
+ import { RuntimeExtensions } from "@zuplo/runtime";
41
+ import { McpGatewayPlugin } from "@zuplo/runtime/mcp-gateway";
42
+
43
+ export function runtimeInit(runtime: RuntimeExtensions) {
44
+ runtime.addPlugin(new McpGatewayPlugin());
45
+ }
46
+ ```
47
+
48
+ The plugin registers the OAuth metadata, authorization endpoints, consent page,
49
+ and upstream connect callbacks the gateway needs. It's a no-op when no
50
+ MCP-related policy is present, so adding it to projects that don't yet use the
51
+ gateway has zero runtime cost.
52
+
53
+ ## 3. Define one OAuth policy
54
+
55
+ The OAuth policy authenticates inbound MCP requests against your identity
56
+ provider. Pick the first-class wrapper for your IdP — the
57
+ [provider catalog](../auth/overview.mdx#identity-providers) lists every
58
+ supported IdP. The Auth0 case looks like this:
59
+
60
+ ```jsonc title="config/policies.json"
61
+ {
62
+ "name": "auth0-managed-oauth",
63
+ "policyType": "mcp-auth0-oauth-inbound",
64
+ "handler": {
65
+ "module": "$import(@zuplo/runtime/mcp-gateway)",
66
+ "export": "McpAuth0OAuthInboundPolicy",
67
+ "options": {
68
+ "auth0Domain": "$env(AUTH0_DOMAIN)",
69
+ "clientId": "$env(AUTH0_CLIENT_ID)",
70
+ "clientSecret": "$env(AUTH0_CLIENT_SECRET)",
71
+ },
72
+ },
73
+ }
74
+ ```
75
+
76
+ Each wrapper takes a small set of provider-specific options (a domain, a tenant
77
+ ID, a subdomain, and so on) and derives the OIDC URLs from them. For IdPs
78
+ without a dedicated wrapper — Ory Hydra, Authentik, FusionAuth, PingFederate, a
79
+ custom OIDC server — use the generic `mcp-oauth-inbound` policy. See
80
+ [Configuring a generic OIDC provider](../auth/configuring-generic-oidc.mdx) for
81
+ the worked example.
82
+
83
+ :::caution
84
+
85
+ A project can have only one MCP OAuth policy. The gateway rejects any
86
+ configuration with two, regardless of variant. The same policy is attached to
87
+ every MCP route in the project — every route authenticates against the same
88
+ identity provider.
89
+
90
+ :::
91
+
92
+ ## 4. Define one token-exchange policy per upstream
93
+
94
+ Each OAuth-protected upstream gets its own `mcp-token-exchange-inbound` policy:
95
+
96
+ ```jsonc title="config/policies.json"
97
+ {
98
+ "name": "mcp-token-exchange-linear",
99
+ "policyType": "mcp-token-exchange-inbound",
100
+ "handler": {
101
+ "module": "$import(@zuplo/runtime/mcp-gateway)",
102
+ "export": "McpTokenExchangeInboundPolicy",
103
+ "options": {
104
+ "displayName": "Linear",
105
+ "protectedResourceMetadataUrl": "https://mcp.linear.app/.well-known/oauth-protected-resource",
106
+ "authMode": "user-oauth",
107
+ "scopes": [],
108
+ "clientRegistration": { "mode": "auto" },
109
+ },
110
+ },
111
+ }
112
+ ```
113
+
114
+ Name each policy `mcp-token-exchange-<id>`. The id after the prefix identifies
115
+ the upstream in analytics and connect URLs. Changing the id strands any existing
116
+ user-to-upstream connections, so pick it once and keep it.
117
+
118
+ For per-mode reference and worked examples per provider, see
119
+ [Connect a gateway to an upstream OAuth provider](../how-to/connect-upstream-oauth.mdx).
120
+
121
+ ## 5. Define one route per upstream
122
+
123
+ Each upstream gets a route in `routes.oas.json`. The handler points at the
124
+ upstream URL; the inbound policy chain attaches the OAuth policy followed by the
125
+ matching token exchange policy:
126
+
127
+ ```jsonc title="config/routes.oas.json"
128
+ {
129
+ "openapi": "3.1.0",
130
+ "info": { "title": "MCP Gateway", "version": "0.1.0" },
131
+ "paths": {
132
+ "/mcp/linear-v1": {
133
+ "get,post": {
134
+ "operationId": "linear-mcp-server",
135
+ "summary": "Linear MCP Proxy",
136
+ "x-zuplo-route": {
137
+ "corsPolicy": "none",
138
+ "handler": {
139
+ "module": "$import(@zuplo/runtime/mcp-gateway)",
140
+ "export": "McpProxyHandler",
141
+ "options": {
142
+ "rewritePattern": "https://mcp.linear.app/mcp",
143
+ },
144
+ },
145
+ "policies": {
146
+ "inbound": ["auth0-managed-oauth", "mcp-token-exchange-linear"],
147
+ },
148
+ },
149
+ },
150
+ },
151
+ },
152
+ }
153
+ ```
154
+
155
+ The path is yours to choose — `/mcp/<provider>-v<n>` is the recommended
156
+ convention because it makes the path self-describing and reserves room for
157
+ versioned upgrades, but the gateway works with any path the OpenAPI router
158
+ accepts.
159
+
160
+ `get,post` is Zuplo's multi-method shorthand. The handler rejects GET with
161
+ `405 Method Not Allowed` because the gateway only speaks stateless Streamable
162
+ HTTP over POST — see [`McpProxyHandler`](./mcp-proxy-handler.mdx) for the full
163
+ handler reference.
164
+
165
+ Every MCP route must set `operationId`. Across the project, no two MCP routes
166
+ can share an `operationId` or a path, and no two `mcp-token-exchange-*` policies
167
+ can share an upstream `id`. If `operationId` is missing or duplicated, the
168
+ gateway returns a configuration error on the first matching request.
169
+
170
+ ## Verify the gateway is wired up
171
+
172
+ Start the project with `zuplo dev` and the gateway is reachable at
173
+ `http://127.0.0.1:9000/mcp/linear-v1`. A quick sanity check is to send an
174
+ unauthenticated POST:
175
+
176
+ ```bash
177
+ curl -i -X POST http://127.0.0.1:9000/mcp/linear-v1 \
178
+ -H "Content-Type: application/json" \
179
+ -d '{"jsonrpc":"2.0","method":"tools/list","id":1}'
180
+ ```
181
+
182
+ The gateway should return `401 Unauthorized` with a `WWW-Authenticate` header
183
+ that points at the Protected Resource Metadata URL. If you see that, the OAuth
184
+ policy is wired up correctly. See [Local development](./local-development.mdx)
185
+ for the dev-loop specifics, including the loopback-only login shortcut that
186
+ skips your IdP during development.
187
+
188
+ ## Add more upstreams
189
+
190
+ The pattern is the same for each additional upstream: one MCP OAuth policy stays
191
+ shared across the project, and one `mcp-token-exchange-*` policy and one route
192
+ get added per new upstream MCP server. Per-user state is keyed by
193
+ `(subjectId, upstreamServerId)`, so each user maintains independent connections
194
+ to each upstream they consent to.
195
+
196
+ For a worked example with two upstreams and the full file layout, see
197
+ [Add multiple upstream MCP servers](./multi-upstream.mdx).
198
+
199
+ ## Related
200
+
201
+ - [`McpProxyHandler` reference](./mcp-proxy-handler.mdx) — every option and
202
+ every behavior of the route handler.
203
+ - [Compatibility dates](./compatibility-dates.mdx) — why `2026-03-01` is
204
+ required and what older dates break.
205
+ - [Local development](./local-development.mdx) — dev-loop, loopback URLs, the
206
+ `/oauth/dev-login` shortcut, and the `workerd` restart quirk.
207
+ - [Add multiple upstream MCP servers](./multi-upstream.mdx) — one project, many
208
+ upstream MCP servers.
209
+ - [Curate the tools an upstream exposes](../how-to/curate-tools.mdx) — restrict
210
+ and re-project the tools, prompts, and resources a route exposes.
@@ -0,0 +1,127 @@
1
+ ---
2
+ title: "Connect ChatGPT"
3
+ sidebar_label: "ChatGPT"
4
+ description:
5
+ Connect ChatGPT to a Zuplo MCP Gateway as a custom connector using Developer
6
+ Mode, complete the OAuth flow, and start using your tools in conversation.
7
+ ---
8
+
9
+ ChatGPT connects to remote MCP servers as **custom connectors**. To add a custom
10
+ connector that exposes general-purpose MCP tools, you need to enable **Developer
11
+ Mode** on your ChatGPT account. Once enabled, paste the gateway URL into
12
+ ChatGPT's connector settings and complete the OAuth flow.
13
+
14
+ :::note
15
+
16
+ ChatGPT's general-purpose custom-connector support runs through Developer Mode,
17
+ which is available on Pro, Team, Enterprise, and Edu plans. Before Developer
18
+ Mode shipped, connector support in ChatGPT was limited to read-only Deep
19
+ Research connectors. Use Developer Mode to expose the full range of tools the
20
+ Zuplo MCP Gateway provides.
21
+
22
+ :::
23
+
24
+ ## Prerequisites
25
+
26
+ - A Zuplo project with the MCP Gateway plugin configured and at least one MCP
27
+ route. See the [quickstart](../quickstart.mdx) if you haven't set one up yet.
28
+ - A ChatGPT Pro, Team, Enterprise, or Edu subscription.
29
+ - Developer Mode enabled on your ChatGPT account. The toggle lives in
30
+ **Settings** → **Connectors** → **Advanced** (the exact location varies by
31
+ plan; see OpenAI's
32
+ [Apps SDK documentation](https://developers.openai.com/apps-sdk/) for current
33
+ instructions).
34
+
35
+ ## Get the route URL
36
+
37
+ Each MCP route in `config/routes.oas.json` is reachable at
38
+ `https://{deploymentUrl}/{routePath}` once deployed — for example
39
+ `https://{deploymentUrl}/mcp/linear-v1`.
40
+
41
+ ## Add the connector
42
+
43
+ <Stepper>
44
+
45
+ 1. **Open Connectors settings in ChatGPT.**
46
+
47
+ In the ChatGPT web app, open **Settings** → **Connectors**.
48
+
49
+ 2. **Add a custom connector.**
50
+
51
+ Click the option to add a custom connector. Depending on your plan, this may
52
+ be **Add custom connector**, **Create**, or **Advanced** → **Add MCP
53
+ server**.
54
+
55
+ 3. **Enter the gateway URL.**
56
+
57
+ Paste the route URL. Give the connector a name and description — these are
58
+ what ChatGPT shows in the conversation interface.
59
+
60
+ 4. **Authenticate against the gateway.**
61
+
62
+ Save the connector. ChatGPT opens the gateway's OAuth flow. Sign in with the
63
+ identity provider you configured for the gateway.
64
+
65
+ 5. **Complete the upstream connection.**
66
+
67
+ The gateway shows a consent page with the upstream MCP server the route
68
+ proxies to. Click **Connect** next to the upstream, complete its OAuth flow,
69
+ then click **Authorize** to finish.
70
+
71
+ 6. **Enable the connector for chats.**
72
+
73
+ Back in ChatGPT, enable the connector for the conversations or assistants
74
+ where you want it active. Tools from the gateway then appear when ChatGPT
75
+ needs them.
76
+
77
+ </Stepper>
78
+
79
+ ## What ChatGPT supports
80
+
81
+ ChatGPT registers itself with the gateway through
82
+ [Dynamic Client Registration (DCR)](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization)
83
+ and the newer
84
+ [Client ID Metadata Documents (CIMD)](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization)
85
+ flow. It supports:
86
+
87
+ - **Tools** — invoke gateway-exposed tools from the conversation.
88
+ - **MCP Apps** — render interactive HTML widgets inline. This is the same
89
+ surface that powers the OpenAI Apps SDK, which is built directly on top of MCP
90
+ Apps.
91
+
92
+ ChatGPT doesn't currently consume prompts, resources, roots, sampling, or
93
+ elicitation from a remote MCP server.
94
+
95
+ ## Build an Apps SDK app on top of the gateway
96
+
97
+ If you're authoring an MCP server intended to render rich UI inside ChatGPT,
98
+ read OpenAI's [Apps SDK documentation](https://developers.openai.com/apps-sdk/)
99
+ — the Apps SDK builds on the MCP Apps extension, so an Apps SDK app **is** an
100
+ MCP server with UI conventions on top. The Zuplo MCP Gateway forwards
101
+ Apps-related metadata (`_meta.ui.*`) from upstream MCP servers unchanged. Tool
102
+ authors who want to ship UI to ChatGPT should follow the Apps SDK guide; the
103
+ gateway transparently relays the additional metadata to the client.
104
+
105
+ For more background on Apps SDK and Zuplo-hosted MCP servers, see
106
+ [OpenAI Apps SDK with Zuplo](../../mcp-server/openai-apps-sdk.mdx).
107
+
108
+ ## Troubleshooting
109
+
110
+ - **"Custom connector" option isn't visible.** Confirm your plan supports
111
+ Developer Mode (Pro, Team, Enterprise, or Edu) and that Developer Mode is
112
+ enabled in your settings.
113
+ - **Sign-in succeeds but no tools appear.** Tools only appear when ChatGPT
114
+ decides to invoke them. Try a prompt that mentions the action you want to
115
+ take. If the connector itself is disabled in a conversation, ChatGPT doesn't
116
+ see any of its tools.
117
+ - **OAuth fails with a redirect error.** ChatGPT registers its redirect URI
118
+ dynamically. The gateway accepts dynamic registration by default. If you've
119
+ locked down DCR on your identity provider, switch to a provider that supports
120
+ DCR, or pre-register an OAuth app for ChatGPT.
121
+
122
+ ## Related
123
+
124
+ - [Connect MCP clients overview](./overview.mdx)
125
+ - OpenAI's [Apps SDK documentation](https://developers.openai.com/apps-sdk/)
126
+ - [OpenAI Apps SDK with Zuplo](../../mcp-server/openai-apps-sdk.mdx)
127
+ - [Authentication overview](../auth/overview.mdx)