zuplo 7.9.13 → 7.9.15
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/bedrock-runtime.mdx +1 -1
- package/docs/ai-gateway/custom-policies.mdx +1 -1
- package/docs/ai-gateway/fallback.mdx +1 -1
- package/docs/ai-gateway/integrations/ai-sdk.mdx +5 -5
- package/docs/ai-gateway/integrations/claude-code.mdx +6 -8
- package/docs/ai-gateway/integrations/codex.mdx +6 -5
- package/docs/ai-gateway/integrations/goose.mdx +3 -3
- package/docs/ai-gateway/integrations/langchain.mdx +1 -1
- package/docs/ai-gateway/integrations/openai.mdx +3 -3
- package/docs/ai-gateway/managing-providers.mdx +1 -1
- package/docs/ai-gateway/openrouter.mdx +14 -13
- package/docs/ai-gateway/overview.mdx +4 -2
- package/docs/ai-gateway/providers.mdx +2 -2
- package/docs/ai-gateway/source-control.mdx +61 -2
- package/docs/ai-gateway/universal-api.mdx +1 -1
- package/docs/ai-gateway/user-apps.mdx +5 -2
- package/docs/articles/accounts/audit-logs.mdx +3 -9
- package/docs/articles/environment-variables.mdx +56 -231
- package/docs/articles/graphql-support.mdx +59 -18
- package/docs/articles/local-development-env-variables.mdx +37 -8
- package/docs/articles/logging.mdx +21 -0
- package/docs/articles/opentelemetry.mdx +1 -1
- package/docs/cli/source-upgrade.mdx +39 -0
- package/docs/cli/source-upgrade.partial.mdx +11 -0
- package/docs/concepts/ai-gateway.mdx +1 -1
- package/docs/dev-portal/zudoku/guides/environment-variables.md +75 -0
- package/docs/dev-portal/zudoku-partials/guides/environment-variables.partial.md +74 -0
- package/docs/handlers/mcp-server.mdx +72 -0
- package/docs/mcp-server/overview.mdx +7 -0
- package/docs/mcp-server/tools.mdx +72 -0
- package/docs/policies/_index.md +1 -1
- package/docs/policies/ai-gateway-auth-inbound/doc.md +2 -2
- package/docs/policies/ai-gateway-configuration-executor-inbound/doc.md +3 -3
- package/docs/policies/ai-gateway-prompt-injection/doc.md +1 -2
- package/docs/policies/ai-gateway-prompt-injection/schema.json +2 -2
- package/docs/policies/ai-gateway-smart-router-inbound/doc.md +112 -29
- package/docs/policies/ai-gateway-smart-router-inbound/schema.json +109 -12
- package/docs/policies/internal-only-inbound/doc.md +206 -0
- package/docs/policies/internal-only-inbound/intro.md +10 -0
- package/docs/policies/internal-only-inbound/schema.json +75 -0
- package/docs/programmable-api/environment.mdx +220 -48
- package/docs/programmable-api/hooks.mdx +24 -0
- package/docs/programmable-api/mcp-sdk.mdx +22 -0
- package/package.json +5 -5
- package/docs/policies/ai-gateway-internal-only-inbound/doc.md +0 -102
- package/docs/policies/ai-gateway-internal-only-inbound/intro.md +0 -11
- package/docs/policies/ai-gateway-internal-only-inbound/schema.json +0 -50
|
@@ -1,26 +1,42 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: Environment Variables
|
|
2
|
+
title: Gateway Environment Variables
|
|
3
3
|
sidebar_label: Environment Variables
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
Your API gateway can read every environment variable set for its environment,
|
|
7
|
+
including secrets. This page covers how to read variables in code, configuration
|
|
8
|
+
files, and URL handlers, and which variables Zuplo sets for you.
|
|
8
9
|
|
|
9
10
|
:::info{title="Configuration Guide"}
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
Portal, see
|
|
12
|
+
To set and manage environment variables, and to learn how gateway variables
|
|
13
|
+
differ from Developer Portal variables, see
|
|
13
14
|
[Configuring Environment Variables](../articles/environment-variables.mdx).
|
|
14
15
|
|
|
15
16
|
:::
|
|
16
17
|
|
|
17
|
-
##
|
|
18
|
+
## Where to Use Variables
|
|
19
|
+
|
|
20
|
+
Each location in your project has its own syntax:
|
|
21
|
+
|
|
22
|
+
| Location | Syntax | Resolved |
|
|
23
|
+
| ----------------------------------------------------- | ---------------------- | --------------------- |
|
|
24
|
+
| Custom code (handlers, policies, hooks) | `environment.VAR_NAME` | Runtime |
|
|
25
|
+
| Configuration files (`policies.json`, OpenAPI routes) | `$env(VAR_NAME)` | Build time |
|
|
26
|
+
| URL Rewrite, URL Forward, and WebSocket handlers | `${env.VAR_NAME}` | Runtime (per request) |
|
|
27
|
+
|
|
28
|
+
## In Code
|
|
29
|
+
|
|
30
|
+
The `environment` object from `@zuplo/runtime` returns a record of environment
|
|
31
|
+
variable names to their values.
|
|
32
|
+
|
|
33
|
+
### Interface
|
|
18
34
|
|
|
19
35
|
```ts
|
|
20
36
|
const environment: Record<string, string | undefined>;
|
|
21
37
|
```
|
|
22
38
|
|
|
23
|
-
|
|
39
|
+
### Usage
|
|
24
40
|
|
|
25
41
|
Access environment variables using the `environment` object:
|
|
26
42
|
|
|
@@ -49,7 +65,7 @@ export default async function handler(
|
|
|
49
65
|
}
|
|
50
66
|
```
|
|
51
67
|
|
|
52
|
-
|
|
68
|
+
### Type Safety
|
|
53
69
|
|
|
54
70
|
Since environment variables might not be defined, they return
|
|
55
71
|
`string | undefined`:
|
|
@@ -70,6 +86,185 @@ if (value) {
|
|
|
70
86
|
const port = environment.PORT || "3000";
|
|
71
87
|
```
|
|
72
88
|
|
|
89
|
+
## In Configuration Files
|
|
90
|
+
|
|
91
|
+
Inside policy options, route handler options, and CORS policy options,
|
|
92
|
+
environment variables can be referenced with the `$env(VAR_NAME)` pattern.
|
|
93
|
+
Substitutions happen at build time — the build replaces each `$env()` expression
|
|
94
|
+
with a reference to the runtime `environment` object before the project is
|
|
95
|
+
deployed.
|
|
96
|
+
|
|
97
|
+
### Where `$env()` is allowed
|
|
98
|
+
|
|
99
|
+
- `config/policies.json` — any property under `policies[].handler.options`,
|
|
100
|
+
including nested objects and array elements
|
|
101
|
+
- `config/policies.json` — any direct property of a `corsPolicies[]` entry, such
|
|
102
|
+
as `allowedOrigins`, `allowedHeaders`, `allowedMethods`, `exposeHeaders`, and
|
|
103
|
+
`maxAge`
|
|
104
|
+
- `config/routes.oas.json` (and other OpenAPI route files) — any property under
|
|
105
|
+
a route's `x-zuplo-route.handler.options`, including nested objects and array
|
|
106
|
+
elements
|
|
107
|
+
|
|
108
|
+
`$env()` is **not** allowed in other locations such as policy `name`,
|
|
109
|
+
`policyType`, `module`, route paths, or top-level OpenAPI fields. Using it
|
|
110
|
+
elsewhere produces a build error:
|
|
111
|
+
|
|
112
|
+
```text
|
|
113
|
+
An $env() statement is not expected at this location.
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Standalone substitution
|
|
117
|
+
|
|
118
|
+
When the value is _only_ an `$env()` expression, the variable's value is
|
|
119
|
+
inserted directly. The runtime type matches the type of the environment variable
|
|
120
|
+
(always a string when set, `undefined` when not).
|
|
121
|
+
|
|
122
|
+
```json
|
|
123
|
+
{
|
|
124
|
+
"name": "my-custom-code-inbound-policy",
|
|
125
|
+
"policyType": "custom-code-inbound",
|
|
126
|
+
"handler": {
|
|
127
|
+
"export": "default",
|
|
128
|
+
"module": "$import(./modules/YOUR_MODULE)",
|
|
129
|
+
"options": {
|
|
130
|
+
"apiKey": "$env(BACKEND_API_KEY)",
|
|
131
|
+
"config2": true
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### String interpolation
|
|
138
|
+
|
|
139
|
+
You can mix `$env()` with literal text to compose a value. The build wraps the
|
|
140
|
+
result in a JavaScript template literal that's evaluated at runtime, so each
|
|
141
|
+
request gets the current value of the variable.
|
|
142
|
+
|
|
143
|
+
```json
|
|
144
|
+
{
|
|
145
|
+
"options": {
|
|
146
|
+
"endpoint": "https://$env(API_HOST)/v1",
|
|
147
|
+
"userAgent": "MyGateway/$env(APP_VERSION) ($env(ENVIRONMENT_NAME))"
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
:::note
|
|
153
|
+
|
|
154
|
+
If the variable isn't set, the interpolated portion resolves to an empty string.
|
|
155
|
+
For example, with `API_HOST` unset, `"https://$env(API_HOST)/v1"` becomes
|
|
156
|
+
`"https:///v1"`. Use a standalone `$env()` (no surrounding text) if you need to
|
|
157
|
+
detect an unset variable in your handler or policy code.
|
|
158
|
+
|
|
159
|
+
:::
|
|
160
|
+
|
|
161
|
+
### In arrays
|
|
162
|
+
|
|
163
|
+
`$env()` works inside string array elements, including mixed arrays where some
|
|
164
|
+
elements are static and others are interpolated:
|
|
165
|
+
|
|
166
|
+
```json
|
|
167
|
+
{
|
|
168
|
+
"options": {
|
|
169
|
+
"allowedKeys": ["$env(PRIMARY_KEY)", "$env(SECONDARY_KEY)"],
|
|
170
|
+
"tags": ["public", "$env(REGION)", "tier-$env(SERVICE_TIER)"]
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### In nested objects
|
|
176
|
+
|
|
177
|
+
`$env()` works at any depth within a handler or policy `options` object:
|
|
178
|
+
|
|
179
|
+
```json
|
|
180
|
+
{
|
|
181
|
+
"options": {
|
|
182
|
+
"database": {
|
|
183
|
+
"host": "$env(DB_HOST)",
|
|
184
|
+
"credentials": {
|
|
185
|
+
"username": "$env(DB_USER)",
|
|
186
|
+
"password": "$env(DB_PASSWORD)"
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Variable name rules
|
|
194
|
+
|
|
195
|
+
The name inside `$env(...)` is matched literally up to the first closing
|
|
196
|
+
parenthesis. Use only letters, digits, and underscores in the name — matching
|
|
197
|
+
what the
|
|
198
|
+
[Zuplo Portal](../articles/environment-variables.mdx#environment-variable-editor)
|
|
199
|
+
accepts when you create the variable.
|
|
200
|
+
|
|
201
|
+
## Rewrite, Forwarding, and WebSocket Handlers
|
|
202
|
+
|
|
203
|
+
The URL Rewrite handler's `rewritePattern`, the URL Forward handler's `baseUrl`,
|
|
204
|
+
and the WebSocket handler's `rewritePattern` use a different syntax. These
|
|
205
|
+
options are evaluated as JavaScript template literals at request time, so you
|
|
206
|
+
reference variables with `${env.VAR_NAME}`:
|
|
207
|
+
|
|
208
|
+
```json
|
|
209
|
+
{
|
|
210
|
+
"handler": {
|
|
211
|
+
"export": "urlForwardHandler",
|
|
212
|
+
"module": "$import(@zuplo/runtime)",
|
|
213
|
+
"options": {
|
|
214
|
+
"baseUrl": "https://${env.BACKEND_HOST}"
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Because `rewritePattern` and `baseUrl` run as code, they also have access to the
|
|
221
|
+
request, URL parts, route params, and query string. See the
|
|
222
|
+
[URL Rewrite Handler](../handlers/url-rewrite.mdx) docs for the full list of
|
|
223
|
+
available variables.
|
|
224
|
+
|
|
225
|
+
:::caution
|
|
226
|
+
|
|
227
|
+
Using `$env(VAR_NAME)` inside `rewritePattern` or `baseUrl` is the most common
|
|
228
|
+
mistake. The build will warn you when it detects this, but the value will be
|
|
229
|
+
passed through to the handler as a literal string and the rewrite will fail at
|
|
230
|
+
runtime. Always use `${env.VAR_NAME}` in these two options.
|
|
231
|
+
|
|
232
|
+
:::
|
|
233
|
+
|
|
234
|
+
## System Environment Variables
|
|
235
|
+
|
|
236
|
+
Zuplo sets the following variables for your gateway. You can't create variables
|
|
237
|
+
with these names.
|
|
238
|
+
|
|
239
|
+
| Variable | Value |
|
|
240
|
+
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
241
|
+
| `ZUPLO_ENVIRONMENT_STAGE` | The environment stage: `production`, `preview`, `working-copy`, or `local`. Use this variable to check which environment the gateway runs in. |
|
|
242
|
+
| `ZUPLO_ENVIRONMENT_TYPE` | The kind of host that runs the gateway, such as `edge` or `local`. |
|
|
243
|
+
| `ZUPLO_ENVIRONMENT_NAME` | The globally unique name of the environment, as used in its default URL. For example, `my-project-main-1235` in `my-project-main-1235.zuplo.app`. Custom domains don't change this value. |
|
|
244
|
+
| `ZUPLO_ACCOUNT_NAME` | The name of the Zuplo account that owns the environment. |
|
|
245
|
+
| `ZUPLO_PROJECT_NAME` | The name of the project that owns the environment. |
|
|
246
|
+
| `ZUPLO_BUILD_ID` | A unique ID (UUID) for the build of the environment. |
|
|
247
|
+
| `ZUPLO_COMPATIBILITY_DATE` | The [compatibility date](./compatibility-dates.mdx) of the runtime environment. |
|
|
248
|
+
|
|
249
|
+
:::note
|
|
250
|
+
|
|
251
|
+
Your Developer Portal gets a different set of system variables, and its
|
|
252
|
+
`ZUPLO_ENVIRONMENT_TYPE` has different values. See
|
|
253
|
+
[Dev Portal environment variables](../dev-portal/zudoku/guides/environment-variables.md).
|
|
254
|
+
|
|
255
|
+
:::
|
|
256
|
+
|
|
257
|
+
## Environment Detection
|
|
258
|
+
|
|
259
|
+
Zuplo doesn't set `NODE_ENV`, because the gateway runs on a V8 runtime, not
|
|
260
|
+
Node.js. Use `ZUPLO_ENVIRONMENT_STAGE` instead:
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
import { environment } from "@zuplo/runtime";
|
|
264
|
+
|
|
265
|
+
const isProduction = environment.ZUPLO_ENVIRONMENT_STAGE === "production";
|
|
266
|
+
```
|
|
267
|
+
|
|
73
268
|
## Common Patterns
|
|
74
269
|
|
|
75
270
|
### Required Environment Variables
|
|
@@ -158,28 +353,19 @@ export default async function handler(
|
|
|
158
353
|
|
|
159
354
|
### Environment-Specific Configuration
|
|
160
355
|
|
|
161
|
-
|
|
356
|
+
Use `ZUPLO_ENVIRONMENT_STAGE` to change behavior between production, preview,
|
|
357
|
+
and development environments:
|
|
162
358
|
|
|
163
359
|
```ts
|
|
164
|
-
import { environment } from "@zuplo/runtime";
|
|
360
|
+
import { environment, ZuploContext, ZuploRequest } from "@zuplo/runtime";
|
|
165
361
|
|
|
166
|
-
|
|
167
|
-
const isProduction = environment.ENVIRONMENT === "production";
|
|
168
|
-
const isStaging = environment.ENVIRONMENT === "staging";
|
|
169
|
-
const isDevelopment = environment.ENVIRONMENT === "development";
|
|
362
|
+
const isProduction = environment.ZUPLO_ENVIRONMENT_STAGE === "production";
|
|
170
363
|
|
|
171
364
|
export default async function handler(
|
|
172
365
|
request: ZuploRequest,
|
|
173
366
|
context: ZuploContext,
|
|
174
367
|
) {
|
|
175
|
-
//
|
|
176
|
-
const apiUrl = isProduction
|
|
177
|
-
? "https://api.example.com"
|
|
178
|
-
: isStaging
|
|
179
|
-
? "https://staging-api.example.com"
|
|
180
|
-
: "https://dev-api.example.com";
|
|
181
|
-
|
|
182
|
-
// Enable debug logging in non-production
|
|
368
|
+
// Enable debug logging outside production
|
|
183
369
|
if (!isProduction) {
|
|
184
370
|
context.log.debug("Incoming request", {
|
|
185
371
|
headers: Object.fromEntries(request.headers),
|
|
@@ -187,19 +373,15 @@ export default async function handler(
|
|
|
187
373
|
});
|
|
188
374
|
}
|
|
189
375
|
|
|
190
|
-
|
|
191
|
-
const headers: HeadersInit = {
|
|
192
|
-
"Content-Type": "application/json",
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
if (isProduction) {
|
|
196
|
-
headers["Strict-Transport-Security"] = "max-age=31536000";
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
return new Response("OK", { headers });
|
|
376
|
+
return new Response("OK");
|
|
200
377
|
}
|
|
201
378
|
```
|
|
202
379
|
|
|
380
|
+
To use different values per environment, such as a backend URL, set the same
|
|
381
|
+
variable with a different value for each
|
|
382
|
+
[environment](../articles/environment-variables.mdx#environments) instead of
|
|
383
|
+
branching in code.
|
|
384
|
+
|
|
203
385
|
### Secret Management
|
|
204
386
|
|
|
205
387
|
```ts
|
|
@@ -242,13 +424,6 @@ function validateSecrets() {
|
|
|
242
424
|
validateSecrets();
|
|
243
425
|
```
|
|
244
426
|
|
|
245
|
-
## Environment Detection
|
|
246
|
-
|
|
247
|
-
Zuplo doesn't provide a built-in `NODE_ENV` variable since it runs on a custom
|
|
248
|
-
V8 runtime, not Node.js. To implement environment-specific logic, define your
|
|
249
|
-
own environment variables (for example, `ENVIRONMENT`, `STAGE`, or `ENV_TYPE`)
|
|
250
|
-
and set them appropriately for each environment in your project settings.
|
|
251
|
-
|
|
252
427
|
## Best Practices
|
|
253
428
|
|
|
254
429
|
### 1. Use Descriptive Names
|
|
@@ -313,17 +488,14 @@ context.log.info("Config loaded", {
|
|
|
313
488
|
|
|
314
489
|
## Setting Environment Variables
|
|
315
490
|
|
|
316
|
-
Set environment variables in the Zuplo Portal
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
tab of your project.
|
|
321
|
-
2. Select an environment and add variables under **Environment Variables**.
|
|
322
|
-
3. Zuplo encrypts variables at rest and in transit.
|
|
491
|
+
Set environment variables in the Zuplo Portal under **Settings** >
|
|
492
|
+
[**Environment Variables**](https://portal.zuplo.com/+/account/project/settings/environment-variables).
|
|
493
|
+
For local development, use a `.env` file. See
|
|
494
|
+
[Configuring Environment Variables Locally](../articles/local-development-env-variables.mdx).
|
|
323
495
|
|
|
324
496
|
## See Also
|
|
325
497
|
|
|
326
|
-
- [Environment Variables](../articles/environment-variables.mdx) -
|
|
327
|
-
environment variables
|
|
498
|
+
- [Configuring Environment Variables](../articles/environment-variables.mdx) -
|
|
499
|
+
Setting up environment variables
|
|
328
500
|
- [ConfigurationError](./runtime-errors.mdx#configurationerror) - Error handling
|
|
329
501
|
for configuration issues
|
|
@@ -307,6 +307,30 @@ Hooks can be registered in two ways:
|
|
|
307
307
|
2. **Globally via Runtime Extensions** - Register hooks that apply to all
|
|
308
308
|
requests using [Runtime Extensions](./runtime-extensions.mdx)
|
|
309
309
|
|
|
310
|
+
## Internal route invocations
|
|
311
|
+
|
|
312
|
+
Global runtime hooks also run when
|
|
313
|
+
[`context.invokeRoute`](./zuplo-context.mdx#invokeroute) invokes another route
|
|
314
|
+
in the same gateway. This includes API routes invoked by the MCP Server handler
|
|
315
|
+
for tool calls. The hooks receive the internal route's request and a new context
|
|
316
|
+
whose `parentContext` points to the calling context.
|
|
317
|
+
|
|
318
|
+
In request and response hooks, use `context.parentContext === undefined` to
|
|
319
|
+
select only client requests, or check for a parent context to select internal
|
|
320
|
+
invocations. Pre-routing hooks receive only a request, so they can't use this
|
|
321
|
+
context check.
|
|
322
|
+
|
|
323
|
+
By default, an internal invocation inherits the parent's `requestId`, while each
|
|
324
|
+
context has a distinct `contextId`. Group related analytics events by
|
|
325
|
+
`requestId` and distinguish executions by `contextId`.
|
|
326
|
+
|
|
327
|
+
For a complete MCP example, including how to identify the tool and avoid
|
|
328
|
+
counting both the client request and its internal invocation, see
|
|
329
|
+
[Track tool calls with runtime hooks](../mcp-server/tools.mdx#track-tool-calls-with-runtime-hooks).
|
|
330
|
+
Use an [`addResponseSendingFinalHook`](#hook-onresponsesendingfinal) when your
|
|
331
|
+
event needs the response status. For an internal invocation, that status belongs
|
|
332
|
+
to the API route response; the outer `/mcp` response contains the MCP result.
|
|
333
|
+
|
|
310
334
|
## Complete Hook Pipeline Example
|
|
311
335
|
|
|
312
336
|
This example demonstrates how all hooks work together in a request/response
|
|
@@ -177,6 +177,28 @@ export default async function (request: ZuploRequest, context: ZuploContext) {
|
|
|
177
177
|
Unlike `setRawCallToolResult`, this method can be called any number of times —
|
|
178
178
|
the request object is not consumed.
|
|
179
179
|
|
|
180
|
+
#### MCP metadata in runtime hooks
|
|
181
|
+
|
|
182
|
+
The MCP Server handler stores the original `tools/call` request when it starts
|
|
183
|
+
processing the request. Availability therefore depends on where the hook runs:
|
|
184
|
+
|
|
185
|
+
| Hook location | `getRawCallToolRequest()` result |
|
|
186
|
+
| ------------------------------------------------- | --------------------------------------------------------------------------- |
|
|
187
|
+
| Global request hook on the initial `/mcp` request | `null`: the MCP handler hasn't run yet |
|
|
188
|
+
| Request or response hook on the tool's API route | The original tool call, read from the parent context |
|
|
189
|
+
| Response hook on the outer `/mcp` request | The original tool call, if the MCP handler processed a `tools/call` request |
|
|
190
|
+
|
|
191
|
+
Use `context.parentContext` to distinguish an internal invocation from the
|
|
192
|
+
initial client request. The presence of a tool call alone doesn't distinguish
|
|
193
|
+
them in a response hook. See
|
|
194
|
+
[Track tool calls with runtime hooks](../mcp-server/tools.mdx#track-tool-calls-with-runtime-hooks)
|
|
195
|
+
for an analytics example.
|
|
196
|
+
|
|
197
|
+
The helper checks only the current context and its immediate parent. If your
|
|
198
|
+
tool's API route invokes another route, the deeper invocation can't read the
|
|
199
|
+
original tool call through this helper unless your code passes the metadata
|
|
200
|
+
along. Calls such as `tools/list` don't populate tool-call metadata.
|
|
201
|
+
|
|
180
202
|
## Output schema enforcement
|
|
181
203
|
|
|
182
204
|
When a tool advertises an `outputSchema` (via `includeOutputSchema` on the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zuplo",
|
|
3
|
-
"version": "7.9.
|
|
3
|
+
"version": "7.9.15",
|
|
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.9.
|
|
36
|
-
"@zuplo/core": "7.9.
|
|
37
|
-
"@zuplo/runtime": "7.9.
|
|
38
|
-
"@zuplo/test": "7.9.
|
|
35
|
+
"@zuplo/cli": "7.9.15",
|
|
36
|
+
"@zuplo/core": "7.9.15",
|
|
37
|
+
"@zuplo/runtime": "7.9.15",
|
|
38
|
+
"@zuplo/test": "7.9.15"
|
|
39
39
|
}
|
|
40
40
|
}
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
## How it works
|
|
2
|
-
|
|
3
|
-
`context.invokeRoute` runs a route on this gateway without leaving the process,
|
|
4
|
-
and the runtime marks the request it creates by setting `context.parentContext`
|
|
5
|
-
to the calling request's context. Nothing else sets that property. A request
|
|
6
|
-
that arrives over the network never has one, whatever headers, URL, or body it
|
|
7
|
-
carries. This policy passes a request when `context.parentContext` is present
|
|
8
|
-
and rejects it otherwise.
|
|
9
|
-
|
|
10
|
-
| Request origin | Result |
|
|
11
|
-
| ------------------------------------------------------------------------------ | ------ |
|
|
12
|
-
| Any HTTP client, with or without an API key | `403` |
|
|
13
|
-
| `context.invokeRoute` from a policy, handler, or custom module on this gateway | Passes |
|
|
14
|
-
| The MCP server handler calling the route as a tool | Passes |
|
|
15
|
-
| An internal application calling another internal application | Passes |
|
|
16
|
-
|
|
17
|
-
The rejection is a `403 Forbidden` problem response. On AWS Bedrock Runtime
|
|
18
|
-
routes it uses the AWS error envelope (`AccessDeniedException`) so the AWS SDKs
|
|
19
|
-
surface the message.
|
|
20
|
-
|
|
21
|
-
The policy does not authenticate the caller and does not set `request.user`.
|
|
22
|
-
Metering records the request against the application with no consumer, and a
|
|
23
|
-
budget rule keyed on `request.user` has no value to accumulate for these
|
|
24
|
-
requests. Attach per-consumer budgets to the calling application instead.
|
|
25
|
-
|
|
26
|
-
## Add the policy
|
|
27
|
-
|
|
28
|
-
Declare the policy in `config/policies.json`:
|
|
29
|
-
|
|
30
|
-
```json
|
|
31
|
-
{
|
|
32
|
-
"policies": [
|
|
33
|
-
{
|
|
34
|
-
"name": "ai-gateway-internal-only-inbound",
|
|
35
|
-
"policyType": "ai-gateway-internal-only",
|
|
36
|
-
"handler": {
|
|
37
|
-
"export": "AIGatewayInternalOnlyInboundPolicy",
|
|
38
|
-
"module": "$import(@zuplo/runtime)",
|
|
39
|
-
"options": {}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
]
|
|
43
|
-
}
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
Then select it in the application's `inboundPolicyChain` in place of
|
|
47
|
-
`ai-gateway-auth-inbound`. Put it first so nothing else runs for a rejected
|
|
48
|
-
request:
|
|
49
|
-
|
|
50
|
-
```json
|
|
51
|
-
{
|
|
52
|
-
"inboundPolicyChain": [
|
|
53
|
-
{ "name": "ai-gateway-internal-only-inbound" },
|
|
54
|
-
{ "name": "ai-gateway-model-filtering-inbound" },
|
|
55
|
-
{ "name": "ai-gateway-metering-inbound" }
|
|
56
|
-
]
|
|
57
|
-
}
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
The policy also works as a route-level inbound policy on any route that should
|
|
61
|
-
be reachable only through `context.invokeRoute`.
|
|
62
|
-
|
|
63
|
-
## Call the application
|
|
64
|
-
|
|
65
|
-
Call the application from a policy or handler on the same gateway. No
|
|
66
|
-
`Authorization` header is needed:
|
|
67
|
-
|
|
68
|
-
```ts
|
|
69
|
-
const response = await context.invokeRoute(
|
|
70
|
-
`/${classifierAppId}/v1/chat/completions`,
|
|
71
|
-
{
|
|
72
|
-
method: "POST",
|
|
73
|
-
headers: { "content-type": "application/json" },
|
|
74
|
-
body: JSON.stringify({ model: "openai/gpt-5-mini", messages }),
|
|
75
|
-
}
|
|
76
|
-
);
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
The Smart Router and Prompt Injection Protection policies already call their
|
|
80
|
-
classifier applications this way.
|
|
81
|
-
|
|
82
|
-
A direct call from outside the gateway is rejected:
|
|
83
|
-
|
|
84
|
-
```bash
|
|
85
|
-
curl https://gateway.example.com/my-classifier-app/v1/chat/completions \
|
|
86
|
-
--header "Content-Type: application/json" \
|
|
87
|
-
--data '{
|
|
88
|
-
"model": "openai/gpt-5-mini",
|
|
89
|
-
"messages": [{ "role": "user", "content": "Hello" }]
|
|
90
|
-
}'
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
The response is `403 Forbidden`, and its `detail` explains that the application
|
|
94
|
-
only accepts requests made from inside the gateway with `context.invokeRoute`.
|
|
95
|
-
|
|
96
|
-
## What it does not check
|
|
97
|
-
|
|
98
|
-
This policy asserts that a request originated inside this gateway deployment,
|
|
99
|
-
not that the caller is trusted. Any route on the same gateway that forwards
|
|
100
|
-
requests through `context.invokeRoute`, including an MCP server that exposes the
|
|
101
|
-
application as a tool, reaches the application. Protect such routes with their
|
|
102
|
-
own authentication, exactly as you would an internal URL.
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
The Ensure Gateway Internal Invocation Only policy admits a request only when
|
|
2
|
-
this gateway made it with `context.invokeRoute`, and answers every request that
|
|
3
|
-
arrived over the network with `403 Forbidden`.
|
|
4
|
-
|
|
5
|
-
Add it to an application's `inboundPolicyChain` in place of AI Gateway
|
|
6
|
-
Authentication when the application is only ever called by other routes and
|
|
7
|
-
policies on the same gateway, such as the classifier application behind the
|
|
8
|
-
Smart Router or Prompt Injection Protection policies. Those calls never leave
|
|
9
|
-
the gateway, so an API key on them protects nothing; this policy checks the one
|
|
10
|
-
thing that matters. It has no options, makes no network calls, and never reads
|
|
11
|
-
the request body.
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://json-schema.org/draft-07/schema",
|
|
3
|
-
"$id": "https://cdn.zuplo.com/policies/runtime/schemas/ai-gateway-internal-only-inbound.json",
|
|
4
|
-
"type": "object",
|
|
5
|
-
"title": "Ensure Gateway Internal Invocation Only",
|
|
6
|
-
"isDeprecated": false,
|
|
7
|
-
"isPaidAddOn": false,
|
|
8
|
-
"isEnterprise": false,
|
|
9
|
-
"isInternal": false,
|
|
10
|
-
"isBeta": false,
|
|
11
|
-
"isHidden": false,
|
|
12
|
-
"requiresAI": true,
|
|
13
|
-
"policyType": "ai-gateway-internal-only",
|
|
14
|
-
"products": ["ai-gateway"],
|
|
15
|
-
"description": "Accepts only requests that this gateway made itself with `context.invokeRoute`, and rejects every request that arrived over the network with `403 Forbidden`.\n\nUse it in place of AI Gateway Authentication on an application that is only ever called by other routes and policies on the same gateway, such as the classifier application behind the Smart Router or Prompt Injection Protection policies. Those calls never leave the gateway, so an API key on them protects nothing. This policy asserts the one thing that matters: the request originated inside the deployment.\n\nThe check reads `context.parentContext`, which the runtime sets only on the child context that `context.invokeRoute` creates. A client cannot supply it through a header, the URL, or the body. The policy has no options, makes no network calls, reads no body, and leaves `request.user` unset.",
|
|
16
|
-
"deprecatedMessage": "",
|
|
17
|
-
"required": ["handler"],
|
|
18
|
-
"properties": {
|
|
19
|
-
"handler": {
|
|
20
|
-
"type": "object",
|
|
21
|
-
"default": {},
|
|
22
|
-
"required": ["export", "module", "options"],
|
|
23
|
-
"properties": {
|
|
24
|
-
"export": {
|
|
25
|
-
"const": "AIGatewayInternalOnlyInboundPolicy",
|
|
26
|
-
"description": "The name of the exported type"
|
|
27
|
-
},
|
|
28
|
-
"module": {
|
|
29
|
-
"const": "$import(@zuplo/runtime)",
|
|
30
|
-
"description": "The module containing the policy"
|
|
31
|
-
},
|
|
32
|
-
"options": {
|
|
33
|
-
"title": "AIGatewayInternalOnlyInboundPolicyOptions",
|
|
34
|
-
"type": "object",
|
|
35
|
-
"description": "The options for this policy. It has none.",
|
|
36
|
-
"additionalProperties": false,
|
|
37
|
-
"required": [],
|
|
38
|
-
"properties": {}
|
|
39
|
-
}
|
|
40
|
-
},
|
|
41
|
-
"examples": [
|
|
42
|
-
{
|
|
43
|
-
"export": "AIGatewayInternalOnlyInboundPolicy",
|
|
44
|
-
"module": "$import(@zuplo/runtime)",
|
|
45
|
-
"options": {}
|
|
46
|
-
}
|
|
47
|
-
]
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|