@frontmcp/skills 1.4.1 → 1.5.0
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/catalog/frontmcp-deployment/examples/deploy-to-cloudflare/basic-worker-deploy.md +7 -4
- package/catalog/frontmcp-deployment/examples/deploy-to-cloudflare/worker-custom-domain.md +7 -4
- package/catalog/frontmcp-deployment/examples/deploy-to-cloudflare/worker-with-kv-storage.md +6 -3
- package/catalog/frontmcp-deployment/references/deploy-to-cloudflare-skills-only.md +72 -18
- package/catalog/frontmcp-deployment/references/deploy-to-cloudflare.md +58 -9
- package/catalog/frontmcp-development/SKILL.md +1 -1
- package/catalog/frontmcp-development/examples/openapi-adapter/ref-security-and-filtering.md +19 -12
- package/catalog/frontmcp-development/references/create-plugin-hooks.md +32 -0
- package/catalog/frontmcp-development/references/openapi-adapter.md +51 -39
- package/catalog/frontmcp-extensibility/references/skill-audit-log.md +2 -2
- package/catalog/frontmcp-production-readiness/examples/production-cloudflare/durable-objects-state.md +5 -2
- package/catalog/frontmcp-production-readiness/examples/production-cloudflare/wrangler-config.md +2 -1
- package/catalog/skills-manifest.json +4 -4
- package/package.json +1 -1
|
@@ -47,12 +47,15 @@ export default MyServer;
|
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
```toml
|
|
50
|
-
# wrangler.toml — the Cloudflare adapter overwrites
|
|
51
|
-
#
|
|
52
|
-
# frontmcp.config.deployments[].wrangler).
|
|
50
|
+
# wrangler.toml — the Cloudflare adapter overwrites these top-level keys on
|
|
51
|
+
# every build (configure name/compatibility_date/compatibility_flags via
|
|
52
|
+
# frontmcp.config.deployments[].wrangler). nodejs_compat is always emitted —
|
|
53
|
+
# the worker entry require()s @frontmcp/sdk + Express, so without it the
|
|
54
|
+
# Worker fails to boot.
|
|
53
55
|
name = "frontmcp-worker"
|
|
54
56
|
main = "dist/cloudflare/index.js"
|
|
55
|
-
compatibility_date = "2024-
|
|
57
|
+
compatibility_date = "2024-09-23"
|
|
58
|
+
compatibility_flags = ["nodejs_compat"]
|
|
56
59
|
```
|
|
57
60
|
|
|
58
61
|
```bash
|
|
@@ -55,12 +55,15 @@ export default TranslateServer;
|
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
```toml
|
|
58
|
-
# wrangler.toml — name/compatibility_date are managed by
|
|
59
|
-
# bindings appended below are re-applied after each build (the
|
|
60
|
-
# overwrites the top-level keys on every `frontmcp build --target
|
|
58
|
+
# wrangler.toml — name/compatibility_date/compatibility_flags are managed by
|
|
59
|
+
# frontmcp.config; bindings appended below are re-applied after each build (the
|
|
60
|
+
# adapter overwrites the top-level keys on every `frontmcp build --target
|
|
61
|
+
# cloudflare`). nodejs_compat is always emitted — the worker entry needs Node
|
|
62
|
+
# builtins or it won't boot.
|
|
61
63
|
name = "translate-worker"
|
|
62
64
|
main = "dist/cloudflare/index.js"
|
|
63
|
-
compatibility_date = "2024-
|
|
65
|
+
compatibility_date = "2024-09-23"
|
|
66
|
+
compatibility_flags = ["nodejs_compat"]
|
|
64
67
|
|
|
65
68
|
[[kv_namespaces]]
|
|
66
69
|
binding = "FRONTMCP_KV"
|
|
@@ -49,11 +49,14 @@ export default MyServer;
|
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
```toml
|
|
52
|
-
# wrangler.toml — name/main/compatibility_date are
|
|
53
|
-
#
|
|
52
|
+
# wrangler.toml — name/main/compatibility_date/compatibility_flags are
|
|
53
|
+
# rewritten by the build (nodejs_compat is always emitted; the worker entry
|
|
54
|
+
# needs Node builtins or it won't boot). Bindings below need to be re-applied
|
|
55
|
+
# (or appended) after each build.
|
|
54
56
|
name = "frontmcp-worker"
|
|
55
57
|
main = "dist/cloudflare/index.js"
|
|
56
|
-
compatibility_date = "2024-
|
|
58
|
+
compatibility_date = "2024-09-23"
|
|
59
|
+
compatibility_flags = ["nodejs_compat"]
|
|
57
60
|
|
|
58
61
|
[[kv_namespaces]]
|
|
59
62
|
binding = "FRONTMCP_KV"
|
|
@@ -1,14 +1,34 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: deploy-to-cloudflare-skills-only
|
|
3
|
-
description: Deploy
|
|
3
|
+
description: Deploy an auto-updating FrontMCP server to Cloudflare Workers with @frontmcp/edge createEdgeMcp — managed skilled-OpenAPI bundle pulled from a SaaS endpoint, cached in KV, refreshed on a Cron Trigger
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Deploy to Cloudflare Workers (Skills-Only Model)
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
# Deploy to Cloudflare Workers (Managed / Skills-Only Model)
|
|
7
|
+
|
|
8
|
+
> **⚠️ Status — experimental.** `@frontmcp/edge` `createEdgeMcp` **deploys and
|
|
9
|
+
> serves on real Cloudflare** (verified live), as long as you (1) keep
|
|
10
|
+
> `serve: false` (now the createEdgeMcp default) and (2) stub three Node-only
|
|
11
|
+
> transports your bundler statically includes but the edge never uses —
|
|
12
|
+
> `express`, `raw-body`, `cross-spawn`. NOTE: **miniflare local-dev is stricter
|
|
13
|
+
> than production** and rejects `node:http2`/`node:fs` that real Cloudflare's
|
|
14
|
+
> `nodejs_compat` provides, so the local managed e2e is skipped even though the
|
|
15
|
+
> package runs in production. Managed mode (this page) additionally needs a SaaS
|
|
16
|
+
> bundle endpoint + the optional peer `@frontmcp/plugin-skilled-openapi`. The
|
|
17
|
+
> worker-conditioned SDK build (roadmap) removes the manual stubs. For the
|
|
18
|
+
> simplest production path, the decorator build in
|
|
19
|
+
> [`deploy-to-cloudflare.md`](./deploy-to-cloudflare.md) needs none of this.
|
|
20
|
+
> The GitHub Action / signed-resync-webhook / Durable Object stores / Frontegg
|
|
21
|
+
> edge auth described below remain ROADMAP — not yet implemented.
|
|
22
|
+
|
|
23
|
+
The managed model hosts FrontMCP where the MCP surface is a small set of
|
|
24
|
+
meta-tools (`search_skill`, `load_skill`, `run_workflow`) and every capability
|
|
25
|
+
is reached through a skill compiled from an OpenAPI spec. `run_workflow` runs a
|
|
26
|
+
short AgentScript program in the Worker isolate, where each `callTool(actionId,
|
|
27
|
+
input)` invokes a loaded skill's operation. The bundle is pulled from a SaaS
|
|
28
|
+
endpoint, cached in KV, and refreshed on a Cron Trigger.
|
|
9
29
|
|
|
10
30
|
For the conceptual picture, see [Skills-Only Deployment](https://docs.agentfront.dev/frontmcp/features/skills-only-deployment).
|
|
11
|
-
For the
|
|
31
|
+
For the production-ready decorator build, see [`deploy-to-cloudflare.md`](./deploy-to-cloudflare.md).
|
|
12
32
|
|
|
13
33
|
## When to Use This Skill
|
|
14
34
|
|
|
@@ -32,21 +52,47 @@ For the older Express-to-Workers adapter, see [`deploy-to-cloudflare.md`](./depl
|
|
|
32
52
|
## Worker Entry File
|
|
33
53
|
|
|
34
54
|
```ts
|
|
35
|
-
// worker.ts (
|
|
36
|
-
import {
|
|
55
|
+
// worker.ts — the real API is createEdgeMcp (not createWorker)
|
|
56
|
+
import { createEdgeMcp, kvBundleCacheFromEnv } from '@frontmcp/edge';
|
|
57
|
+
|
|
58
|
+
export default createEdgeMcp({
|
|
59
|
+
info: { name: 'my-worker', version: '1.0.0' },
|
|
60
|
+
apps: [],
|
|
61
|
+
tasks: { enabled: false },
|
|
62
|
+
managed: {
|
|
63
|
+
endpoint: 'https://cloud.example.com/v1/bundles/acme',
|
|
64
|
+
authToken: 'pinned-pull-token',
|
|
65
|
+
expectedAudience: 'acme-mcp',
|
|
66
|
+
jwksUrl: 'https://cloud.example.com/.well-known/jwks.json',
|
|
67
|
+
expectedIssuer: 'https://cloud.example.com',
|
|
68
|
+
// KV-backed last-good cache, resolved from the per-request `env`.
|
|
69
|
+
cache: kvBundleCacheFromEnv('BUNDLE_CACHE'),
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
```
|
|
37
73
|
|
|
38
|
-
|
|
74
|
+
`createEdgeMcp` returns `{ fetch, scheduled }`: `fetch` serves MCP; `scheduled`
|
|
75
|
+
is the **Cron Trigger** entrypoint that pulls a fresh bundle and hot-swaps it.
|
|
76
|
+
Managed mode requires the optional peer `@frontmcp/plugin-skilled-openapi`.
|
|
39
77
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
78
|
+
This path is bundled by **wrangler** (not `frontmcp build`), so you maintain
|
|
79
|
+
`wrangler.toml` yourself — it needs a `[[kv_namespaces]] binding = "BUNDLE_CACHE"`
|
|
80
|
+
and a `[triggers] crontabs = [...]` (the `managed.pollIntervalMs` option is
|
|
81
|
+
ignored on edge — Workers have no background timers; the Cron drives refresh):
|
|
44
82
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
83
|
+
```toml
|
|
84
|
+
name = "my-worker"
|
|
85
|
+
main = "worker.ts"
|
|
86
|
+
compatibility_date = "2024-09-23"
|
|
87
|
+
compatibility_flags = ["nodejs_compat"]
|
|
48
88
|
|
|
49
|
-
|
|
89
|
+
[[kv_namespaces]]
|
|
90
|
+
binding = "BUNDLE_CACHE"
|
|
91
|
+
id = "<your-kv-namespace-id>"
|
|
92
|
+
|
|
93
|
+
[triggers]
|
|
94
|
+
crontabs = ["*/5 * * * *"]
|
|
95
|
+
```
|
|
50
96
|
|
|
51
97
|
## Storage Layout (Opinionated Default)
|
|
52
98
|
|
|
@@ -92,7 +138,11 @@ Day-to-day skill / OpenAPI edits are pure hot-reload. `wrangler deploy` is only
|
|
|
92
138
|
|
|
93
139
|
`@enclave-vm/core` (full VM) needs `node:vm` and is NOT Worker-safe — the Worker target uses AST-preflight + frozen scope only.
|
|
94
140
|
|
|
95
|
-
## Auth at the Edge
|
|
141
|
+
## Auth at the Edge 🚧 Roadmap — not yet implemented
|
|
142
|
+
|
|
143
|
+
> The Frontegg **edge** JWT verification below is a ROADMAP shape, not a shipped
|
|
144
|
+
> feature. Don't wire it expecting edge-native verification today; use the
|
|
145
|
+
> standard auth providers via the decorator build until this lands.
|
|
96
146
|
|
|
97
147
|
```yaml
|
|
98
148
|
auth:
|
|
@@ -126,7 +176,11 @@ wrangler secret put ACME_API_TOKEN
|
|
|
126
176
|
|
|
127
177
|
The cross-validator REJECTS any manifest that references a secret name not declared in `secrets[]`.
|
|
128
178
|
|
|
129
|
-
## Sample `.github/workflows/deploy.yml`
|
|
179
|
+
## Sample `.github/workflows/deploy.yml` 🚧 Roadmap — not yet implemented
|
|
180
|
+
|
|
181
|
+
> The packaged GitHub Action and the signed-resync webhook are ROADMAP. The
|
|
182
|
+
> workflow below is an illustrative target, not a copy-paste-ready pipeline —
|
|
183
|
+
> deploy manually with `wrangler deploy` until the Action ships.
|
|
130
184
|
|
|
131
185
|
```yaml
|
|
132
186
|
name: Deploy
|
|
@@ -64,19 +64,20 @@ wrangler.toml # Wrangler configuration (overwritten on every build)
|
|
|
64
64
|
|
|
65
65
|
Cloudflare Workers use CommonJS (not ESM). The build command sets `--module commonjs` automatically.
|
|
66
66
|
|
|
67
|
-
> **Important:** The Cloudflare adapter sets `alwaysWriteConfig: true` and overwrites the entire `wrangler.toml` on every build with the
|
|
67
|
+
> **Important:** The Cloudflare adapter sets `alwaysWriteConfig: true` and overwrites the entire `wrangler.toml` on every build with the template below. Hand-edited bindings (`[[kv_namespaces]]`, `[vars]`, `[[d1_databases]]`, etc.) WILL be erased the next time you run `frontmcp build --target cloudflare`. Configure `name`, `compatibility_date`, and extra `compatibility_flags` via your `frontmcp.config` file's `deployments[].wrangler` section, and keep bindings in a separate config file referenced from your toolchain (or re-add them after each build).
|
|
68
68
|
|
|
69
69
|
## Step 3: Configure wrangler.toml
|
|
70
70
|
|
|
71
|
-
The build always writes
|
|
71
|
+
The build always writes this. `compatibility_flags = ["nodejs_compat"]` is **always** emitted — the worker entry is an ES Module that imports `@frontmcp/sdk`'s web-fetch handler, which still transitively pulls in Node builtins (no Express on the Worker), so without the flag the deployed Worker fails to load. The default `compatibility_date` is `2024-09-23` (the date that enables full `nodejs_compat`). `main` is `dist/cloudflare/index.js`.
|
|
72
72
|
|
|
73
73
|
```toml
|
|
74
74
|
name = "frontmcp-worker"
|
|
75
75
|
main = "dist/cloudflare/index.js"
|
|
76
|
-
compatibility_date = "2024-
|
|
76
|
+
compatibility_date = "2024-09-23"
|
|
77
|
+
compatibility_flags = ["nodejs_compat"]
|
|
77
78
|
```
|
|
78
79
|
|
|
79
|
-
`name` and `
|
|
80
|
+
`name`, `compatibility_date`, and any extra `compatibilityFlags` come from `frontmcp.config.{ts,js}`'s `deployments` array (`nodejs_compat` is merged in automatically). Example:
|
|
80
81
|
|
|
81
82
|
```ts
|
|
82
83
|
// frontmcp.config.ts
|
|
@@ -87,6 +88,8 @@ export default {
|
|
|
87
88
|
wrangler: {
|
|
88
89
|
name: 'my-worker',
|
|
89
90
|
compatibilityDate: '2025-01-15',
|
|
91
|
+
// Optional — nodejs_compat is always added for you.
|
|
92
|
+
compatibilityFlags: ['nodejs_compat_populate_process_env'],
|
|
90
93
|
},
|
|
91
94
|
},
|
|
92
95
|
],
|
|
@@ -99,6 +102,7 @@ To add KV storage or other bindings, append them AFTER each build (or use a wrap
|
|
|
99
102
|
name = "my-worker"
|
|
100
103
|
main = "dist/cloudflare/index.js"
|
|
101
104
|
compatibility_date = "2025-01-15"
|
|
105
|
+
compatibility_flags = ["nodejs_compat"]
|
|
102
106
|
|
|
103
107
|
[[kv_namespaces]]
|
|
104
108
|
binding = "FRONTMCP_KV"
|
|
@@ -170,12 +174,57 @@ curl -X POST https://frontmcp-worker.your-subdomain.workers.dev/mcp \
|
|
|
170
174
|
-d '{"jsonrpc":"2.0","method":"tools/list","id":1}'
|
|
171
175
|
```
|
|
172
176
|
|
|
177
|
+
## Endpoint path, CORS & SSE — config-driven
|
|
178
|
+
|
|
179
|
+
The worker's transport is driven by the standard `http` + `transport` config (the same fields the Express host reads), so behaviour is identical on both adapters. The worker serves MCP at **exactly one path** — `http.entryPath` (the worker root `/` when unset) — not a guessed `/` + `/mcp` set. Cloudflare never strips the path before it reaches the worker:
|
|
180
|
+
|
|
181
|
+
| Clients use | `http.entryPath` | `wrangler.toml` route | Worker serves |
|
|
182
|
+
| --- | --- | --- | --- |
|
|
183
|
+
| `https://mcp.example.com` (subdomain) | omit (`/`) | `routes = [{ pattern = "mcp.example.com", custom_domain = true }]` | `/` |
|
|
184
|
+
| `https://example.com/mcp` (path) | `'/mcp'` | `routes = [{ pattern = "example.com/mcp*", zone_name = "example.com" }]` | `/mcp` |
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
createEdgeMcp({
|
|
188
|
+
/* …info, apps… */
|
|
189
|
+
http: {
|
|
190
|
+
entryPath: '/mcp', // the ONE path MCP is served at (omit → root '/')
|
|
191
|
+
cors: { origin: true }, // browser MCP clients (e.g. Inspector "Direct" mode); { origin, credentials, maxAge }
|
|
192
|
+
},
|
|
193
|
+
// transport: 'legacy'/'modern' → SSE streaming on POST; 'stateless-api' → buffered JSON.
|
|
194
|
+
});
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
- **CORS** ← `http.cors` (`false` disables; a function `origin` is unsupported on the worker — use `true` / string / `string[]`).
|
|
198
|
+
- **SSE** ← derived from the transport protocol (streaming under `legacy`/`modern`, buffered JSON under `stateless-api`); server→client `GET` streams are always honored.
|
|
199
|
+
- A trailing slash is normalized (`/mcp/` matches `/mcp`); `/healthz` + `/readyz` always answer a liveness 200 regardless of `entryPath`.
|
|
200
|
+
|
|
173
201
|
## Workers Limitations
|
|
174
202
|
|
|
175
203
|
- **Bundle size**: Workers have a 1 MB compressed / 10 MB uncompressed limit (paid plan: 10 MB / 30 MB). Review dependencies and remove unused packages to reduce bundle size.
|
|
176
204
|
- **CPU time**: 10 ms CPU time on free plan, 30 seconds on paid. Long-running operations must be optimized or use Durable Objects.
|
|
177
205
|
- **No native modules**: `better-sqlite3` and other native Node.js modules are not available. Use KV, D1, or Upstash Redis for storage.
|
|
178
|
-
- **Streaming**: SSE
|
|
206
|
+
- **Streaming**: Streamable HTTP works, including SSE responses (`POST` with `Accept: text/event-stream`) and the server→client SSE `GET` stream. The worker uses the SDK's `WebStandardStreamableHTTPServerTransport` — which **is** the standard Streamable HTTP transport (the Node `StreamableHTTPServerTransport` is a thin `req`/`res` wrapper over it, so there's one engine). **Server→client notifications** on the standalone `GET` stream require **stateful sessions** — set `sessions: {}` and bind the `SessionDurableObject` (Durable Object) per `Mcp-Session-Id`. Without it the worker is stateless and the `GET` stream can't deliver pushed notifications.
|
|
207
|
+
|
|
208
|
+
### Stateful sessions (Durable Object)
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
const mcp = createEdgeMcp({ /* …info, apps… */ http: { entryPath: '/mcp' }, sessions: {} });
|
|
212
|
+
export default mcp;
|
|
213
|
+
export const FrontMcpSession = mcp.SessionDurableObject;
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
```toml
|
|
217
|
+
[[durable_objects.bindings]]
|
|
218
|
+
name = "FRONTMCP_SESSIONS"
|
|
219
|
+
class_name = "FrontMcpSession"
|
|
220
|
+
[[migrations]]
|
|
221
|
+
tag = "v1"
|
|
222
|
+
new_classes = ["FrontMcpSession"]
|
|
223
|
+
[vars]
|
|
224
|
+
MCP_SESSION_SECRET = "..." # required on production isolates; bridged into process.env
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
One DO per session holds a persistent transport so the `GET` notification stream stays open and `tools/call` notifications reach it. It runs the **same `http:request` flow** (auth/session:verify/router/audit/metrics + hooks) as the stateless path — so transparent auth returns `401` + `WWW-Authenticate` on the worker too.
|
|
179
228
|
|
|
180
229
|
## Storage Options
|
|
181
230
|
|
|
@@ -190,16 +239,16 @@ curl -X POST https://frontmcp-worker.your-subdomain.workers.dev/mcp \
|
|
|
190
239
|
| Problem | Cause | Solution |
|
|
191
240
|
| ----------------------------- | ---------------------------------------------- | ------------------------------------------------------------------------- |
|
|
192
241
|
| Worker exceeds size limit | Too many bundled dependencies | Review dependencies and remove unused packages to reduce bundle size |
|
|
193
|
-
| Module format errors |
|
|
242
|
+
| Module format errors | Worker bundled as a Service Worker | FrontMCP Cloudflare builds emit an **ES Module Worker** (`export default { fetch }`); `nodejs_compat` requires it. Don't force `type`/CommonJS |
|
|
194
243
|
| KV binding errors | Namespace not created or binding name mismatch | Run `wrangler kv:namespace create` and copy the `id` into `wrangler.toml` |
|
|
195
244
|
| Timeout errors | CPU time exceeds plan limit | Upgrade plan or offload heavy computation to Durable Objects |
|
|
196
|
-
| CORS failures on MCP endpoint | Missing CORS headers in Worker response |
|
|
245
|
+
| CORS failures on MCP endpoint | Missing CORS headers in Worker response | `@frontmcp/edge`: pass `cors: { origin: true }` to `createEdgeMcp({...})` (transport-level CORS) |
|
|
197
246
|
|
|
198
247
|
## Common Patterns
|
|
199
248
|
|
|
200
249
|
| Pattern | Correct | Incorrect | Why |
|
|
201
250
|
| ------------------ | -------------------------------------------------------------------------- | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
|
202
|
-
| Module format |
|
|
251
|
+
| Module format | ES Module Worker (`main = "dist/cloudflare/index.js"`, `export default { fetch }`) | Service Worker / forced CommonJS | FrontMCP Cloudflare builds emit an ES Module Worker at this exact path; the build overwrites `wrangler.toml`. `nodejs_compat` requires the Module shape |
|
|
203
252
|
| Transport key | `transport: { protocol: 'modern' }` (or `{ sse: true, streamable: true }`) | `transport: { type: 'sse' }` | The schema field is `protocol`; valid presets are `'legacy' \| 'modern' \| 'stateless-api' \| 'full'`, or pass a `ProtocolConfig` object |
|
|
204
253
|
| Storage binding | `[[kv_namespaces]]` with matching `binding` | Hardcoded KV namespace ID in code | Bindings are injected at runtime by Workers |
|
|
205
254
|
| Compatibility date | Set via `frontmcp.config.deployments[].wrangler.compatibilityDate` | Hand-editing `wrangler.toml` | The build overwrites `wrangler.toml`; config-driven values survive |
|
|
@@ -215,7 +264,7 @@ curl -X POST https://frontmcp-worker.your-subdomain.workers.dev/mcp \
|
|
|
215
264
|
|
|
216
265
|
**Configuration**
|
|
217
266
|
|
|
218
|
-
- [ ] `wrangler.toml` has correct `name`, `main`, and `
|
|
267
|
+
- [ ] `wrangler.toml` has correct `name`, `main`, `compatibility_date`, and `compatibility_flags = ["nodejs_compat"]`
|
|
219
268
|
- [ ] KV namespace IDs match between dashboard and `wrangler.toml`
|
|
220
269
|
- [ ] Secrets are stored via `wrangler secret put`, not in `[vars]`
|
|
221
270
|
|
|
@@ -257,7 +257,7 @@ The full `@Tool({...})` surface — including the former `create-tool-annotation
|
|
|
257
257
|
| [`basic-openapi-adapter`](./examples/openapi-adapter/basic-openapi-adapter.md) | Basic | Demonstrates converting an OpenAPI specification into MCP tools automatically using `OpenapiAdapter` with minimal configuration. |
|
|
258
258
|
| [`authenticated-adapter-with-polling`](./examples/openapi-adapter/authenticated-adapter-with-polling.md) | Intermediate | Demonstrates configuring authentication (API key and bearer token) and automatic spec polling for OpenAPI adapters. |
|
|
259
259
|
| [`format-resolution-and-custom-resolvers`](./examples/openapi-adapter/format-resolution-and-custom-resolvers.md) | Intermediate | Demonstrates using built-in and custom format resolvers to enrich tool input schemas with concrete constraints from OpenAPI format values. |
|
|
260
|
-
| [`ref-security-and-filtering`](./examples/openapi-adapter/ref-security-and-filtering.md) | Intermediate | Demonstrates configuring $ref resolution security to prevent SSRF attacks and filtering which API operations become MCP tools. |
|
|
260
|
+
| [`ref-security-and-filtering`](./examples/openapi-adapter/ref-security-and-filtering.md) | Intermediate | Demonstrates configuring $ref / spec-URL resolution security to prevent SSRF attacks (GHSA-65h7-9wrw-629c) and filtering which API operations become MCP tools. |
|
|
261
261
|
| [`multi-api-hub-with-inline-spec`](./examples/openapi-adapter/multi-api-hub-with-inline-spec.md) | Advanced | Demonstrates registering multiple OpenAPI adapters from different APIs in a single app, including one with an inline spec definition instead of a remote URL. |
|
|
262
262
|
|
|
263
263
|
### `official-plugins`
|
|
@@ -2,19 +2,19 @@
|
|
|
2
2
|
name: ref-security-and-filtering
|
|
3
3
|
reference: openapi-adapter
|
|
4
4
|
level: intermediate
|
|
5
|
-
description: 'Demonstrates configuring $ref resolution security to prevent SSRF attacks and filtering which API operations become MCP tools.'
|
|
5
|
+
description: 'Demonstrates configuring $ref / spec-URL resolution security to prevent SSRF attacks (GHSA-65h7-9wrw-629c) and filtering which API operations become MCP tools.'
|
|
6
6
|
tags: [development, openapi, adapters, security, ssrf, filtering]
|
|
7
7
|
features:
|
|
8
|
-
- '
|
|
9
|
-
- '
|
|
10
|
-
- 'Using `
|
|
8
|
+
- 'Secure defaults: external `$ref` resolution off, spec-URL redirects not followed, internal/private targets blocked (DNS-resolved) — on `mcp-from-openapi` >= 2.5.0'
|
|
9
|
+
- 'Opting back into external refs with `allowedProtocols`, and restricting the spec URL + `$ref`s with `allowedHosts`'
|
|
10
|
+
- 'Using `allowInternalIPs` for trusted internal/local targets (governs the spec URL and `$ref`s)'
|
|
11
11
|
- 'Filtering operations with `includeOperations`, `excludeOperations`, and `filterFn`'
|
|
12
12
|
- 'Combining security hardening with operation filtering for a production-ready setup'
|
|
13
13
|
---
|
|
14
14
|
|
|
15
15
|
# $ref Security and Operation Filtering
|
|
16
16
|
|
|
17
|
-
Demonstrates configuring $ref resolution security to prevent SSRF attacks and filtering which API operations become MCP tools.
|
|
17
|
+
Demonstrates configuring $ref / spec-URL resolution security to prevent SSRF attacks (GHSA-65h7-9wrw-629c) and filtering which API operations become MCP tools.
|
|
18
18
|
|
|
19
19
|
## Code
|
|
20
20
|
|
|
@@ -26,14 +26,19 @@ import { OpenapiAdapter } from '@frontmcp/adapters';
|
|
|
26
26
|
@App({
|
|
27
27
|
name: 'secure-app',
|
|
28
28
|
adapters: [
|
|
29
|
-
// Production-hardened:
|
|
29
|
+
// Production-hardened: ENABLE external $refs but restrict to trusted hosts.
|
|
30
|
+
// (External refs are OFF by default in FrontMCP; you must opt in.)
|
|
31
|
+
// NOTE: allowedHosts now also gates the spec URL itself, so the spec host
|
|
32
|
+
// (api.partner.com) must be in the list. Internal targets stay blocked and
|
|
33
|
+
// hostnames are DNS-resolved (mcp-from-openapi >= 2.5.0).
|
|
30
34
|
OpenapiAdapter.init({
|
|
31
35
|
name: 'partner-api',
|
|
32
36
|
url: 'https://api.partner.com/openapi.json',
|
|
33
37
|
baseUrl: 'https://api.partner.com',
|
|
34
38
|
loadOptions: {
|
|
35
39
|
refResolution: {
|
|
36
|
-
|
|
40
|
+
allowedProtocols: ['http', 'https'], // opt back into external refs
|
|
41
|
+
// Only allow the spec URL + $refs pointing to the partner's own hosts
|
|
37
42
|
allowedHosts: ['schemas.partner.com', 'api.partner.com'],
|
|
38
43
|
// Block additional hosts known to be problematic
|
|
39
44
|
blockedHosts: ['internal.partner.com'],
|
|
@@ -50,7 +55,8 @@ import { OpenapiAdapter } from '@frontmcp/adapters';
|
|
|
50
55
|
},
|
|
51
56
|
}),
|
|
52
57
|
|
|
53
|
-
// Internal API: allow internal
|
|
58
|
+
// Internal API: allow internal targets (trusted environment only).
|
|
59
|
+
// allowInternalIPs re-allows BOTH the spec-URL fetch to 10.0.0.5 AND $refs.
|
|
54
60
|
OpenapiAdapter.init({
|
|
55
61
|
name: 'internal-api',
|
|
56
62
|
url: 'http://10.0.0.5:8080/openapi.json',
|
|
@@ -67,7 +73,8 @@ import { OpenapiAdapter } from '@frontmcp/adapters';
|
|
|
67
73
|
},
|
|
68
74
|
}),
|
|
69
75
|
|
|
70
|
-
// Maximum lockdown: no external $refs at all
|
|
76
|
+
// Maximum lockdown: no external $refs at all. This matches the FrontMCP
|
|
77
|
+
// default (external refs off) — shown explicitly for clarity.
|
|
71
78
|
OpenapiAdapter.init({
|
|
72
79
|
name: 'sandbox-api',
|
|
73
80
|
url: 'https://sandbox.example.com/openapi.json',
|
|
@@ -99,9 +106,9 @@ class MyServer {}
|
|
|
99
106
|
|
|
100
107
|
## What This Demonstrates
|
|
101
108
|
|
|
102
|
-
-
|
|
103
|
-
-
|
|
104
|
-
- Using `
|
|
109
|
+
- Secure defaults: external `$ref` resolution off, spec-URL redirects not followed, internal/private targets blocked (DNS-resolved) — on `mcp-from-openapi` >= 2.5.0
|
|
110
|
+
- Opting back into external refs with `allowedProtocols`, and restricting the spec URL + `$ref`s with `allowedHosts`
|
|
111
|
+
- Using `allowInternalIPs` for trusted internal/local targets (governs the spec URL and `$ref`s)
|
|
105
112
|
- Filtering operations with `includeOperations`, `excludeOperations`, and `filterFn`
|
|
106
113
|
- Combining security hardening with operation filtering for a production-ready setup
|
|
107
114
|
|
|
@@ -66,6 +66,38 @@ These are the flow names with pre-built hook decorator exports in `@frontmcp/sdk
|
|
|
66
66
|
| `channels:send-notification` | Channel notification send | `ChannelSendHook` |
|
|
67
67
|
| `channels:list` | Channel listing | `ChannelListHook` |
|
|
68
68
|
|
|
69
|
+
## Strict architecture: flows are the only path — never bypass them
|
|
70
|
+
|
|
71
|
+
This is the load-bearing invariant behind every hook above: in FrontMCP **every
|
|
72
|
+
request runs through a flow**, and because flows are made of `@Stage` steps that
|
|
73
|
+
`FlowHooksOf` exposes for interception, hooks work *everywhere* automatically.
|
|
74
|
+
The hookability is only guaranteed because nothing handles a request outside a
|
|
75
|
+
flow.
|
|
76
|
+
|
|
77
|
+
Therefore:
|
|
78
|
+
|
|
79
|
+
- **Never bypass the flow pipeline to make something work.** Add or extend a flow
|
|
80
|
+
+ its stages; do not hand-roll request logic (auth, transport, routing) in a
|
|
81
|
+
transport/adapter that skips the flow. A bypass silently deletes every hook on
|
|
82
|
+
that path.
|
|
83
|
+
- **Adapters only translate.** A transport adapter (Express, the Web-fetch/worker
|
|
84
|
+
handler, stdio) converts its native request/response to the flow's normalized
|
|
85
|
+
`ServerRequest` + `httpRespond` output and then runs the **same** flows. Two
|
|
86
|
+
adapters must never diverge in behavior (e.g. one enforcing auth, another not).
|
|
87
|
+
- **Fix runtime gaps in the flow, not around it.** If a flow stage can't run in a
|
|
88
|
+
target runtime (e.g. a stage needs a Node `ServerResponse` but a Worker only has
|
|
89
|
+
Web `Request`/`Response`), make the stage runtime-agnostic (emit normalized
|
|
90
|
+
output each adapter renders) — do not write a runtime-specific shortcut that
|
|
91
|
+
skips the flow.
|
|
92
|
+
- **Cross-cutting concerns are stages, not inlined code.** Auth, quota, audit,
|
|
93
|
+
metrics belong to flow stages (so they're hookable), never re-implemented inside
|
|
94
|
+
an adapter.
|
|
95
|
+
- **Use `FlowInputOf` / `FlowOutputOf`**, never ad-hoc `as { … }` casts on flow
|
|
96
|
+
results — a cast is a sign you're working around the flow instead of with it.
|
|
97
|
+
|
|
98
|
+
If a change handles a request without going through a flow, or inlines a
|
|
99
|
+
cross-cutting concern, it's wrong — rework it through a hookable flow.
|
|
100
|
+
|
|
69
101
|
## Server Lifecycle Hooks
|
|
70
102
|
|
|
71
103
|
In addition to flow-based hooks, the framework exposes a single `scope.onServerStarted(callback)` API for post-startup work. Callbacks register against the active `ScopeEntry` and run after `server.start()` completes.
|
|
@@ -285,68 +285,79 @@ OpenapiAdapter.init({
|
|
|
285
285
|
| `resolveFormats` | `boolean` | `false` | Enable built-in format resolvers (uuid, date-time, email, int32, etc.) |
|
|
286
286
|
| `formatResolvers` | `Record<string, FormatResolver>` | `undefined` | Custom resolvers; merged with built-ins when `resolveFormats: true`, custom takes precedence |
|
|
287
287
|
|
|
288
|
-
## $ref Resolution Security
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
288
|
+
## Spec Loading & $ref Resolution Security (SSRF)
|
|
289
|
+
|
|
290
|
+
> **Advisories: GHSA-v6ph-xcq9-qxxj, GHSA-65h7-9wrw-629c.** Loading a spec fetches
|
|
291
|
+
> attacker-influenceable URLs (the spec `url` and any external `$ref`s) — an SSRF
|
|
292
|
+
> vector. Hostname-string denylists are bypassable via DNS names that resolve to
|
|
293
|
+
> internal IPs (e.g. `http://127.0.0.1.nip.io/`), redirects, and IPv4-mapped IPv6.
|
|
294
|
+
> **Use `mcp-from-openapi` ≥ 2.5.0**, which resolves DNS and validates the
|
|
295
|
+
> *resolved IP*, guards the spec-URL fetch (not just `$ref`s), and re-validates
|
|
296
|
+
> every redirect hop.
|
|
297
|
+
|
|
298
|
+
FrontMCP's secure defaults:
|
|
299
|
+
|
|
300
|
+
- **External `$ref` resolution is disabled by default** — only internal `#/...`
|
|
301
|
+
refs resolve; inline `spec:` is unaffected. Enable by setting
|
|
302
|
+
`loadOptions.refResolution` explicitly.
|
|
303
|
+
- **Spec-URL redirects are not followed by default** — opt in with
|
|
304
|
+
`loadOptions.followRedirects: true` (each hop is still re-validated).
|
|
305
|
+
- **Internal/private targets are blocked** for the spec `url` and `$ref`s alike
|
|
306
|
+
(loopback, RFC 1918, CGNAT, link-local/cloud-metadata `169.254/16`, multicast,
|
|
307
|
+
IPv6 ULA/link-local), and hostnames are **DNS-resolved** and re-checked.
|
|
308
|
+
- **`file://` is blocked** (prevents local file reads).
|
|
309
|
+
|
|
310
|
+
Configure via `loadOptions.refResolution` (applies to the spec URL **and** `$ref`s):
|
|
297
311
|
|
|
298
312
|
```typescript
|
|
299
|
-
//
|
|
313
|
+
// DEFAULT: external $refs disabled, redirects not followed, internal targets blocked.
|
|
314
|
+
// (No config needed; shown for clarity.)
|
|
300
315
|
OpenapiAdapter.init({
|
|
301
316
|
name: 'my-api',
|
|
302
317
|
url: 'https://api.example.com/openapi.json',
|
|
303
318
|
loadOptions: {
|
|
304
|
-
refResolution: {
|
|
305
|
-
allowedHosts: ['schemas.example.com'],
|
|
306
|
-
},
|
|
319
|
+
refResolution: { allowedProtocols: [] },
|
|
307
320
|
},
|
|
308
321
|
});
|
|
309
322
|
|
|
310
|
-
//
|
|
323
|
+
// Enable external $refs (public hosts only; internal targets stay blocked, DNS-validated)
|
|
311
324
|
OpenapiAdapter.init({
|
|
312
325
|
name: 'my-api',
|
|
313
326
|
url: 'https://api.example.com/openapi.json',
|
|
314
327
|
loadOptions: {
|
|
315
|
-
refResolution: {
|
|
316
|
-
allowedProtocols: ['http', 'https', 'file'],
|
|
317
|
-
},
|
|
328
|
+
refResolution: { allowedProtocols: ['http', 'https'] },
|
|
318
329
|
},
|
|
319
330
|
});
|
|
320
331
|
|
|
321
|
-
//
|
|
332
|
+
// Restrict the spec URL / $refs to specific hosts only
|
|
322
333
|
OpenapiAdapter.init({
|
|
323
|
-
name: '
|
|
324
|
-
url: '
|
|
334
|
+
name: 'my-api',
|
|
335
|
+
url: 'https://api.example.com/openapi.json',
|
|
325
336
|
loadOptions: {
|
|
326
|
-
refResolution: {
|
|
327
|
-
allowInternalIPs: true,
|
|
328
|
-
},
|
|
337
|
+
refResolution: { allowedProtocols: ['http', 'https'], allowedHosts: ['schemas.example.com'] },
|
|
329
338
|
},
|
|
330
339
|
});
|
|
331
340
|
|
|
332
|
-
//
|
|
341
|
+
// Local / internal development: allow loopback/private targets for the spec URL AND $refs
|
|
333
342
|
OpenapiAdapter.init({
|
|
334
|
-
name: '
|
|
335
|
-
url: '
|
|
343
|
+
name: 'local-api',
|
|
344
|
+
url: 'http://localhost:3000/openapi.json',
|
|
336
345
|
loadOptions: {
|
|
337
|
-
refResolution: {
|
|
338
|
-
allowedProtocols: [],
|
|
339
|
-
},
|
|
346
|
+
refResolution: { allowInternalIPs: true },
|
|
340
347
|
},
|
|
341
348
|
});
|
|
342
349
|
```
|
|
343
350
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
|
347
|
-
|
|
|
348
|
-
| `
|
|
349
|
-
| `
|
|
351
|
+
These apply to **both** the spec-URL fetch and external `$ref` resolution (`mcp-from-openapi` ≥ 2.5.0):
|
|
352
|
+
|
|
353
|
+
| Option | Type | Default (FrontMCP) | Description |
|
|
354
|
+
| ------------------ | ---------- | ------------------ | -------------------------------------------------------------------------------------------------- |
|
|
355
|
+
| `allowedProtocols` | `string[]` | `[]` | Protocols allowed for external `$ref` resolution. **FrontMCP defaults to `[]`** (external refs off); set `['http','https']` to enable |
|
|
356
|
+
| `allowedHosts` | `string[]` | `undefined` | When set, only the spec URL / `$ref` URLs to these hostnames are allowed |
|
|
357
|
+
| `blockedHosts` | `string[]` | `undefined` | Additional hostnames/IPs to block beyond the built-in internal-address list |
|
|
358
|
+
| `allowInternalIPs` | `boolean` | `false` | Allow loopback/private/internal targets for the spec URL **and** `$ref`s (skips ranges + DNS recheck). Trusted/local only |
|
|
359
|
+
|
|
360
|
+
> `followRedirects` (a `loadOptions` field, not `refResolution`) defaults to `false` in FrontMCP.
|
|
350
361
|
|
|
351
362
|
## Load Options
|
|
352
363
|
|
|
@@ -375,7 +386,7 @@ OpenapiAdapter.init({
|
|
|
375
386
|
| Auth configuration | `staticAuth: { jwt: process.env.API_TOKEN! }` | Hardcoding secrets: `staticAuth: { jwt: 'sk-xxx' }` | Always use environment variables |
|
|
376
387
|
| Spec source | Use `url` for hosted specs or `spec` for inline | Using both `url` and `spec` simultaneously | Only one source; `spec` takes precedence |
|
|
377
388
|
| Multiple APIs | Separate `OpenapiAdapter.init()` with unique `name` values | Same `name` for different adapters | Duplicate names cause tool collisions |
|
|
378
|
-
|
|
|
389
|
+
| Spec/$ref SSRF | Keep secure defaults (external refs off, redirects off, internal targets blocked) on `mcp-from-openapi` ≥ 2.5.0 | Setting `allowInternalIPs: true` in production; forwarding untrusted spec URLs without an `allowedHosts` allow-list | Defaults protect against SSRF (incl. DNS-name-to-internal) |
|
|
379
390
|
| Format resolution | `generateOptions: { resolveFormats: true }` | Writing manual patterns for standard formats | Built-in resolvers handle uuid, date-time, etc. |
|
|
380
391
|
|
|
381
392
|
## Verification Checklist
|
|
@@ -409,8 +420,9 @@ OpenapiAdapter.init({
|
|
|
409
420
|
| Authentication errors on API calls | Wrong auth config or missing credentials | Configure `staticAuth`, `securityResolver`, `authProviderMapper`, or `additionalHeaders`; verify env vars |
|
|
410
421
|
| Duplicate tool name error | Two adapters with the same `name` | Give each adapter a unique `name` |
|
|
411
422
|
| Stale tools after API update | Spec polling not configured | Add `polling: { intervalMs: 300000 }` |
|
|
412
|
-
| External $refs not resolving |
|
|
413
|
-
|
|
|
423
|
+
| External $refs not resolving | External refs are **disabled by default** in FrontMCP | Set `loadOptions.refResolution.allowedProtocols: ['http','https']` (add `allowedHosts` to restrict) |
|
|
424
|
+
| Spec URL / $ref to internal host blocked | Target is loopback/private, or a DNS name resolving to one (SSRF guard) | Use `refResolution.allowInternalIPs: true` only in trusted/local environments |
|
|
425
|
+
| Spec URL redirect not followed | `followRedirects` defaults to `false` | Set `loadOptions.followRedirects: true` (each hop is re-validated on `mcp-from-openapi` ≥ 2.5.0) |
|
|
414
426
|
| TypeScript error importing adapter | Wrong import path | Import from `@frontmcp/adapters` |
|
|
415
427
|
|
|
416
428
|
## Examples
|
|
@@ -420,7 +432,7 @@ OpenapiAdapter.init({
|
|
|
420
432
|
| [`basic-openapi-adapter`](../examples/openapi-adapter/basic-openapi-adapter.md) | Basic | Demonstrates converting an OpenAPI specification into MCP tools automatically using `OpenapiAdapter` with minimal configuration. |
|
|
421
433
|
| [`authenticated-adapter-with-polling`](../examples/openapi-adapter/authenticated-adapter-with-polling.md) | Intermediate | Demonstrates configuring authentication (API key and bearer token) and automatic spec polling for OpenAPI adapters. |
|
|
422
434
|
| [`format-resolution-and-custom-resolvers`](../examples/openapi-adapter/format-resolution-and-custom-resolvers.md) | Intermediate | Demonstrates using built-in and custom format resolvers to enrich tool input schemas with concrete constraints from OpenAPI format values. |
|
|
423
|
-
| [`ref-security-and-filtering`](../examples/openapi-adapter/ref-security-and-filtering.md) | Intermediate | Demonstrates configuring $ref resolution security to prevent SSRF attacks and filtering which API operations become MCP tools. |
|
|
435
|
+
| [`ref-security-and-filtering`](../examples/openapi-adapter/ref-security-and-filtering.md) | Intermediate | Demonstrates configuring $ref / spec-URL resolution security to prevent SSRF attacks (GHSA-65h7-9wrw-629c) and filtering which API operations become MCP tools. |
|
|
424
436
|
| [`multi-api-hub-with-inline-spec`](../examples/openapi-adapter/multi-api-hub-with-inline-spec.md) | Advanced | Demonstrates registering multiple OpenAPI adapters from different APIs in a single app, including one with an inline spec definition instead of a remote URL. |
|
|
425
437
|
|
|
426
438
|
> See all examples in [`examples/openapi-adapter/`](../examples/openapi-adapter/)
|
|
@@ -6,7 +6,7 @@ tags: [extensibility, audit, skills, tamper-evident, signature, chain]
|
|
|
6
6
|
|
|
7
7
|
# Skill Audit Log
|
|
8
8
|
|
|
9
|
-
The `@frontmcp/adapters/skills` module provides a tamper-evident, hash-chained audit log for skill action executions. Every authority pass / authority fail / HTTP success / HTTP failure phase emitted by
|
|
9
|
+
The `@frontmcp/adapters/skills` module provides a tamper-evident, hash-chained audit log for skill action executions. Every authority pass / authority fail / HTTP success / HTTP failure phase emitted by the skill-action executor (`run_workflow`'s `callTool`) is captured, signed, and chained so any later mutation breaks signature verification.
|
|
10
10
|
|
|
11
11
|
## Architecture
|
|
12
12
|
|
|
@@ -15,7 +15,7 @@ phase method assembles its payload internally and routes through a shared
|
|
|
15
15
|
chain pipeline:
|
|
16
16
|
|
|
17
17
|
```text
|
|
18
|
-
|
|
18
|
+
run_workflow → callTool(action)
|
|
19
19
|
├── writer.writeAuthorityPass(ctx) // authority-check-pass
|
|
20
20
|
├── writer.writeAuthorityFail(ctx, { reason }) // authority-check-fail
|
|
21
21
|
├── writer.writeHttpCallSuccess(ctx, { status, output }) // http-call-success
|
|
@@ -19,10 +19,13 @@ Shows how to use Cloudflare Durable Objects for stateful coordination alongside
|
|
|
19
19
|
|
|
20
20
|
```toml
|
|
21
21
|
# wrangler.toml — with Durable Objects and R2
|
|
22
|
-
# NOTE: `main`
|
|
22
|
+
# NOTE: `main`, `compatibility_date`, and `compatibility_flags` are written by
|
|
23
|
+
# `frontmcp build --target cloudflare`. Do not hand-edit. nodejs_compat is
|
|
24
|
+
# required — the worker entry needs Node builtins or it won't boot.
|
|
23
25
|
name = "stateful-mcp-worker"
|
|
24
26
|
main = "dist/cloudflare/index.js"
|
|
25
|
-
compatibility_date = "2024-
|
|
27
|
+
compatibility_date = "2024-09-23"
|
|
28
|
+
compatibility_flags = ["nodejs_compat"]
|
|
26
29
|
|
|
27
30
|
# KV for cache
|
|
28
31
|
[[kv_namespaces]]
|
package/catalog/frontmcp-production-readiness/examples/production-cloudflare/wrangler-config.md
CHANGED
|
@@ -46,7 +46,8 @@ Checklist for verifying the `wrangler.toml` produced by `frontmcp build --target
|
|
|
46
46
|
|
|
47
47
|
- [ ] No secrets in `wrangler.toml` — all set via `wrangler secret put <NAME>`
|
|
48
48
|
- [ ] `[vars]` block contains only non-sensitive config (region names, feature flags)
|
|
49
|
-
- [ ] `compatibility_date` set to a recent date and locked
|
|
49
|
+
- [ ] `compatibility_date` set to a recent date and locked (the build defaults to `2024-09-23`, the date that enables full `nodejs_compat`)
|
|
50
|
+
- [ ] `compatibility_flags` includes `nodejs_compat` — the build emits it automatically; without it the deployed Worker fails to boot (`require()`/`node:*` are unavailable)
|
|
50
51
|
|
|
51
52
|
## Deploy & observability
|
|
52
53
|
|
|
@@ -2143,13 +2143,13 @@
|
|
|
2143
2143
|
},
|
|
2144
2144
|
{
|
|
2145
2145
|
"name": "ref-security-and-filtering",
|
|
2146
|
-
"description": "Demonstrates configuring $ref resolution security to prevent SSRF attacks and filtering which API operations become MCP tools.",
|
|
2146
|
+
"description": "Demonstrates configuring $ref / spec-URL resolution security to prevent SSRF attacks (GHSA-65h7-9wrw-629c) and filtering which API operations become MCP tools.",
|
|
2147
2147
|
"level": "intermediate",
|
|
2148
2148
|
"tags": ["development", "openapi", "adapters", "security", "ssrf", "filtering"],
|
|
2149
2149
|
"features": [
|
|
2150
|
-
"
|
|
2151
|
-
"
|
|
2152
|
-
"Using `
|
|
2150
|
+
"Secure defaults: external `$ref` resolution off, spec-URL redirects not followed, internal/private targets blocked (DNS-resolved) — on `mcp-from-openapi` >= 2.5.0",
|
|
2151
|
+
"Opting back into external refs with `allowedProtocols`, and restricting the spec URL + `$ref`s with `allowedHosts`",
|
|
2152
|
+
"Using `allowInternalIPs` for trusted internal/local targets (governs the spec URL and `$ref`s)",
|
|
2153
2153
|
"Filtering operations with `includeOperations`, `excludeOperations`, and `filterFn`",
|
|
2154
2154
|
"Combining security hardening with operation filtering for a production-ready setup"
|
|
2155
2155
|
]
|