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,162 @@
1
+ ---
2
+ title: "Capability filtering"
3
+ sidebar_label: "Capability filtering"
4
+ description:
5
+ How the Zuplo MCP Gateway curates the tools, prompts, resources, and resource
6
+ templates an upstream MCP server exposes — what the
7
+ mcp-capability-filter-inbound policy filters, how projections work, and where
8
+ the boundary actually lives.
9
+ ---
10
+
11
+ The Model Context Protocol lets a server advertise tools, prompts, resources,
12
+ and resource templates. When the Zuplo MCP Gateway proxies an upstream server,
13
+ every one of those capabilities flows through to the client by default. That's
14
+ the right behavior when the upstream is small and trusted, and the wrong
15
+ behavior when the upstream exposes dozens of operations only a few of which
16
+ belong in front of an AI client.
17
+
18
+ The `mcp-capability-filter-inbound` policy is how the gateway curates that
19
+ surface area. This page covers what the policy filters, the rules that govern
20
+ when capabilities are exposed versus hidden, the projection model that lets the
21
+ gateway rewrite descriptions, and the boundary the filter actually enforces.
22
+
23
+ To attach the policy to a route and walk through worked examples, see
24
+ [Curate the tools an upstream exposes](./how-to/curate-tools.mdx).
25
+
26
+ ## What the policy filters
27
+
28
+ The policy operates on four MCP capability types, each matched by the upstream
29
+ identifier the protocol uses:
30
+
31
+ | Capability | Matched by | List method | Invocation method |
32
+ | ------------------- | ------------- | -------------------------- | ----------------- |
33
+ | `tools` | `name` | `tools/list` | `tools/call` |
34
+ | `prompts` | `name` | `prompts/list` | `prompts/get` |
35
+ | `resources` | `uri` | `resources/list` | `resources/read` |
36
+ | `resourceTemplates` | `uriTemplate` | `resources/templates/list` | `resources/read` |
37
+
38
+ Matching is case-sensitive and exact. There's no regex, glob, or category
39
+ matching — if the upstream returns a tool named `createUser` and the policy
40
+ lists `create_user`, the tool stays hidden.
41
+
42
+ ## Omit versus empty array
43
+
44
+ The behavior of each option depends on whether it's present at all:
45
+
46
+ - **Omit the option** — every capability of that type passes through unchanged.
47
+ This is the default and is useful when filtering tools but leaving prompts and
48
+ resources alone.
49
+ - **Provide an empty array** — expose nothing of that type. The list response
50
+ becomes empty and every direct call returns `MethodNotFound`.
51
+ - **Provide entries** — expose only the listed items. Everything else is
52
+ filtered or blocked.
53
+
54
+ The omit-versus-empty-array distinction is the single most consequential rule in
55
+ the filter. Omitting an option is a pass-through; an empty array is the opposite
56
+ — it hides every capability of that type. Confusing the two is the most common
57
+ source of "why can the client still see that tool?" reports.
58
+
59
+ ## Projections
60
+
61
+ Each allow-list entry is either a plain string (name only) or a projection
62
+ object that keeps the upstream identifier but overrides what the client sees.
63
+ Projections let the gateway rewrite the description for clarity, override tool
64
+ annotations like `destructiveHint` or `readOnlyHint`, attach `_meta` fields that
65
+ downstream middleware reads, or rewrite a resource's `name` and `mimeType` for a
66
+ curated catalog.
67
+
68
+ The upstream identifier — `name` for tools and prompts, `uri` for resources,
69
+ `uriTemplate` for resource templates — is always required and serves as the
70
+ stable match key. Annotation and `_meta` overrides are deep-merged with the
71
+ upstream values: fields the projection specifies win, fields it doesn't specify
72
+ pass through.
73
+
74
+ Schema fields stay upstream. `inputSchema` and `outputSchema` always come from
75
+ the upstream list response — the projection can't rewrite parameter shapes or
76
+ enforce additional validation. A separate policy on the route handles those
77
+ concerns when they come up.
78
+
79
+ ## How the filter behaves at runtime
80
+
81
+ When the gateway sees a successful response to `tools/list`, `prompts/list`,
82
+ `resources/list`, or `resources/templates/list`, it reads the list from the
83
+ upstream response, keeps only items whose identifier appears on the allow-list,
84
+ merges any projection overrides into the kept items, and returns the filtered
85
+ list. Items the upstream returned that aren't on the allow-list are silently
86
+ dropped — the client never learns they exist.
87
+
88
+ When the gateway sees `tools/call`, `prompts/get`, or `resources/read`, it reads
89
+ the target identifier from the request (`params.name` for tools and prompts,
90
+ `params.uri` for resources). If the identifier isn't on the matching allow-list,
91
+ the gateway returns a JSON-RPC `MethodNotFound` error **before forwarding
92
+ upstream**:
93
+
94
+ ```json
95
+ {
96
+ "jsonrpc": "2.0",
97
+ "id": "1",
98
+ "error": {
99
+ "code": -32601,
100
+ "message": "Method not found"
101
+ }
102
+ }
103
+ ```
104
+
105
+ The filter blocks calls before forwarding upstream, so a client that already
106
+ knows a hidden tool's name — from a cached `tools/list`, a different gateway, or
107
+ guesswork — still can't invoke it. The same block fires when the option is set
108
+ to an empty array: every direct call of that capability type returns
109
+ `MethodNotFound`.
110
+
111
+ ## Batch requests
112
+
113
+ The policy handles JSON-RPC batch requests with two rules. List responses inside
114
+ a batch are filtered per item — the policy matches each response item to its
115
+ originating list request by ID and applies the same filtering and projection
116
+ rules as for a single response. Hidden invocations inside a batch block the
117
+ whole batch with a single `MethodNotFound` error; the gateway does not split,
118
+ partially filter, or forward sibling items.
119
+
120
+ ## Where it sits in the policy chain
121
+
122
+ The capability filter belongs **after** any policy that produces or replaces the
123
+ upstream response — `mcp-token-exchange-inbound` is the most common one. The
124
+ filter operates on the final response, so policies that transform the response
125
+ upstream of it have already done their work by the time the filter runs.
126
+
127
+ Keep the filter last in the chain even when there's no
128
+ `mcp-token-exchange-inbound` policy on the route (for example, an API-key
129
+ upstream via `set-headers-inbound` or `set-upstream-api-key-inbound`), so any
130
+ future inbound policies that produce or replace responses run before it.
131
+
132
+ ## What the filter does not do
133
+
134
+ A few capabilities are intentionally out of scope:
135
+
136
+ - **No schema overrides.** `inputSchema` and `outputSchema` always come from the
137
+ upstream list response.
138
+ - **No regex, glob, or category matching.** Allow-lists are exact, by
139
+ identifier. If the upstream renames a tool, the policy entry must be updated
140
+ to match.
141
+ - **No non-JSON filtering.** Filtering applies only to JSON responses. Streamed
142
+ or binary responses pass through untouched.
143
+ - **No effect on capability metadata in `initialize`.** The protocol-level
144
+ `serverCapabilities` block in the `initialize` response advertises which
145
+ capability types the server supports (tools, prompts, resources). The filter
146
+ doesn't strip those flags. A client sees that the gateway supports tools even
147
+ when the tool allow-list is empty; only the list and call responses change.
148
+ - **No quota or rate limit.** Capability filtering trims the surface area the
149
+ gateway exposes but doesn't bound how often clients can call what remains.
150
+ Pair it with the [`rate-limit-inbound`](../policies/rate-limit-inbound.mdx)
151
+ policy when usage controls are needed.
152
+
153
+ ## Related
154
+
155
+ - [Curate the tools an upstream exposes](./how-to/curate-tools.mdx) — how to
156
+ attach the policy, override descriptions, and verify the filter is active.
157
+ - [`McpProxyHandler` reference](./code-config/mcp-proxy-handler.mdx) — the route
158
+ handler the filter runs in front of.
159
+ - [Per-user OAuth to upstream MCP servers](./auth/upstream-oauth.mdx) — the
160
+ upstream side of the picture; the filter usually composes with the
161
+ token-exchange policy on the same route.
162
+ - [MCP capability semantics in the specification](https://modelcontextprotocol.io/specification/2025-11-25/server/tools).
@@ -0,0 +1,33 @@
1
+ ---
2
+ title: "Compatibility dates"
3
+ sidebar_label: "Compatibility dates"
4
+ description:
5
+ The Zuplo MCP Gateway requires compatibilityDate 2026-03-01 or later in
6
+ zuplo.jsonc.
7
+ ---
8
+
9
+ The Zuplo MCP Gateway requires `compatibilityDate >= 2026-03-01` in
10
+ `zuplo.jsonc`.
11
+
12
+ ```jsonc
13
+ // zuplo.jsonc
14
+ {
15
+ "version": 1,
16
+ "compatibilityDate": "2026-03-01",
17
+ }
18
+ ```
19
+
20
+ :::caution
21
+
22
+ The build fails if your project uses any MCP Gateway feature (the
23
+ `McpProxyHandler` handler or an `mcp-*-inbound` policy) with a compatibility
24
+ date older than `2026-03-01`. Bump the date in `zuplo.jsonc` before adding those
25
+ features.
26
+
27
+ :::
28
+
29
+ New Zuplo projects default to a recent compatibility date, so this only applies
30
+ to existing projects being upgraded to use the MCP Gateway.
31
+
32
+ For background on Zuplo's compatibility-date system in general, see
33
+ [Compatibility dates](../../programmable-api/compatibility-dates.mdx).
@@ -0,0 +1,198 @@
1
+ ---
2
+ title: "Local development"
3
+ sidebar_label: "Local development"
4
+ description:
5
+ Run the Zuplo MCP Gateway locally with zuplo dev, bypass your identity
6
+ provider with the loopback /oauth/dev-login shortcut, wire up an MCP client
7
+ against 127.0.0.1, and recover cleanly from the known workerd restart quirk.
8
+ ---
9
+
10
+ The MCP Gateway runs the same way locally as any Zuplo project — `zuplo dev`,
11
+ port `9000`, hot reload on file changes. A few details are specific to the
12
+ gateway: the gateway prefers `127.0.0.1` over `localhost`, OAuth login can be
13
+ short-circuited entirely in dev, and the local `workerd` worker needs a full
14
+ restart after some MCP client connect attempts.
15
+
16
+ ## Start the gateway
17
+
18
+ From the project root:
19
+
20
+ ```bash
21
+ zuplo dev
22
+ ```
23
+
24
+ The gateway listens at `http://127.0.0.1:9000`. Each MCP route in
25
+ `routes.oas.json` becomes reachable at that origin — for example
26
+ `http://127.0.0.1:9000/mcp/linear-v1`.
27
+
28
+ ## Prefer `127.0.0.1` over `localhost`
29
+
30
+ OAuth metadata, callback URLs, and the in-dev login shortcut all key off the
31
+ request origin. Other loopback aliases (`localhost`, `::1`, `[::1]`) can cause
32
+ subtle OAuth issues in local dev.
33
+
34
+ When configuring an MCP client locally, use `127.0.0.1`:
35
+
36
+ ```jsonc
37
+ // Good
38
+ "url": "http://127.0.0.1:9000/mcp/linear-v1"
39
+
40
+ // Avoid in local dev — works for most things, breaks subtly for OAuth
41
+ "url": "http://localhost:9000/mcp/linear-v1"
42
+ ```
43
+
44
+ The same applies to any callback or redirect URI you configure with an identity
45
+ provider for local testing.
46
+
47
+ ## Bypass your IdP with `/oauth/dev-login`
48
+
49
+ Setting up a real OIDC provider for local development is friction — you'd have
50
+ to register a localhost callback, manage test users, and so on. The gateway
51
+ exposes a loopback-only shortcut that skips the IdP round-trip entirely and
52
+ signs you in as a fixed `dev-browser-user` subject.
53
+
54
+ To use it, set `browserLogin.url` to the dev-login URL when configuring the
55
+ OAuth policy:
56
+
57
+ ```jsonc
58
+ // config/policies.json — using the generic mcp-oauth-inbound policy
59
+ {
60
+ "name": "dev-oauth",
61
+ "policyType": "mcp-oauth-inbound",
62
+ "handler": {
63
+ "module": "$import(@zuplo/runtime/mcp-gateway)",
64
+ "export": "McpOAuthInboundPolicy",
65
+ "options": {
66
+ "oidc": {
67
+ "issuer": "http://127.0.0.1:9000",
68
+ "jwksUrl": "http://127.0.0.1:9000/.well-known/jwks.json",
69
+ },
70
+ "browserLogin": {
71
+ "url": "http://127.0.0.1:9000/oauth/dev-login",
72
+ },
73
+ },
74
+ },
75
+ }
76
+ ```
77
+
78
+ When `browserLogin.url` points at `/oauth/dev-login`, the
79
+ `browserLogin.tokenUrl`, `browserLogin.clientId`, and
80
+ `browserLogin.clientSecret` options aren't required. The consent page renders
81
+ normally.
82
+
83
+ :::caution
84
+
85
+ The `/oauth/dev-login` route returns `403 Forbidden` for any request that
86
+ doesn't arrive over loopback. It's not a security risk to leave configured for
87
+ production, but it's also not useful — production deployments should use a real
88
+ OIDC provider through one of the
89
+ [IdP-specific wrappers](../auth/overview.mdx#identity-providers).
90
+
91
+ :::
92
+
93
+ A common pattern is keeping two OAuth policies in the project — one for
94
+ production (Auth0, Okta, Entra, or any other supported IdP) and one for local
95
+ dev — and selecting between them in `routes.oas.json` based on the environment.
96
+
97
+ ## Environment variables
98
+
99
+ When the OAuth policy reads from `$env(...)` references, define the values in a
100
+ `.env` file at the project root:
101
+
102
+ ```bash
103
+ # .env
104
+
105
+ # Auth0 wrapper interpolations
106
+ AUTH0_DOMAIN=your-tenant.us.auth0.com
107
+ AUTH0_CLIENT_ID=your-auth0-web-app-client-id
108
+ AUTH0_CLIENT_SECRET=your-auth0-web-app-client-secret
109
+
110
+ # Optional: the audience the gateway requires on issued tokens
111
+ AUTH0_AUDIENCE=https://mcp-gateway.example.com
112
+ ```
113
+
114
+ `.env` is read at `zuplo dev` startup. Restart the dev server after adding or
115
+ changing an environment variable.
116
+
117
+ Never commit `.env` to source control. Instead, check in a `.env.example` (or
118
+ `env.example`) that documents which variables are required and an
119
+ empty/placeholder value for each.
120
+
121
+ ## Adding the gateway to a local MCP client
122
+
123
+ Once `zuplo dev` is running and the route is reachable, add the gateway URL to
124
+ your MCP client config the same way you'd add any other remote MCP server. For
125
+ example, with Claude Desktop:
126
+
127
+ ```jsonc
128
+ // claude_desktop_config.json
129
+ {
130
+ "mcpServers": {
131
+ "linear-via-zuplo-local": {
132
+ "url": "http://127.0.0.1:9000/mcp/linear-v1",
133
+ },
134
+ },
135
+ }
136
+ ```
137
+
138
+ The client triggers the gateway's OAuth flow on first connect. With
139
+ `/oauth/dev-login` configured, the browser tab opens, lands on the consent page
140
+ without any IdP login, and you connect each upstream through its normal browser
141
+ OAuth flow. Subsequent calls reuse the issued tokens until they expire.
142
+
143
+ See [Connect MCP clients](../connect-clients/overview.mdx) for client-specific
144
+ snippets and the connect URL format.
145
+
146
+ ## When `zuplo dev` crashes after a connect attempt
147
+
148
+ Some MCP client connect attempts can leave the local dev server in a state where
149
+ hot reload no longer recovers it. If the dev server stops responding after an
150
+ MCP client connects — particularly after browser OAuth callbacks finish — fully
151
+ restart `zuplo dev`:
152
+
153
+ ```bash
154
+ # Stop zuplo dev with Ctrl+C
155
+ # Start it again
156
+ zuplo dev
157
+ ```
158
+
159
+ Then have the MCP client reconnect. A restart doesn't force a re-consent — your
160
+ upstream tokens are still stored.
161
+
162
+ This is a known dev-only quirk and doesn't affect deployed gateways.
163
+
164
+ ## Verifying the gateway is up
165
+
166
+ Two quick checks that don't require an MCP client:
167
+
168
+ **Fetch the well-known OAuth metadata for a route.** The path follows the
169
+ route's `operationId`:
170
+
171
+ ```bash
172
+ curl http://127.0.0.1:9000/.well-known/oauth-protected-resource/mcp/linear-v1
173
+ ```
174
+
175
+ A correct response is JSON with `resource`, `authorization_servers`,
176
+ `bearer_methods_supported`, and `scopes_supported` fields.
177
+
178
+ **Send a POST without a token.** The gateway should return `401` with a
179
+ `WWW-Authenticate` header pointing at the Protected Resource Metadata URL:
180
+
181
+ ```bash
182
+ curl -i -X POST http://127.0.0.1:9000/mcp/linear-v1 \
183
+ -H "Content-Type: application/json" \
184
+ -d '{"jsonrpc":"2.0","method":"tools/list","id":1}'
185
+ ```
186
+
187
+ If you see the 401 plus the challenge, the OAuth policy is wired up correctly.
188
+ The next call from a real client will then start the OAuth dance.
189
+
190
+ ## Next steps
191
+
192
+ - [`McpProxyHandler` reference](./mcp-proxy-handler.mdx) — the route handler the
193
+ gateway uses for proxying.
194
+ - [Compatibility dates](./compatibility-dates.mdx) — pin `2026-03-01` in
195
+ `zuplo.jsonc`.
196
+ - [Multi-upstream pattern](./multi-upstream.mdx) — one project, many upstreams.
197
+ - [Connect MCP clients](../connect-clients/overview.mdx) — wire each client to
198
+ the local or deployed gateway URL.
@@ -0,0 +1,186 @@
1
+ ---
2
+ title: "McpProxyHandler reference"
3
+ sidebar_label: "McpProxyHandler"
4
+ description:
5
+ The route handler that turns a Zuplo path into an MCP Gateway endpoint —
6
+ forwards POST requests to an upstream MCP server, rejects GET with 405, and
7
+ emits per-capability analytics on every call.
8
+ ---
9
+
10
+ `McpProxyHandler` is the route handler that backs every MCP Gateway route. It
11
+ accepts stateless Streamable HTTP requests over POST, forwards them to the
12
+ configured upstream MCP server using Zuplo's standard URL rewrite, and emits a
13
+ pair of analytics events per request so the gateway dashboard knows what each
14
+ capability call did.
15
+
16
+ ## When to use it
17
+
18
+ Use `McpProxyHandler` on any route that proxies to an upstream MCP server. Pair
19
+ it with at least one MCP OAuth policy on the inbound chain; add an
20
+ `mcp-token-exchange-inbound` policy when the upstream itself requires OAuth, and
21
+ optionally `mcp-capability-filter-inbound` to curate what the upstream
22
+ advertises.
23
+
24
+ If the upstream uses a static API key or static header instead of OAuth, keep
25
+ the MCP OAuth policy on the route, drop the token exchange policy, and add
26
+ [`set-upstream-api-key-inbound`](../../policies/set-upstream-api-key-inbound.mdx)
27
+ or [`set-headers-inbound`](../../policies/set-headers-inbound.mdx) to attach the
28
+ credential before the handler forwards.
29
+
30
+ ## Configuration
31
+
32
+ The handler is referenced from the route's `x-zuplo-route.handler` block in
33
+ `routes.oas.json`:
34
+
35
+ ```jsonc
36
+ "x-zuplo-route": {
37
+ "corsPolicy": "none",
38
+ "handler": {
39
+ "module": "$import(@zuplo/runtime/mcp-gateway)",
40
+ "export": "McpProxyHandler",
41
+ "options": {
42
+ "rewritePattern": "https://mcp.linear.app/mcp"
43
+ }
44
+ },
45
+ "policies": {
46
+ "inbound": [
47
+ "auth0-managed-oauth",
48
+ "mcp-token-exchange-linear"
49
+ ]
50
+ }
51
+ }
52
+ ```
53
+
54
+ Set `corsPolicy` to `"none"`. MCP clients aren't browser-based and shouldn't be
55
+ sending ambient credentials.
56
+
57
+ ## Options
58
+
59
+ ### `rewritePattern` (required)
60
+
61
+ The upstream MCP server URL. The handler forwards each authenticated POST to
62
+ this URL, with the resolved upstream `Authorization: Bearer` header applied by
63
+ the token exchange policy.
64
+
65
+ Two value forms are supported:
66
+
67
+ - **A literal HTTPS or HTTP URL.** Used verbatim as the upstream target.
68
+ - **An environment-variable reference of the form `${env.X}`.** The variable
69
+ must resolve to a fully-qualified HTTP(S) URL.
70
+
71
+ ```jsonc
72
+ // Literal URL
73
+ { "rewritePattern": "https://mcp.linear.app/mcp" }
74
+
75
+ // Environment variable
76
+ { "rewritePattern": "${env.UPSTREAM_MCP_URL}" }
77
+ ```
78
+
79
+ Dynamic request-based patterns are explicitly rejected — MCP routes need a
80
+ stable upstream URL.
81
+
82
+ :::caution
83
+
84
+ The URL Rewrite handler's broader template syntax — `${params.x}`,
85
+ `${headers.get("x")}`, and so on — is **not** supported on `rewritePattern` for
86
+ MCP routes. Use a literal URL or an `${env.X}` reference.
87
+
88
+ :::
89
+
90
+ ### `forwardSearch` (optional)
91
+
92
+ Type: `boolean`. Default: `true`.
93
+
94
+ When `true`, the inbound request's query string is appended to the upstream URL
95
+ before forwarding. Set to `false` to drop client query parameters.
96
+
97
+ ### `followRedirects` (optional)
98
+
99
+ Type: `boolean`. Default: `false`.
100
+
101
+ When `false`, redirects from the upstream return as-is to the client (status
102
+ code and `Location` header passed through). Set to `true` to have the runtime
103
+ follow them transparently.
104
+
105
+ ### `mtlsCertificate` (optional)
106
+
107
+ Type: `string`. The id of an mTLS certificate registered with the Zuplo project.
108
+ When set, the upstream fetch uses mutual TLS with the specified client
109
+ certificate. Most MCP upstreams don't require mTLS; leave this unset unless you
110
+ specifically need it.
111
+
112
+ ## Behavior
113
+
114
+ ### GET returns 405
115
+
116
+ The gateway only speaks stateless Streamable HTTP, and the MCP authorization
117
+ spec uses POST for every JSON-RPC call. A `GET` to an MCP route returns:
118
+
119
+ ```http
120
+ HTTP/1.1 405 Method Not Allowed
121
+ Allow: POST
122
+ Content-Type: application/problem+json
123
+
124
+ {
125
+ "type": "https://httpproblems.com/http-status/405",
126
+ "status": 405,
127
+ "detail": "MCP Gateway routes support stateless Streamable HTTP requests over POST. Server-sent event GET streams are not supported."
128
+ }
129
+ ```
130
+
131
+ If you've seen an MCP server that exposes a GET endpoint for SSE event streams,
132
+ that's a different transport. The Zuplo MCP Gateway is Streamable HTTP,
133
+ POST-only.
134
+
135
+ ### POST forwards to the upstream
136
+
137
+ A POST request runs through the inbound policy chain, then the handler emits
138
+ capability analytics events, forwards to the upstream URL, and emits a
139
+ completion event with `outcome`, `mcpStatus`, `latencyMs`, and any JSON-RPC
140
+ error details.
141
+
142
+ Inbound auth headers don't leak to the upstream — the gateway-issued bearer
143
+ token is stripped, and the token exchange policy sets the upstream's own
144
+ `Authorization: Bearer <upstream-token>` header.
145
+
146
+ ## Route requirements
147
+
148
+ Every route that uses `McpProxyHandler` must:
149
+
150
+ - **Set `operationId`.** It's used to identify the MCP route.
151
+ - **Include an MCP OAuth policy** in the inbound chain — one of the
152
+ [IdP-specific wrappers](../auth/overview.mdx#identity-providers) (Auth0,
153
+ Cognito, Clerk, Entra, Google, Keycloak, Logto, Okta, OneLogin, Ping, WorkOS)
154
+ or the generic `mcp-oauth-inbound`.
155
+ - **Include at most one `mcp-token-exchange-inbound` policy.**
156
+
157
+ Across the project:
158
+
159
+ - No two MCP routes can share an `operationId`.
160
+ - No two MCP routes can share a path.
161
+ - No two `mcp-token-exchange-*` policies can share an upstream `id`.
162
+
163
+ ## Analytics
164
+
165
+ Every POST emits two analytics events when the request body parses as a JSON-RPC
166
+ call:
167
+
168
+ - A **`capability_invocation_started`** event fired before the upstream fetch,
169
+ carrying the parsed `mcpMethod` and `capabilityName`.
170
+ - A **`capability_invocation_completed`** event fired after the response,
171
+ carrying `outcome`, `mcpStatus`, `latencyMs`, and any JSON-RPC error details.
172
+
173
+ Each event also includes the route's `operationId` (as `virtualServerName`), the
174
+ upstream `id` (as `upstreamServerName`), the authenticated `subjectId`, the
175
+ `authProfileId`, and the `upstreamAuthMode`. See
176
+ [Analytics](../observability/analytics.mdx) for the dashboard view and
177
+ [Logging](../observability/logging.mdx) for the structured-log counterpart.
178
+
179
+ ## Related
180
+
181
+ - `mcp-token-exchange-inbound` — resolves the upstream credential and handles
182
+ upstream 401 refresh and retry.
183
+ - `mcp-capability-filter-inbound` — curates the upstream surface area on a
184
+ per-route basis.
185
+ - [Multi-upstream pattern](./multi-upstream.mdx) — pair one `McpProxyHandler`
186
+ route with each upstream MCP server in one project.