zuplo 7.13.2 → 7.13.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/ai-gateway/integrations/ai-sdk.mdx +4 -5
- package/docs/ai-gateway/providers.mdx +1 -1
- package/docs/ai-gateway/universal-api.mdx +1 -1
- package/docs/articles/version-support-policy.mdx +164 -41
- package/docs/policies/mcp-capability-filter-inbound/doc.md +177 -6
- package/docs/policies/mcp-capability-filter-inbound/schema.json +2 -2
- package/docs/policies/mcp-token-exchange-inbound/doc.md +6 -5
- package/docs/policies/mcp-token-exchange-inbound/schema.json +2 -2
- package/package.json +5 -5
|
@@ -146,11 +146,10 @@ const { text } = await generateText({
|
|
|
146
146
|
|
|
147
147
|
### xAI
|
|
148
148
|
|
|
149
|
-
|
|
150
|
-
Responses API
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
models reach it is the matrix in [AI Providers](../providers.mdx).
|
|
149
|
+
Use either `xai(...)` or `xai.chat(...)`. The bare callable targets xAI's
|
|
150
|
+
Responses API (`/v1/responses`), and `xai.chat(...)` targets
|
|
151
|
+
`/v1/chat/completions`. Use `xai(...)` for xAI's server-side tools, such as
|
|
152
|
+
`web_search` and `x_search`, because xAI runs them only on the Responses API.
|
|
154
153
|
|
|
155
154
|
```typescript
|
|
156
155
|
import { createXai } from "@ai-sdk/xai";
|
|
@@ -46,7 +46,7 @@ The following capabilities are supported across providers:
|
|
|
46
46
|
| Anthropic | ✅ | ❌ | ❌ | ✅ |
|
|
47
47
|
| Google | ✅ | ✅ | ❌ | ❌ |
|
|
48
48
|
| Mistral | ✅ | ✅ | ❌ | ❌ |
|
|
49
|
-
| xAI | ✅ | ✅ |
|
|
49
|
+
| xAI | ✅ | ✅ | ✅ | ❌ |
|
|
50
50
|
| Azure AI | ✅ | ✅ | ✅ | ✅ |
|
|
51
51
|
| Bedrock Mantle | ✅ | ❌ | ✅ | ✅ |
|
|
52
52
|
| Bedrock Runtime | ❌ | ❌ | ❌ | ❌ |
|
|
@@ -70,7 +70,7 @@ clients that can't set a model still work.
|
|
|
70
70
|
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
71
71
|
| `/v1/chat/completions` | Chat completions for every provider |
|
|
72
72
|
| `/v1/embeddings` | Embeddings for the providers marked in the [capability matrix](./providers.mdx#supported-providers) |
|
|
73
|
-
| `/v1/responses` | OpenAI Responses API: OpenAI, and the OpenAI-compatible models that serve it on [Azure AI](./azure-ai.mdx), [Bedrock Mantle](./bedrock-mantle.mdx) and [OpenRouter](./openrouter.mdx)
|
|
73
|
+
| `/v1/responses` | OpenAI Responses API: OpenAI, xAI, and the OpenAI-compatible models that serve it on [Azure AI](./azure-ai.mdx), [Bedrock Mantle](./bedrock-mantle.mdx) and [OpenRouter](./openrouter.mdx) |
|
|
74
74
|
| `/v1/messages` | Anthropic Messages API: Anthropic, and the Claude models of [Azure AI](./azure-ai.mdx), [Bedrock Mantle](./bedrock-mantle.mdx), [Vertex AI](./vertex-ai.mdx) and [OpenRouter](./openrouter.mdx) |
|
|
75
75
|
|
|
76
76
|
Jev is the exception: it serves TypeSafe's System One API on `/v1/systemone`,
|
|
@@ -17,11 +17,75 @@ support window applies.
|
|
|
17
17
|
|
|
18
18
|
## At a glance
|
|
19
19
|
|
|
20
|
-
| Surface | What controls compatibility | Support window
|
|
21
|
-
| -------------------------------------------------- | --------------------------------------------- |
|
|
22
|
-
| Gateway runtime | The `compatibilityDate` in your `zuplo.jsonc` |
|
|
23
|
-
| Specification-based features (MCP, OAuth, OpenAPI) | The upstream specification's own lifecycle | Tracks the specification. Old revisions eventually drop.
|
|
24
|
-
| Developer Portal | The `zudoku` package version in your project | Current version only. Fixes aren't backported.
|
|
20
|
+
| Surface | What controls compatibility | Support window |
|
|
21
|
+
| -------------------------------------------------- | --------------------------------------------- | --------------------------------------------------------- |
|
|
22
|
+
| Gateway runtime | The `compatibilityDate` in your `zuplo.jsonc` | At least 12 months per compatibility date, likely longer. |
|
|
23
|
+
| Specification-based features (MCP, OAuth, OpenAPI) | The upstream specification's own lifecycle | Tracks the specification. Old revisions eventually drop. |
|
|
24
|
+
| Developer Portal | The `zudoku` package version in your project | Current version only. Fixes aren't backported. |
|
|
25
|
+
|
|
26
|
+
## What counts as a breaking change
|
|
27
|
+
|
|
28
|
+
The compatibility promise focuses on the parts of Zuplo that a production API
|
|
29
|
+
depends on. Other parts of the platform, such as diagnostics and tooling, keep
|
|
30
|
+
improving between releases.
|
|
31
|
+
|
|
32
|
+
### Breaking changes
|
|
33
|
+
|
|
34
|
+
Zuplo treats these as breaking and gates them behind a compatibility date:
|
|
35
|
+
|
|
36
|
+
- **Request handling.** A change that materially affects how the gateway handles
|
|
37
|
+
a request: which policies and handlers run and in what order, the status code,
|
|
38
|
+
headers, or body a client receives, how a request is routed, validated,
|
|
39
|
+
authenticated, rate limited, or forwarded.
|
|
40
|
+
- **Exported code APIs.** The documented, public exports of `@zuplo/runtime`
|
|
41
|
+
that custom code imports, such as
|
|
42
|
+
[`ZuploRequest`](../programmable-api/zuplo-request.mdx),
|
|
43
|
+
[`ZuploContext`](../programmable-api/zuplo-context.mdx), and
|
|
44
|
+
[`HttpProblems`](../programmable-api/http-problems.mdx). Removing an export,
|
|
45
|
+
changing a signature, or changing what a documented API returns is breaking.
|
|
46
|
+
- **Configuration.** A change that makes a valid `zuplo.jsonc`, route file, or
|
|
47
|
+
policy configuration fail to build, or that changes what an existing option
|
|
48
|
+
does. Stricter validation of invalid configuration is covered separately
|
|
49
|
+
below.
|
|
50
|
+
|
|
51
|
+
### Not breaking changes
|
|
52
|
+
|
|
53
|
+
These can change from release to release without a compatibility date. Tests and
|
|
54
|
+
integrations hold up best when they don't depend on them:
|
|
55
|
+
|
|
56
|
+
- **Error messages.** The human-readable text of errors throughout Zuplo, in
|
|
57
|
+
runtime responses, build and deployment output, and the CLI, can be reworded,
|
|
58
|
+
expanded, or localized. For example, the `title` and `detail` fields of a
|
|
59
|
+
[Problem Details](../programmable-api/http-problems.mdx) response can change.
|
|
60
|
+
Match on stable identifiers such as the status code and the problem `type`
|
|
61
|
+
instead.
|
|
62
|
+
- **Additive changes.** New APIs, new policies, new optional configuration
|
|
63
|
+
properties, new response headers, and new fields in a response body or a
|
|
64
|
+
Problem Details object. Clients and tests should ignore fields and headers
|
|
65
|
+
they don't recognize.
|
|
66
|
+
- **Logs, traces, and metrics.** Log messages and their wording, which
|
|
67
|
+
operations emit logs or spans, span names and attributes, and which metrics
|
|
68
|
+
and analytics Zuplo collects all change over time. New log entries appear and
|
|
69
|
+
existing ones change, so avoid parsing log text to drive automation.
|
|
70
|
+
- **Local development.** The [local development](./local-development.mdx)
|
|
71
|
+
experience, including `zuplo dev`, the local Route Designer, and locally
|
|
72
|
+
mocked or emulated services, changes as the tooling improves. Behavior in
|
|
73
|
+
local development can differ from a deployed gateway. Treat a
|
|
74
|
+
[preview environment](./branch-based-deployments.mdx) as the source of truth
|
|
75
|
+
for production behavior.
|
|
76
|
+
- **Undocumented behavior and internal APIs.** Anything not described in the
|
|
77
|
+
documentation: exports not documented as public, imports from internal paths
|
|
78
|
+
of a Zuplo package, internal headers, and incidental behavior such as the
|
|
79
|
+
order of headers or the exact timing of a request.
|
|
80
|
+
- **Performance.** Latency, memory use, and throughput improve or shift between
|
|
81
|
+
releases. [Platform limits](./limits.mdx) are documented separately.
|
|
82
|
+
- **Stricter build validation.** Zuplo continues to add checks that catch
|
|
83
|
+
invalid configuration and code at build time instead of at request time. For
|
|
84
|
+
example, a policy that's misconfigured so that it fails on every request might
|
|
85
|
+
build and deploy today, then fail the build after a new check ships. Catching
|
|
86
|
+
these problems before deployment keeps them out of production, though it means
|
|
87
|
+
a project that built before can occasionally need a fix to build again. The
|
|
88
|
+
build error explains what's wrong and how to fix it.
|
|
25
89
|
|
|
26
90
|
## Gateway runtime
|
|
27
91
|
|
|
@@ -51,8 +115,9 @@ raising the date in `zuplo.jsonc`:
|
|
|
51
115
|
```
|
|
52
116
|
|
|
53
117
|
New projects default to a recent compatibility date. Existing projects stay on
|
|
54
|
-
the date they set until someone changes it
|
|
55
|
-
|
|
118
|
+
the date they set until someone changes it. Zuplo supports each compatibility
|
|
119
|
+
date for at least 12 months after it's released, and likely much longer. To
|
|
120
|
+
date, Zuplo hasn't retired any compatibility date.
|
|
56
121
|
|
|
57
122
|
Every compatibility date and the behavior it changes is documented in
|
|
58
123
|
[Compatibility Dates](../programmable-api/compatibility-dates.mdx). Each entry
|
|
@@ -66,18 +131,98 @@ that deployment, and merge once it passes.
|
|
|
66
131
|
|
|
67
132
|
:::
|
|
68
133
|
|
|
69
|
-
###
|
|
134
|
+
### Compatibility flags
|
|
70
135
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
136
|
+
Each behavior change in the runtime is implemented as a compatibility flag. A
|
|
137
|
+
compatibility date is a named set of flags: setting a date turns on every flag
|
|
138
|
+
that shipped with that date or an earlier one. Flags only ever turn new behavior
|
|
139
|
+
on. A project can't use a flag to turn off a behavior that its compatibility
|
|
140
|
+
date includes.
|
|
141
|
+
|
|
142
|
+
Zuplo occasionally ships a flag before it belongs to any compatibility date, to
|
|
143
|
+
resolve a specific customer issue or bug ahead of the next date. A project can
|
|
144
|
+
turn on such a flag with `compatibilityFlags` in `zuplo.jsonc`.
|
|
145
|
+
|
|
146
|
+
:::warning
|
|
147
|
+
|
|
148
|
+
Compatibility flags aren't documented or supported, and because they're
|
|
149
|
+
unstable, they aren't recommended for production projects.
|
|
150
|
+
|
|
151
|
+
:::
|
|
152
|
+
|
|
153
|
+
Until a flag becomes part of a compatibility date, it's considered unstable:
|
|
154
|
+
|
|
155
|
+
- **Its behavior can change** between releases while the change is refined.
|
|
156
|
+
- **It can be removed.** When a flag's behavior becomes standard or moves to
|
|
157
|
+
another flag, listing the old flag fails the build with a message explaining
|
|
158
|
+
what to use instead.
|
|
159
|
+
- **Stability guarantees apply to compatibility dates only.** While a project
|
|
160
|
+
sets any `compatibilityFlags`, the guarantees on this page don't apply to it,
|
|
161
|
+
even when the flags it sets are already part of its compatibility date.
|
|
162
|
+
|
|
163
|
+
When a flag becomes part of a compatibility date, raise the date and remove the
|
|
164
|
+
flag from `zuplo.jsonc`. Setting the date is the supported way to get the
|
|
165
|
+
behavior.
|
|
166
|
+
|
|
167
|
+
## Exceptions to the compatibility promise
|
|
168
|
+
|
|
169
|
+
Zuplo works to avoid breaking changes, and gates every change it can behind a
|
|
170
|
+
compatibility date. In three situations, a change may need to ship outside a
|
|
171
|
+
compatibility date. Even then, Zuplo keeps the change as small as possible and
|
|
172
|
+
lets affected customers know.
|
|
173
|
+
|
|
174
|
+
### Security
|
|
175
|
+
|
|
176
|
+
Protecting customer traffic comes first. To close a vulnerability, Zuplo may
|
|
177
|
+
change runtime behavior without a compatibility date, even when the fix affects
|
|
178
|
+
how some existing projects behave.
|
|
75
179
|
|
|
76
180
|
Zuplo scopes these changes as narrowly as the vulnerability allows and
|
|
77
181
|
communicates them as directly as responsible disclosure permits. For Zuplo's
|
|
78
182
|
security practices and how to report a vulnerability, see
|
|
79
183
|
[Security](./security.mdx).
|
|
80
184
|
|
|
185
|
+
### Protocol correctness
|
|
186
|
+
|
|
187
|
+
Some parts of the gateway implement specifications Zuplo doesn't control — most
|
|
188
|
+
visibly the Model Context Protocol (MCP), along with OAuth, OpenID Connect,
|
|
189
|
+
OpenAPI, JSON Schema, and HTTP itself. Zuplo aims to implement these
|
|
190
|
+
specifications correctly, because correct behavior is what keeps the gateway
|
|
191
|
+
working with the clients and servers customers depend on.
|
|
192
|
+
|
|
193
|
+
These features follow the lifecycle of the specification:
|
|
194
|
+
|
|
195
|
+
- **Specification revisions.** When a specification deprecates or removes a
|
|
196
|
+
revision, Zuplo eventually ends support for that revision in line with the
|
|
197
|
+
specification's deprecation policy.
|
|
198
|
+
- **Specification changes.** When a specification changes in a way that can't be
|
|
199
|
+
both backward compatible and compliant, Zuplo follows the specification.
|
|
200
|
+
- **Conformance fixes.** From time to time, Zuplo finds a bug or behavior that's
|
|
201
|
+
inconsistent with a specification. Zuplo fixes these so the gateway behaves as
|
|
202
|
+
the specification describes.
|
|
203
|
+
|
|
204
|
+
Where a protocol change can be gated, Zuplo gates it. The MCP Gateway, for
|
|
205
|
+
example, requires a compatibility date of `2026-03-01` or later — see
|
|
206
|
+
[MCP Gateway compatibility dates](../mcp-gateway/code-config/compatibility-dates.mdx).
|
|
207
|
+
|
|
208
|
+
### Official SDK dependencies
|
|
209
|
+
|
|
210
|
+
Some features are built on an official SDK for a protocol rather than a Zuplo
|
|
211
|
+
implementation. For example, the [MCP Server](../mcp-server/overview.mdx) and
|
|
212
|
+
[MCP Gateway](../mcp-gateway/overview.mdx) use the official MCP SDK for parts of
|
|
213
|
+
their protocol handling.
|
|
214
|
+
|
|
215
|
+
When Zuplo updates an SDK to pick up a new protocol revision, a security fix, or
|
|
216
|
+
a bug fix, behavior that comes from the SDK can change with it. SDK-driven
|
|
217
|
+
changes follow the SDK's own release cycle rather than Zuplo's compatibility
|
|
218
|
+
dates, and Zuplo calls out notable ones in the
|
|
219
|
+
[changelog](https://zuplo.com/changelog).
|
|
220
|
+
|
|
221
|
+
MCP is evolving quickly, so the MCP Gateway and MCP Server track new protocol
|
|
222
|
+
revisions and SDK releases as they publish. Testing MCP integrations against a
|
|
223
|
+
preview environment before each production deployment is the best way to catch a
|
|
224
|
+
change early.
|
|
225
|
+
|
|
81
226
|
## Changes take effect when you deploy
|
|
82
227
|
|
|
83
228
|
Zuplo doesn't rebuild or redeploy a running gateway on your behalf. A new
|
|
@@ -99,31 +244,6 @@ recommends a comprehensive test suite that gates every deployment. The
|
|
|
99
244
|
preview environments, and in CI/CD. See [Testing Your API](./testing.mdx) for
|
|
100
245
|
the recommended setup.
|
|
101
246
|
|
|
102
|
-
## Specification-based features
|
|
103
|
-
|
|
104
|
-
Some parts of the gateway implement specifications Zuplo doesn't control — most
|
|
105
|
-
visibly the Model Context Protocol (MCP), along with OAuth, OpenAPI, and related
|
|
106
|
-
standards. These features follow the lifecycle of the specification rather than
|
|
107
|
-
Zuplo's own compatibility policy.
|
|
108
|
-
|
|
109
|
-
Two consequences:
|
|
110
|
-
|
|
111
|
-
- When a specification deprecates or removes a revision, Zuplo eventually ends
|
|
112
|
-
support for that revision in line with the specification's deprecation policy.
|
|
113
|
-
- When a specification changes such that remaining backward compatible would
|
|
114
|
-
mean falling out of compliance, Zuplo implements the breaking change. A
|
|
115
|
-
non-compliant implementation breaks interoperability with the clients and
|
|
116
|
-
servers customers need to work with, which is the larger break.
|
|
117
|
-
|
|
118
|
-
MCP moves fastest here. The [MCP Gateway](../mcp-gateway/overview.mdx) and
|
|
119
|
-
[MCP Server](../mcp-server/overview.mdx) track protocol revisions as the
|
|
120
|
-
specification publishes them, and support for older revisions ends on the
|
|
121
|
-
specification's schedule.
|
|
122
|
-
|
|
123
|
-
Where a spec-driven change can be gated, Zuplo gates it. The MCP Gateway, for
|
|
124
|
-
example, requires a compatibility date of `2026-03-01` or later — see
|
|
125
|
-
[MCP Gateway compatibility dates](../mcp-gateway/code-config/compatibility-dates.mdx).
|
|
126
|
-
|
|
127
247
|
## Developer Portal
|
|
128
248
|
|
|
129
249
|
The Developer Portal is a web application built on [Zudoku](https://zudoku.dev),
|
|
@@ -158,8 +278,8 @@ that need more than a dependency bump.
|
|
|
158
278
|
|
|
159
279
|
When Zuplo introduces a breaking change:
|
|
160
280
|
|
|
161
|
-
- **
|
|
162
|
-
|
|
281
|
+
- **Changes are communicated.** Zuplo announces breaking changes through the
|
|
282
|
+
[changelog](https://zuplo.com/changelog) and documentation, and contacts
|
|
163
283
|
affected customers directly where the impact is identifiable.
|
|
164
284
|
- **Compatibility dates carry the change where possible.** Gating a runtime
|
|
165
285
|
change behind a compatibility date leaves the timing of adoption to you.
|
|
@@ -169,8 +289,9 @@ When Zuplo introduces a breaking change:
|
|
|
169
289
|
plan work directly with the Zuplo team on upgrade planning, testing, and
|
|
170
290
|
rollout. See [Support](./support.mdx).
|
|
171
291
|
|
|
172
|
-
|
|
173
|
-
|
|
292
|
+
For security, protocol, and SDK changes, the timing of notice depends on the
|
|
293
|
+
situation, such as responsible disclosure for a vulnerability. See
|
|
294
|
+
[Exceptions to the compatibility promise](#exceptions-to-the-compatibility-promise).
|
|
174
295
|
|
|
175
296
|
## Related resources
|
|
176
297
|
|
|
@@ -179,6 +300,8 @@ advance disclosure would put customers at risk.
|
|
|
179
300
|
- [Project Configuration (`zuplo.jsonc`)](../programmable-api/zuplo-json.mdx) —
|
|
180
301
|
where the compatibility date is set
|
|
181
302
|
- [Testing Your API](./testing.mdx) — local, preview, and CI/CD testing
|
|
303
|
+
- [Problem Details Helper](../programmable-api/http-problems.mdx) — the error
|
|
304
|
+
format to assert on in tests
|
|
182
305
|
- [Branch-Based Deployments](./branch-based-deployments.mdx) — how branches map
|
|
183
306
|
to environments
|
|
184
307
|
- [Updating Versions](../dev-portal/updating.mdx) — upgrading the Developer
|
|
@@ -1,10 +1,47 @@
|
|
|
1
1
|
## Overview
|
|
2
2
|
|
|
3
3
|
The `mcp-capability-filter-inbound` policy curates the MCP capabilities exposed
|
|
4
|
-
by a proxied upstream server. It filters successful JSON-RPC list responses
|
|
5
|
-
|
|
4
|
+
by a proxied upstream server. It filters successful JSON-RPC list responses,
|
|
5
|
+
whether the upstream answers with JSON or with a `text/event-stream`, and blocks
|
|
6
|
+
direct JSON-RPC access to hidden tools, prompts, and resources before the
|
|
6
7
|
request is forwarded upstream.
|
|
7
8
|
|
|
9
|
+
Direct access is checked on `tools/call` and `prompts/get` (by `params.name`),
|
|
10
|
+
on `resources/read`, `resources/subscribe` and `resources/unsubscribe` (by
|
|
11
|
+
`params.uri`), and on `completion/complete`: a `ref/prompt` reference's `name`
|
|
12
|
+
against `prompts`, a `ref/resource` reference's `uri` against `resources` and
|
|
13
|
+
`resourceTemplates` (either configured list may allow it, since the URI can be a
|
|
14
|
+
concrete resource or a template). A URI with a template expression (`{name}`) is
|
|
15
|
+
never a concrete resource, so it passes while `resourceTemplates` is omitted,
|
|
16
|
+
whatever `resources` lists. A completion whose `ref` has any other shape (an
|
|
17
|
+
unknown or missing `type`, no `ref`) is refused while `prompts`, `resources` or
|
|
18
|
+
`resourceTemplates` is configured.
|
|
19
|
+
|
|
20
|
+
A call to a hidden capability is not forwarded. It is answered exactly as MCP
|
|
21
|
+
answers a capability that does not exist, so a client cannot tell the two apart:
|
|
22
|
+
|
|
23
|
+
| Request | Answer |
|
|
24
|
+
| --------------------------------------------- | ----------------------------------------------------------------------------------- |
|
|
25
|
+
| `tools/call` of a hidden tool | `-32602`, `Tool "<name>" not found` |
|
|
26
|
+
| `prompts/get` of a hidden prompt | `-32602`, `Prompt "<name>" not found` |
|
|
27
|
+
| `resources/read`, `subscribe`, `unsubscribe` | `-32002`, `Resource not found`, with `data.uri` |
|
|
28
|
+
| `completion/complete` of a hidden reference | `-32602`, `Prompt "<name>" not found` or `Resource template "<uri>" not found` |
|
|
29
|
+
| Any of these without a string `name` or `uri` | `-32602`, for example `Invalid params for tools/call: params.name must be a string` |
|
|
30
|
+
|
|
31
|
+
A method the policy checks or filters spelled another way, in another case or
|
|
32
|
+
with surrounding spaces (`Tools/Call`, ` tools/list`), is answered `-32601`
|
|
33
|
+
(`Method not found`): it is no MCP method, and an upstream that matched it
|
|
34
|
+
leniently would run it unchecked.
|
|
35
|
+
|
|
36
|
+
A resource URI is allowed when `resources` lists it, or when it matches a
|
|
37
|
+
template that `resourceTemplates` allows, as the MCP SDK matches templates. With
|
|
38
|
+
the configuration below, `stripe://customers/cus_1` is readable from the
|
|
39
|
+
`stripe://customers/{id}` template, and a URI from any other template, such as
|
|
40
|
+
`stripe://invoices/in_1`, is refused. When you omit `resources`, a URI passes
|
|
41
|
+
unless it matches only templates hidden from the caller: the policy cannot tell
|
|
42
|
+
a concrete resource from a URI of a template it does not know, so configure
|
|
43
|
+
`resources` too when reads of unlisted templates must be refused.
|
|
44
|
+
|
|
8
45
|
Omit a capability option to pass that capability type through unchanged. Set an
|
|
9
46
|
option to an empty array to expose none of that capability type. Matching is
|
|
10
47
|
case-sensitive and exact.
|
|
@@ -184,9 +221,143 @@ export default async function resolveCapabilities(
|
|
|
184
221
|
}
|
|
185
222
|
```
|
|
186
223
|
|
|
224
|
+
## Requests and responses the filter cannot read
|
|
225
|
+
|
|
226
|
+
The policy fails closed. It never forwards something it could not check:
|
|
227
|
+
|
|
228
|
+
| Case | Result |
|
|
229
|
+
| ------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
|
|
230
|
+
| Body is not JSON (`Content-Type` other than JSON), on any HTTP method | `415` problem+json, not forwarded |
|
|
231
|
+
| Body over 10 MiB | `413` problem+json, not forwarded |
|
|
232
|
+
| Body is JSON-typed but does not parse, or cannot be read (the upload failed) | `400`, `-32700`, not forwarded |
|
|
233
|
+
| Body is JSON but not a JSON-RPC message | `400`, `-32600` (its `id` when valid) |
|
|
234
|
+
| Batch with a member that is not a JSON-RPC 2.0 object (no or another `jsonrpc`, a nested array, a scalar) | `400`, one `-32600`, not forwarded |
|
|
235
|
+
| A `method` that is not a string (an array, number, `null` or object), alone or in a batch | `400`, one `-32600`, not forwarded |
|
|
236
|
+
| A non-ASCII key in a message, its `params` or `params.ref` (Go folds `paramſ` into `params`) | `400`, `-32600`, not forwarded |
|
|
237
|
+
| `jsonrpc`/`method`/`id`/`params`, `params.name`/`uri`/`ref` or `ref.type`/`name`/`uri` twice, in any case | `400`, `-32600`, not forwarded |
|
|
238
|
+
| A message key in another case (`Method`), or `params.Name`/`URI`/`Ref` or `ref.Name`/… on a direct call | `400`, `-32600`, not forwarded |
|
|
239
|
+
| A direct call whose `params` is not an object, or has no string name or URI | `-32602`, not forwarded |
|
|
240
|
+
| A method the policy checks or filters, in another case or with spaces (`Tools/Call`, ` tools/list`) | `-32601`, not forwarded |
|
|
241
|
+
| A refused call sent as a notification (no `id`) | `400`, `-32600`, no `id`, not forwarded |
|
|
242
|
+
| A refused call whose `id` is not a string or an integer (`1.5`, `null`) | `400`, `-32600`, no `id`, not forwarded |
|
|
243
|
+
| Access-control resolver throws or returns no value | `-32603` for each request that needed it |
|
|
244
|
+
| List reply the policy cannot parse, or with no or unknown media type | `-32603` instead of the reply |
|
|
245
|
+
| List reply in a content-coding the runtime does not decode (`deflate`, `zstd`) | `-32603` instead of the reply |
|
|
246
|
+
| List reply that is not a JSON-RPC result, error, notification or request | `-32603` instead of the reply |
|
|
247
|
+
| List result whose `result` is not an object, or whose list (`tools`, `prompts`, …) is missing or not an array | `-32603` instead of the reply |
|
|
248
|
+
| List result whose `nextCursor` is not a string (or `null`) | `-32603` instead of the reply |
|
|
249
|
+
| A JSON-RPC array answering a single (non-batch) request | `-32603` instead of the reply |
|
|
250
|
+
| Result holding a filtered list (`tools`, `prompts`, …) that its request did not list | `-32603` instead of the reply |
|
|
251
|
+
| Batch reply array: a member that is any of the `-32603` cases above, or whose `id` matches no request | Member dropped (see below) |
|
|
252
|
+
| SSE list reply: an event whose `data` is not JSON, or whose message is any of the `-32603` cases above | Event dropped |
|
|
253
|
+
| SSE list reply: an event with an unknown field, an `event` other than `message`, or a malformed `id`/`retry` | Event dropped |
|
|
254
|
+
| SSE list reply: an event over 10 MiB, or a final event without a closing blank line | Event dropped |
|
|
255
|
+
| SSE list reply: a comment with text | Text removed (`:` kept) |
|
|
256
|
+
| A list item without a string name or URI | Item dropped |
|
|
257
|
+
|
|
258
|
+
The key checks guard against an upstream that reads JSON keys case-insensitively
|
|
259
|
+
(as Go's `encoding/json` does) or keeps the first of two duplicates. Non-ASCII
|
|
260
|
+
keys and the repeated keys above are refused on every message, notifications
|
|
261
|
+
included, whatever its method: `{"params":{"name":1,"Name":2}}` is a `400` even
|
|
262
|
+
on `notifications/initialized`. A single case variant in `params` or
|
|
263
|
+
`params.ref` (`Name`, `URI`, `Ref`) is refused only on a direct call; keys
|
|
264
|
+
nested deeper, such as `params.arguments.Name`, are not checked.
|
|
265
|
+
|
|
266
|
+
A request without a body, or with an empty one, passes through whatever its HTTP
|
|
267
|
+
method (`GET`, a `DELETE` that ends a session, an empty `POST`). The policy
|
|
268
|
+
reads a body it cannot parse only as far as its answer needs: one that is not
|
|
269
|
+
JSON up to its first non-blank character, a JSON one up to the 10 MiB limit. A
|
|
270
|
+
non-2xx reply passes through with its status and headers (a problem details or
|
|
271
|
+
OAuth error body, an empty body, one that is not JSON or is still compressed),
|
|
272
|
+
unless it is a JSON reply with a `result` or a batch, which is filtered.
|
|
273
|
+
|
|
274
|
+
A list reply the policy cannot read is replaced by a JSON-RPC `-32603` error for
|
|
275
|
+
the list request, with its `id` (an array of errors for a batch), sent as
|
|
276
|
+
`application/json` with HTTP `200`; the error carries none of the reply. Every
|
|
277
|
+
reply the policy does not forward (a replaced reply, a dropped event or batch
|
|
278
|
+
member) is logged as a warning, without any of its content.
|
|
279
|
+
|
|
280
|
+
A request with a list method the policy filters is sent upstream with
|
|
281
|
+
`Accept-Encoding: gzip, br`, replacing the client's value, so the upstream
|
|
282
|
+
answers in a content-coding the runtime decodes. Other requests keep the
|
|
283
|
+
client's `Accept-Encoding`.
|
|
284
|
+
|
|
285
|
+
When the policy drops an SSE list reply, the client is not left waiting. Each
|
|
286
|
+
list request whose reply was dropped gets a JSON-RPC error event instead, with
|
|
287
|
+
none of the dropped content:
|
|
288
|
+
|
|
289
|
+
```json
|
|
290
|
+
{
|
|
291
|
+
"jsonrpc": "2.0",
|
|
292
|
+
"id": 1,
|
|
293
|
+
"error": {
|
|
294
|
+
"code": -32603,
|
|
295
|
+
"message": "The MCP capability filter could not read the upstream's list response, so it was not forwarded."
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
The error is sent at once when the dropped reply's `id` shows which request it
|
|
301
|
+
answered, and otherwise when the upstream stream ends, for each request still
|
|
302
|
+
unanswered. A request that is not a list request gets a different message (see
|
|
303
|
+
[Batch Requests](#batch-requests)). Once no request is waiting, the stream ends.
|
|
304
|
+
Other events in the stream (progress notifications, server requests, error
|
|
305
|
+
replies, events with only an `id` of letters, digits and `._~:/+=-`, `retry` or
|
|
306
|
+
`event: message`) pass through unchanged. So does an event without a payload (no
|
|
307
|
+
`data`, or an empty one), such as the priming event a 2025-11-25 server sends
|
|
308
|
+
first to make its stream resumable: its `id` is kept, and nothing is logged.
|
|
309
|
+
|
|
310
|
+
The policy adds an error only after it dropped something. When the upstream
|
|
311
|
+
stream ends cleanly without answering a request, and nothing was dropped, the
|
|
312
|
+
policy adds nothing: answering that request is the handler's job.
|
|
313
|
+
`McpProxyHandler` sends a `-32603` error for each unanswered request itself
|
|
314
|
+
(before this policy sees the stream), so behind it the client still gets an
|
|
315
|
+
answer.
|
|
316
|
+
|
|
187
317
|
## Batch Requests
|
|
188
318
|
|
|
189
|
-
For JSON-RPC batch requests, list responses are filtered per response item
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
batch
|
|
319
|
+
For JSON-RPC batch requests, list responses are filtered per response item,
|
|
320
|
+
matched to the original list request by id. A reply id matches a request of the
|
|
321
|
+
same type first (`1` to `1`), and otherwise a request with the same text (`"1"`
|
|
322
|
+
for `1`). A batch may hold ids that differ only in type (`1` and `"1"`): a reply
|
|
323
|
+
goes to the request whose id it matches exactly. A result whose id matches no
|
|
324
|
+
request in the batch fails closed, including one whose id is that of a client
|
|
325
|
+
result or error in the batch: that reply is dropped. Ids can be re-typed in more
|
|
326
|
+
ways than the policy can tell apart (`1.0`, `"01"`, `-0`, numbers past 2^53), so
|
|
327
|
+
a result that holds a list the policy filters (`tools`, `prompts`, `resources`,
|
|
328
|
+
`resourceTemplates`) fails closed the same way unless it answers the request
|
|
329
|
+
that listed it.
|
|
330
|
+
|
|
331
|
+
A batch reply array, as a JSON body or in an SSE event, is checked member by
|
|
332
|
+
member, so one bad member does not lose its siblings' replies (a tool call in
|
|
333
|
+
the batch may already have run). Each member that is safe is sent. A member the
|
|
334
|
+
policy cannot read is replaced by a JSON-RPC `-32603` error for the request it
|
|
335
|
+
answers, or dropped when its `id` matches no request still waiting. When a
|
|
336
|
+
member was dropped, every request left without a reply gets the error too: in a
|
|
337
|
+
JSON reply at once, appended to the array; on a stream when it ends. A JSON
|
|
338
|
+
batch reply that is not an array gets a `-32603` for every request.
|
|
339
|
+
|
|
340
|
+
When the stream drops an event that might have held a reply, every request still
|
|
341
|
+
waiting at the end of the stream gets a JSON-RPC `-32603` error. For a request
|
|
342
|
+
that is not a list request, the message says the filter did not forward the
|
|
343
|
+
reply and the request may already have run, so the client does not assume a
|
|
344
|
+
retry is safe:
|
|
345
|
+
|
|
346
|
+
```json
|
|
347
|
+
{
|
|
348
|
+
"jsonrpc": "2.0",
|
|
349
|
+
"id": 2,
|
|
350
|
+
"error": {
|
|
351
|
+
"code": -32603,
|
|
352
|
+
"message": "The MCP capability filter did not forward the upstream's reply to this request. The request may already have run."
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
If any batch item directly calls a hidden tool, prompt, or resource, the policy
|
|
358
|
+
refuses the whole batch and forwards none of it. It answers with an array that
|
|
359
|
+
has one error per request in the batch: the not-found error for the hidden call,
|
|
360
|
+
and for each other request a `-32603` that says it was not forwarded because
|
|
361
|
+
another request in its batch was refused. Notifications in the batch get
|
|
362
|
+
nothing. On protocol version 2025-06-18 and later, which have no batches, the
|
|
363
|
+
answer is one `400` with `-32600` and no `id`.
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"description": "The name of the exported type"
|
|
27
27
|
},
|
|
28
28
|
"module": {
|
|
29
|
-
"const": "$import(@zuplo/runtime)",
|
|
29
|
+
"const": "$import(@zuplo/runtime/mcp-gateway)",
|
|
30
30
|
"description": "The module containing the policy"
|
|
31
31
|
},
|
|
32
32
|
"options": {
|
|
@@ -330,7 +330,7 @@
|
|
|
330
330
|
"examples": [
|
|
331
331
|
{
|
|
332
332
|
"export": "McpCapabilityFilterInboundPolicy",
|
|
333
|
-
"module": "$import(@zuplo/runtime)",
|
|
333
|
+
"module": "$import(@zuplo/runtime/mcp-gateway)",
|
|
334
334
|
"options": {
|
|
335
335
|
"accessControl": {
|
|
336
336
|
"groupClaim": "groups",
|
|
@@ -20,13 +20,14 @@ The policy does not perform the normal upstream fetch and does not pass hidden
|
|
|
20
20
|
context to the handler. The route should use `McpProxyHandler` with a
|
|
21
21
|
deterministic upstream MCP URL configured on the handler. `McpProxyHandler`
|
|
22
22
|
handles GET stream probes and delegates POST forwarding to Zuplo's
|
|
23
|
-
`urlRewriteHandler`.
|
|
24
|
-
|
|
23
|
+
`urlRewriteHandler`. When the upstream answers `401`, `McpProxyHandler`
|
|
24
|
+
refreshes the upstream credential and retries the request once, before any
|
|
25
|
+
outbound policy or response hook sees the response. If the refresh needs the
|
|
26
|
+
user to reconnect, the handler returns the connect-required JSON-RPC error
|
|
27
|
+
instead.
|
|
25
28
|
|
|
26
29
|
Projects using this policy must run with a compatibility date that enables
|
|
27
|
-
chained response hooks, currently `2026-03-01` or later.
|
|
28
|
-
receive the latest response in the policy chain so later response hooks cannot
|
|
29
|
-
accidentally replace an upstream OAuth retry or connect-required response.
|
|
30
|
+
chained response hooks, currently `2026-03-01` or later.
|
|
30
31
|
|
|
31
32
|
## Configuration
|
|
32
33
|
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"description": "The name of the exported type"
|
|
27
27
|
},
|
|
28
28
|
"module": {
|
|
29
|
-
"const": "$import(@zuplo/runtime)",
|
|
29
|
+
"const": "$import(@zuplo/runtime/mcp-gateway)",
|
|
30
30
|
"description": "The module containing the policy"
|
|
31
31
|
},
|
|
32
32
|
"options": {
|
|
@@ -424,7 +424,7 @@
|
|
|
424
424
|
"examples": [
|
|
425
425
|
{
|
|
426
426
|
"export": "McpTokenExchangeInboundPolicy",
|
|
427
|
-
"module": "$import(@zuplo/runtime)",
|
|
427
|
+
"module": "$import(@zuplo/runtime/mcp-gateway)",
|
|
428
428
|
"options": {
|
|
429
429
|
"displayName": "Linear",
|
|
430
430
|
"id": "linear",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zuplo",
|
|
3
|
-
"version": "7.13.
|
|
3
|
+
"version": "7.13.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The official Zuplo CLI for local development and platform management",
|
|
6
6
|
"homepage": "https://zuplo.com/docs/cli/overview",
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
"zuplo": "zuplo.js"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@zuplo/cli": "7.13.
|
|
36
|
-
"@zuplo/core": "7.13.
|
|
37
|
-
"@zuplo/runtime": "7.13.
|
|
38
|
-
"@zuplo/test": "7.13.
|
|
35
|
+
"@zuplo/cli": "7.13.3",
|
|
36
|
+
"@zuplo/core": "7.13.3",
|
|
37
|
+
"@zuplo/runtime": "7.13.3",
|
|
38
|
+
"@zuplo/test": "7.13.3"
|
|
39
39
|
}
|
|
40
40
|
}
|