@frontmcp/skills 1.8.7 → 1.9.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/README.md +107 -155
- package/catalog/create-tool/examples/21-tool-with-availability-constraints.md +1 -1
- package/catalog/create-tool/references/availability.md +10 -10
- package/catalog/create-tool/references/ui-widgets.md +30 -8
- package/catalog/frontmcp-authorities/SKILL.md +5 -0
- package/catalog/frontmcp-channels/SKILL.md +17 -16
- package/catalog/frontmcp-channels/references/channel-sources.md +4 -4
- package/catalog/frontmcp-channels/references/channel-two-way.md +1 -1
- package/catalog/frontmcp-config/examples/configure-deployment-targets/distributed-ha-config.md +2 -0
- package/catalog/frontmcp-config/examples/configure-session/vercel-kv-session.md +1 -2
- package/catalog/frontmcp-config/examples/configure-transport/custom-protocol-flags.md +0 -1
- package/catalog/frontmcp-config/examples/configure-transport/distributed-sessions-redis.md +0 -1
- package/catalog/frontmcp-config/examples/configure-transport/stateless-serverless.md +2 -3
- package/catalog/frontmcp-config/examples/configure-transport-protocol-presets/stateless-api-serverless.md +2 -3
- package/catalog/frontmcp-config/references/configure-auth.md +1 -1
- package/catalog/frontmcp-config/references/configure-deployment-targets.md +54 -20
- package/catalog/frontmcp-config/references/configure-http.md +5 -2
- package/catalog/frontmcp-config/references/configure-throttle-guard-config.md +1 -1
- package/catalog/frontmcp-config/references/configure-throttle.md +4 -2
- package/catalog/frontmcp-config/references/configure-transport.md +4 -5
- package/catalog/frontmcp-deployment/SKILL.md +19 -19
- package/catalog/frontmcp-deployment/examples/deploy-to-node/docker-compose-with-redis.md +1 -1
- package/catalog/frontmcp-deployment/examples/deploy-to-node/pm2-with-nginx.md +1 -1
- package/catalog/frontmcp-deployment/references/build-for-browser.md +38 -9
- package/catalog/frontmcp-deployment/references/build-for-mcpb.md +15 -9
- package/catalog/frontmcp-deployment/references/build-for-sdk.md +2 -1
- package/catalog/frontmcp-deployment/references/deploy-to-cloudflare.md +3 -1
- package/catalog/frontmcp-deployment/references/deploy-to-lambda.md +2 -2
- package/catalog/frontmcp-deployment/references/deploy-to-node.md +11 -11
- package/catalog/frontmcp-deployment/references/mcp-client-integration.md +12 -7
- package/catalog/frontmcp-deployment/references/protocol-versions.md +7 -3
- package/catalog/frontmcp-development/examples/create-agent/nested-agents-with-swarm.md +50 -10
- package/catalog/frontmcp-development/examples/openapi-adapter/ref-security-and-filtering.md +13 -7
- package/catalog/frontmcp-development/references/create-adapter.md +14 -0
- package/catalog/frontmcp-development/references/create-agent.md +82 -49
- package/catalog/frontmcp-development/references/create-plugin-hooks.md +15 -5
- package/catalog/frontmcp-development/references/create-plugin.md +8 -4
- package/catalog/frontmcp-development/references/create-skill-with-tools.md +7 -2
- package/catalog/frontmcp-development/references/create-skill.md +4 -0
- package/catalog/frontmcp-development/references/decorators-guide.md +10 -11
- package/catalog/frontmcp-development/references/official-adapters.md +1 -1
- package/catalog/frontmcp-development/references/official-plugins.md +127 -24
- package/catalog/frontmcp-development/references/openapi-adapter.md +52 -2
- package/catalog/frontmcp-observability/references/metrics-endpoint.md +3 -1
- package/catalog/frontmcp-production-readiness/examples/distributed-ha/ha-kubernetes-3-replicas.md +12 -1
- package/catalog/frontmcp-production-readiness/references/common-checklist.md +2 -1
- package/catalog/frontmcp-production-readiness/references/distributed-ha.md +48 -28
- package/catalog/frontmcp-setup/examples/nx-workflow/build-test-affected.md +2 -2
- package/catalog/frontmcp-setup/examples/nx-workflow/multi-server-deployment.md +9 -5
- package/catalog/frontmcp-setup/examples/nx-workflow/scaffold-and-generate.md +1 -1
- package/catalog/frontmcp-setup/examples/project-structure-nx/nx-generator-scaffolding.md +1 -1
- package/catalog/frontmcp-setup/references/frontmcp-skills-usage.md +28 -15
- package/catalog/frontmcp-setup/references/multi-app-composition.md +11 -8
- package/catalog/frontmcp-setup/references/nx-workflow.md +53 -21
- package/catalog/frontmcp-setup/references/project-structure-nx.md +7 -4
- package/catalog/frontmcp-setup/references/setup-project.md +15 -0
- package/catalog/frontmcp-setup/references/setup-redis.md +12 -3
- package/catalog/frontmcp-setup/references/setup-sqlite.md +12 -8
- package/catalog/frontmcp-testing/SKILL.md +16 -12
- package/catalog/frontmcp-testing/references/setup-testing.md +14 -1
- package/catalog/skills-manifest.json +10 -8
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@ level: basic
|
|
|
5
5
|
description: 'Configure stateless transport for Vercel, Lambda, or Cloudflare deployments.'
|
|
6
6
|
tags: [config, vercel, lambda, cloudflare, session, transport]
|
|
7
7
|
features:
|
|
8
|
-
-
|
|
8
|
+
- 'Serving without sessions: whether the server keeps sessions follows `transport.protocol` (`sessionMode` has no effect)'
|
|
9
9
|
- "Using the `'stateless-api'` preset: no SSE, no streaming, pure request/response"
|
|
10
10
|
- 'Each request is standalone with no server-side state between invocations'
|
|
11
11
|
- 'Required for serverless targets (Vercel, Lambda, Cloudflare Workers)'
|
|
@@ -48,7 +48,6 @@ class CurrencyApp {}
|
|
|
48
48
|
info: { name: 'serverless-server', version: '1.0.0' },
|
|
49
49
|
apps: [CurrencyApp],
|
|
50
50
|
transport: {
|
|
51
|
-
sessionMode: 'stateless',
|
|
52
51
|
protocol: 'stateless-api',
|
|
53
52
|
},
|
|
54
53
|
})
|
|
@@ -57,7 +56,7 @@ class Server {}
|
|
|
57
56
|
|
|
58
57
|
## What This Demonstrates
|
|
59
58
|
|
|
60
|
-
-
|
|
59
|
+
- Serving without sessions: whether the server keeps sessions follows `transport.protocol` (`sessionMode` has no effect)
|
|
61
60
|
- Using the `'stateless-api'` preset: no SSE, no streaming, pure request/response
|
|
62
61
|
- Each request is standalone with no server-side state between invocations
|
|
63
62
|
- Required for serverless targets (Vercel, Lambda, Cloudflare Workers)
|
|
@@ -7,7 +7,7 @@ tags: [config, vercel, lambda, cloudflare, session, transport]
|
|
|
7
7
|
features:
|
|
8
8
|
- "The `'stateless-api'` preset disables SSE, streaming, and sessions entirely"
|
|
9
9
|
- 'Each request is standalone with no server-side state'
|
|
10
|
-
-
|
|
10
|
+
- 'No `sessionMode` needed: sessions follow the protocol preset'
|
|
11
11
|
- 'Required for Vercel, Lambda, Cloudflare Workers where persistent connections are not allowed'
|
|
12
12
|
---
|
|
13
13
|
|
|
@@ -46,7 +46,6 @@ class TranslateApp {}
|
|
|
46
46
|
info: { name: 'serverless-translate', version: '1.0.0' },
|
|
47
47
|
apps: [TranslateApp],
|
|
48
48
|
transport: {
|
|
49
|
-
sessionMode: 'stateless',
|
|
50
49
|
protocol: 'stateless-api',
|
|
51
50
|
},
|
|
52
51
|
})
|
|
@@ -59,7 +58,7 @@ class Server {}
|
|
|
59
58
|
|
|
60
59
|
- The `'stateless-api'` preset disables SSE, streaming, and sessions entirely
|
|
61
60
|
- Each request is standalone with no server-side state
|
|
62
|
-
-
|
|
61
|
+
- No `sessionMode` needed: sessions follow the protocol preset
|
|
63
62
|
- Required for Vercel, Lambda, Cloudflare Workers where persistent connections are not allowed
|
|
64
63
|
|
|
65
64
|
## Related
|
|
@@ -105,7 +105,7 @@ Key local-mode options:
|
|
|
105
105
|
|
|
106
106
|
- `tokenStorage` -- where authorization codes / refresh tokens / federated sessions persist. Defaults to `'memory'` (lost on restart). Use `{ sqlite: { path } }` for single-node persistence or `{ redis: { ... } }` for multi-instance. This is honored in local mode.
|
|
107
107
|
- `requireEmail` (default `true`) -- when `false`, the login callback mints a code without prompting for an email, deriving a stable `sub` from `anonymousSubject` (default `'local-operator'`). Use for single-operator setups (e.g. Claude Code).
|
|
108
|
-
- `consent` -- enables an **interactive tool-selection screen** during login AND **call-time enforcement**. When `consent.enabled` is `true`, `/oauth/callback` renders a consent screen (after authentication) listing the available tools; the user's checked tools are GET-submitted back to `/oauth/callback`, embedded in the token's `consent` claim, and enforced on every `tools/call` — a call to an unselected tool is rejected with `TOOL_NOT_CONSENTED` (JSON-RPC `-32003`). Honored flags: `groupByApp` (default `true`), `showDescriptions` (default `true`), `allowSelectAll` (default `true`), `requireSelection` (default `true` — rejects an empty submit), `customMessage`, `rememberConsent` (default `true`), `excludedTools` (never offered, always available), `defaultSelectedTools` (pre-checked). Federated logins show the screen after the last provider links. Tokens minted without consent (disabled, or via the test factory) carry no claim and stay all-tools-allowed. `rememberConsent` persists each user's per-client selection (keyed by `consent:{userSub}:{clientId}`, sharing the configured `tokenStorage` backend) and reuses it on the next login: the screen is skipped when no new tool appeared, or re-shown pre-filled when a NEW tool was added (a newly-added tool is never silently granted). Set `rememberConsent: false` to always re-show the screen.
|
|
108
|
+
- `consent` -- enables an **interactive tool-selection screen** during login AND **call-time enforcement**. When `consent.enabled` is `true`, `/oauth/callback` renders a consent screen (after authentication) listing the available tools; the user's checked tools are GET-submitted back to `/oauth/callback`, embedded in the token's `consent` claim, and enforced on every `tools/call` — a call to an unselected tool is rejected with `TOOL_NOT_CONSENTED` (JSON-RPC `-32003`). Honored flags: `groupByApp` (default `true`), `showDescriptions` (default `true`), `allowSelectAll` (default `true`), `requireSelection` (default `true` — rejects an empty submit), `customMessage`, `rememberConsent` (default `true`), `excludedTools` (never offered, always available), `defaultSelectedTools` (pre-checked). Federated logins show the screen after the last provider links. Tokens minted without consent (disabled, or via the test factory) carry no claim and stay all-tools-allowed. `rememberConsent` persists each user's per-client selection (keyed by `consent:{userSub}:{clientId}`, sharing the configured `tokenStorage` backend) and reuses it on the next login: the screen is skipped when no new tool appeared, or re-shown pre-filled when a NEW tool was added (a newly-added tool is never silently granted). Set `rememberConsent: false` to always re-show the screen. An agent is offered as its `invoke_<agent>` tool. The tools declared inside an `@Agent` and its nested agents are never offered, and the consent given to the agent covers them; the other agents and server tools an agent calls (`swarm`, `execution.inheritParentTools`) are offered and need their own consent.
|
|
109
109
|
- `login` -- customize the built-in login page: `title` / `subtitle` / `logoUri`, declarative `fields` (each `{ type: 'text'|'password'|'email'|'select'|'hidden'; label?; required?; placeholder?; options? }`), a full HTML `render(ctx)` override, and a `subject` strategy (`{ fromField, strategy: 'per-session'|'per-account' }`). Omitting `login` keeps the default email/name page.
|
|
110
110
|
- `authenticate(input, ctx)` -- custom verification run at the login callback **before** a token is minted. `input.fields` carries the submitted login fields (reserved OAuth params excluded); `ctx` is `{ get, fetch, logger, clientId?, clientName? }`. Return `{ ok: true, sub?, claims? }` to mint a token (custom `claims` are embedded in the JWT; reserved claims like `sub`/`iss`/`exp`/`scope` are stripped) or `{ ok: false, message, retryField? }` to re-render the login page with the error (no code issued). When set, the email requirement no longer applies.
|
|
111
111
|
- `providers` -- declarative upstream OAuth providers (GitHub, Slack, Jira, …) to orchestrate. When set, FrontMCP federates them at `/oauth/authorize`, stores each provider's tokens **encrypted server-side**, and exposes them to tools via `this.orchestration.getToken(id)`. See [Multi-provider orchestration](#multi-provider-orchestration-providers--federatedauth) below.
|
|
@@ -122,14 +122,44 @@ frontmcp build --target vercel
|
|
|
122
122
|
| `sdk` | Direct | Configurable | Library embedding |
|
|
123
123
|
| `mcpb` | stdio | SQLite, memory | `.mcpb` MCP bundles |
|
|
124
124
|
|
|
125
|
+
### How the settings reach the server
|
|
126
|
+
|
|
127
|
+
`frontmcp build` writes each target's `server` block (and `env`) into the artifact as environment
|
|
128
|
+
defaults the server reads at start-up — only where the variable is not already set (an operator's env var
|
|
129
|
+
wins), and an explicit `@FrontMcp()` value wins over both:
|
|
130
|
+
|
|
131
|
+
| Setting | Variable | Applies to |
|
|
132
|
+
| ------------------------------- | --------------------------------------------------------------- | --------------------------------------------------- |
|
|
133
|
+
| `server.http.port` | `PORT` | `node`, `distributed` (serverless: ignored, warns) |
|
|
134
|
+
| `server.http.socketPath` | `FRONTMCP_DAEMON_SOCKET` | `node`, `distributed` |
|
|
135
|
+
| `server.http.entryPath` | `FRONTMCP_HTTP_ENTRY_PATH` (wins over `transport.http.path`) | every server target |
|
|
136
|
+
| `server.http.cors` | `FRONTMCP_CORS_ORIGINS` (JSON list), `_CREDENTIALS`, `_MAX_AGE` | every server target |
|
|
137
|
+
| `server.cookies` | `FRONTMCP_AFFINITY_COOKIE`, `_DOMAIN`, `_SAMESITE` | `distributed` (sets the LB affinity cookie) |
|
|
138
|
+
| `server.csp` / `server.headers` | `FRONTMCP_CSP_*`, `FRONTMCP_HSTS`, … | every server target |
|
|
139
|
+
| `deployments[].env` | each key as is | every target except the `browser` / `sdk` libraries |
|
|
140
|
+
|
|
141
|
+
`node` / `cli` / `mcpb` bundles set them in a preamble when run as the program; `vercel` / `lambda` /
|
|
142
|
+
`cloudflare` / `distributed` in the generated setup module. Every artifact also sets
|
|
143
|
+
`globalThis.FRONTMCP_BUILD_TARGET` for `availableWhen: { target }` (first one to run wins).
|
|
144
|
+
|
|
125
145
|
### Server HTTP Options
|
|
126
146
|
|
|
127
|
-
| Field | Type | Default | Description
|
|
128
|
-
| -------------- | -------- | ------- |
|
|
129
|
-
| `port` | number | 3000 | Listen port
|
|
130
|
-
| `socketPath` | string | --- | Unix socket (overrides port)
|
|
131
|
-
| `entryPath` | string | `/` | Base path |
|
|
132
|
-
| `cors.origins` | string[] | --- | CORS allowed origins
|
|
147
|
+
| Field | Type | Default | Description |
|
|
148
|
+
| -------------- | -------- | ------- | --------------------------------------------------------------------------------- |
|
|
149
|
+
| `port` | number | 3000 | Listen port (node / distributed) |
|
|
150
|
+
| `socketPath` | string | --- | Unix socket (overrides port; node / distributed) |
|
|
151
|
+
| `entryPath` | string | `/` | Base path; wins over `transport.http.path` for this deployment |
|
|
152
|
+
| `cors.origins` | string[] | --- | CORS allowed origins (`['*']` = any); none = no CORS headers (the server default) |
|
|
153
|
+
|
|
154
|
+
`bodyLimit` / `urlencodedLimit` are not `frontmcp.config` fields — set them in `@FrontMcp({ http })`.
|
|
155
|
+
|
|
156
|
+
### Cookie Options (distributed LB affinity cookie)
|
|
157
|
+
|
|
158
|
+
| Field | Default | Description |
|
|
159
|
+
| ---------- | ----------------- | ------------------ |
|
|
160
|
+
| `affinity` | `__frontmcp_node` | Cookie name |
|
|
161
|
+
| `domain` | --- | `Domain` attribute |
|
|
162
|
+
| `sameSite` | `'Strict'` | `SameSite` |
|
|
133
163
|
|
|
134
164
|
### CSP Options
|
|
135
165
|
|
|
@@ -157,6 +187,8 @@ frontmcp build --target vercel
|
|
|
157
187
|
| `takeoverGracePeriodMs` | number | 5000 | Grace period before takeover |
|
|
158
188
|
| `redisKeyPrefix` | string | `mcp:ha:` | Redis key prefix |
|
|
159
189
|
|
|
190
|
+
The build writes these to `FRONTMCP_HA_HEARTBEAT_INTERVAL_MS`, `FRONTMCP_HA_HEARTBEAT_TTL_MS`, `FRONTMCP_HA_TAKEOVER_GRACE_MS` and `FRONTMCP_HA_KEY_PREFIX` in the generated setup file (only where the platform has not set them), which every pod reads at startup.
|
|
191
|
+
|
|
160
192
|
### Project-Defined CLI Commands (`cli.commands`)
|
|
161
193
|
|
|
162
194
|
Register project-specific verbs that ship alongside the built-in
|
|
@@ -217,6 +249,8 @@ Per-invocation precedence (issue #400):
|
|
|
217
249
|
3. Upward walk from `cwd` to the nearest ancestor containing a `frontmcp.config.*` (caps at 10 levels — monorepo nested apps work without `cd <repo-root>`).
|
|
218
250
|
4. Fallback: `package.json` (derives name, default node target).
|
|
219
251
|
|
|
252
|
+
When the upward walk finds the file in a **parent** folder, `frontmcp build` and `frontmcp dev` run from that folder (the project root): `entry`, `deployments[].outDir`, `tsconfig.json`, `package.json` and `.env` resolve there, so building from `src/` writes `dist/` next to the config. Paths passed as flags (`--entry`, `--out-dir`, `--icon`, `--merge-from`, `--log-file`) still resolve from the folder the command ran in. An explicit `--config` / `FRONTMCP_CONFIG` does not change the working folder.
|
|
253
|
+
|
|
220
254
|
Within a directory:
|
|
221
255
|
|
|
222
256
|
1. `frontmcp.config.ts`
|
|
@@ -239,15 +273,15 @@ There are no per-field `FRONTMCP_<NAME>` environment overrides. The only environ
|
|
|
239
273
|
|
|
240
274
|
The config is consumed by every `frontmcp` command, not just `build`:
|
|
241
275
|
|
|
242
|
-
| Command | Config fields consumed
|
|
243
|
-
| --------------------------------- |
|
|
244
|
-
| `build` | `name`, `version`, `entry`, `deployments`, `build`, `nodeVersion`
|
|
245
|
-
| `dev` | `entry`, `transport.http.port`, `env.shared` ⊕ `env.dev`
|
|
246
|
-
| `test` | `test.timeoutMs` / `test.runInBand` / `test.coverage` / `test.testMatch`, `env.shared` ⊕ `env.test`
|
|
247
|
-
| `inspector` | `transport.default`, `transport.http.port`, `transport.stdio`
|
|
248
|
-
| `pm start` / `socket` / `service` | `
|
|
249
|
-
| `skills install` / `export` | `skills.provider`, `skills.
|
|
250
|
-
| `eject-mcp-config <client>` | `clients.<client>`, `name`, `transport`, `env.ship`
|
|
276
|
+
| Command | Config fields consumed |
|
|
277
|
+
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
278
|
+
| `build` | `name`, `version`, `entry`, `deployments` (incl. `server`, `env`), `build`, `nodeVersion`, `transport.http.path` |
|
|
279
|
+
| `dev` | `entry`, `transport.http.port`, `env.shared` ⊕ `env.dev`, first deployment's `server.csp` / `headers` / `http.cors` |
|
|
280
|
+
| `test` | `test.timeoutMs` / `test.runInBand` / `test.coverage` / `test.testMatch`, `env.shared` ⊕ `env.test` |
|
|
281
|
+
| `inspector` | `transport.default`, `transport.http.port`, `transport.stdio` |
|
|
282
|
+
| `pm start` / `socket` / `service` | `env.shared` ⊕ `env.ship` (config found from the entry's folder upwards; the real env wins) |
|
|
283
|
+
| `skills install` / `export` | `skills.provider`, `skills.install` (else `skills.bundle`; `'none'` = nothing), `skills.exportTarget` — flags win |
|
|
284
|
+
| `eject-mcp-config <client>` | `clients.<client>`, `name`, `transport`, `env.shared` ⊕ `env.ship` (stdio `env`, under the client's own `env`) |
|
|
251
285
|
|
|
252
286
|
See `transport`, `env`, `clients`, `test`, `skills` field reference in [docs/frontmcp/deployment/frontmcp-config](https://docs.agentfront.dev/frontmcp/deployment/frontmcp-config).
|
|
253
287
|
|
|
@@ -255,11 +289,11 @@ See `transport`, `env`, `clients`, `test`, `skills` field reference in [docs/fro
|
|
|
255
289
|
|
|
256
290
|
`transport.http.path` is the mount path of the MCP endpoint for **every** build target, not only `frontmcp dev`:
|
|
257
291
|
|
|
258
|
-
| Target | How the path reaches the server
|
|
259
|
-
| --------------------------------- |
|
|
260
|
-
| `node` (and its SEA binary) | the
|
|
261
|
-
| `vercel`, `lambda`, `distributed` | the generated setup file assigns `process.env.FRONTMCP_HTTP_ENTRY_PATH` before the server loads
|
|
262
|
-
| `cloudflare` | the generated worker setup assigns it the same way
|
|
292
|
+
| Target | How the path reaches the server |
|
|
293
|
+
| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
294
|
+
| `node` (and its SEA binary) | the runner script exports `FRONTMCP_HTTP_ENTRY_PATH`, and the bundle sets the same default itself when run directly (`node dist/node/<name>.bundle.js`, the generated Dockerfile's `CMD`); a real env var wins |
|
|
295
|
+
| `vercel`, `lambda`, `distributed` | the generated setup file assigns `process.env.FRONTMCP_HTTP_ENTRY_PATH` before the server loads |
|
|
296
|
+
| `cloudflare` | the generated worker setup assigns it the same way |
|
|
263
297
|
|
|
264
298
|
A `@FrontMcp({ http: { entryPath } })` value still wins over the config. When the two differ, `frontmcp build` warns.
|
|
265
299
|
|
|
@@ -137,7 +137,8 @@ until you name the public host, and the bound NIC address then joins the list al
|
|
|
137
137
|
**Deployments behind a proxy need one line of config.** A routable bind (`0.0.0.0`, `::`, a specific
|
|
138
138
|
NIC) is reached under a hostname the process cannot know, so a derived list is not enforced there —
|
|
139
139
|
FrontMCP logs a warning and leaves host checking off rather than 403-ing a proxied deployment on a
|
|
140
|
-
patch upgrade
|
|
140
|
+
patch upgrade (the production security audit reports it as `DNS_REBINDING_NOT_ENFORCED`, and only says
|
|
141
|
+
`DNS_REBINDING_PROTECTED` once a list is actually checked). Name the public host to turn it on:
|
|
141
142
|
|
|
142
143
|
```typescript
|
|
143
144
|
http: {
|
|
@@ -163,7 +164,9 @@ there and needs no configuration. A rebound browser cannot reach a unix socket a
|
|
|
163
164
|
**explicit** `allowedHosts` / `allowedOrigins` still applies to a socket server, and because the
|
|
164
165
|
client's `Host` is a placeholder it will reject every request — leave it unset.
|
|
165
166
|
|
|
166
|
-
To turn it off entirely: `dnsRebindingProtection: { enabled: false }`.
|
|
167
|
+
To turn it off entirely: `dnsRebindingProtection: { enabled: false }`. It wins over any `allowedHosts` /
|
|
168
|
+
`allowedOrigins` (and `FRONTMCP_ALLOWED_HOSTS`): no header is checked while it is set, so remove it to
|
|
169
|
+
turn protection back on.
|
|
167
170
|
|
|
168
171
|
A request with **no** `Origin` header is allowed through — non-browser clients never send one, and a
|
|
169
172
|
rebound page always does. Rejecting the absent case breaks every CLI client and adds nothing.
|
|
@@ -69,7 +69,7 @@ interface IpFilterConfig {
|
|
|
69
69
|
|
|
70
70
|
## Storage Failure and Key Format
|
|
71
71
|
|
|
72
|
-
- **Fails closed.** When `storage` cannot be reached at startup, the server does not start: startup rejects with `GuardStorageUnavailableError` (code `GUARD_STORAGE_UNAVAILABLE`), whose message names `throttle.storage`. That is the default in production. If the backend goes away while the server runs, a limited call is refused with the same error
|
|
72
|
+
- **Fails closed.** When `storage` cannot be reached at startup, the server does not start: startup rejects with `GuardStorageUnavailableError` (code `GUARD_STORAGE_UNAVAILABLE`), whose message names `throttle.storage`. That is the default in production. If the backend goes away while the server runs, a limited call is refused with the same error, not `Internal FrontMCP error`: the `global` check answers HTTP 503 (`Retry-After: 1`, JSON-RPC `data.code: 'GUARD_STORAGE_UNAVAILABLE'`); a per-tool limit inside `tools/call` answers an `isError` result (HTTP 200, as MCP returns tool-level failures) with `_meta.code: 'GUARD_STORAGE_UNAVAILABLE'`. The client reads a generic "temporarily unavailable" message; the store address stays in the server log. Set `storage.fallback: 'memory'` to use per-instance counters instead; a mid-run outage then logs one warning and the backend is retried every 30 seconds. (The top-level `redis` and `transport.persistence` differ: they fall back to memory with an error log.)
|
|
73
73
|
- **Keys.** `<keyPrefix><entity>:<partition>:<kind>:...`, e.g. `mcp:guard:export_tickets:global:rl:1790722980000`. Before 1.8.6 the default prefix wrote `mcp:guard::export_tickets:...`; old and new instances do not read each other's counters, so limits briefly split during a rolling deploy. A custom `keyPrefix` without a trailing `:` keeps its keys.
|
|
74
74
|
|
|
75
75
|
## Partition Strategies
|
|
@@ -45,7 +45,7 @@ Protect your FrontMCP server with rate limiting, concurrency control, execution
|
|
|
45
45
|
partitionBy: 'global', // shared across all clients
|
|
46
46
|
},
|
|
47
47
|
|
|
48
|
-
// Global concurrency limit (a tool called with this.callTool() runs inside its caller's slot)
|
|
48
|
+
// Global concurrency limit (a tool called with this.callTool(), or by an agent during its run, runs inside its caller's slot)
|
|
49
49
|
globalConcurrency: {
|
|
50
50
|
maxConcurrent: 50,
|
|
51
51
|
partitionBy: 'global',
|
|
@@ -112,6 +112,8 @@ class ExpensiveQueryTool extends ToolContext {
|
|
|
112
112
|
}
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
+
A tool's own `rateLimit` and `concurrency` are enforced without a `throttle` option, as are those of an agent, of the tools declared inside an `@Agent` and of its nested agents. `throttle.enabled: false` turns every guard off, including these.
|
|
116
|
+
|
|
115
117
|
## `ipFilter` is enforced on every HTTP route
|
|
116
118
|
|
|
117
119
|
`allowList`, `denyList` and `defaultAction` are checked by the `checkIpFilter` stage that starts
|
|
@@ -246,7 +248,7 @@ throttle: {
|
|
|
246
248
|
|
|
247
249
|
`storage` is a `StorageConfig` from `@frontmcp/utils` -- `type` picks the backend, and its options go under the matching key (`redis: { config }` or `redis: { url }`, `vercelKv: { url, token }`, `upstash: { url, token }`). It is NOT the top-level `redis` shape: `{ provider: 'redis', host, port }` has no `type`, so it is auto-detected from `REDIS_URL` / `REDIS_HOST` and otherwise runs in memory.
|
|
248
250
|
|
|
249
|
-
**Rate limits fail closed.** If the store is unreachable at startup, the server does not start: startup rejects with `GuardStorageUnavailableError` (`throttle.storage (redis) is unavailable: ...`), the default in production. If the store goes away while the server is running, a limited call is refused with the same error rather than `Internal FrontMCP error
|
|
251
|
+
**Rate limits fail closed.** If the store is unreachable at startup, the server does not start: startup rejects with `GuardStorageUnavailableError` (`throttle.storage (redis) is unavailable: ...`), the default in production. If the store goes away while the server is running, a limited call is refused with the same error rather than `Internal FrontMCP error` (HTTP 503 from the `global` check; an `isError` result with `_meta.code: 'GUARD_STORAGE_UNAVAILABLE'` from a per-tool limit inside `tools/call`). To use per-instance counters instead (at startup and during a mid-run outage, going back to the store once it answers), opt in:
|
|
250
252
|
|
|
251
253
|
```typescript
|
|
252
254
|
storage: {
|
|
@@ -35,7 +35,6 @@ Configure how clients connect to your FrontMCP server — SSE, Streamable HTTP,
|
|
|
35
35
|
info: { name: 'my-server', version: '1.0.0' },
|
|
36
36
|
apps: [MyApp],
|
|
37
37
|
transport: {
|
|
38
|
-
sessionMode: 'stateful', // 'stateful' | 'stateless'
|
|
39
38
|
protocol: 'legacy', // preset or custom ProtocolConfig
|
|
40
39
|
persistence: {
|
|
41
40
|
// false to disable
|
|
@@ -157,7 +156,7 @@ curl -X POST http://localhost:3000/ -H 'Content-Type: application/json' -d '{"js
|
|
|
157
156
|
| Pattern | Correct | Incorrect | Why |
|
|
158
157
|
| -------------------- | -------------------------------------------------------------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------------------------------- |
|
|
159
158
|
| Choosing a preset | `protocol: 'modern'` | `protocol: { sse: true, streamable: true, legacy: false }` | Use a preset when it matches your needs; custom config is for overrides only |
|
|
160
|
-
| Serverless transport | `protocol: 'stateless-api'`
|
|
159
|
+
| Serverless transport | `protocol: 'stateless-api'` | `protocol: 'legacy'` on Lambda | Legacy preset creates sessions that serverless cannot maintain between invocations |
|
|
161
160
|
| Distributed sessions | `distributedMode: true` with Redis `persistence` configured | `distributedMode: true` without Redis | Distributed mode requires Redis; omitting it causes a startup error |
|
|
162
161
|
| Event store provider | `provider: 'redis'` for multi-instance, `provider: 'memory'` for single instance | `provider: 'memory'` behind a load balancer | In-memory event store is not shared across instances, breaking SSE resumability |
|
|
163
162
|
| Session TTL | Set `defaultTtlMs` to match your expected session duration | Omitting `defaultTtlMs` when using Redis persistence | Missing TTL can cause sessions to accumulate indefinitely in Redis |
|
|
@@ -167,12 +166,12 @@ curl -X POST http://localhost:3000/ -H 'Content-Type: application/json' -d '{"js
|
|
|
167
166
|
### Transport Protocol
|
|
168
167
|
|
|
169
168
|
- [ ] Correct preset is chosen for the deployment target (see Target-Specific Recommendations table)
|
|
170
|
-
- [ ] Custom protocol flags, if used,
|
|
169
|
+
- [ ] Custom protocol flags, if used, match whether the server should keep sessions (`stateless: true` serves without them)
|
|
171
170
|
- [ ] Legacy SSE is disabled when all clients support modern MCP protocol
|
|
172
171
|
|
|
173
172
|
### Session and Persistence
|
|
174
173
|
|
|
175
|
-
- [ ] `
|
|
174
|
+
- [ ] `protocol` is `'stateless-api'` for serverless deployments (sessions follow `protocol`; `sessionMode` has no effect and logs a startup warning when set to anything but `'stateful'`)
|
|
176
175
|
- [ ] `distributedMode` is enabled and Redis is configured for multi-instance deployments
|
|
177
176
|
- [ ] `defaultTtlMs` is set to a reasonable value when persistence is enabled
|
|
178
177
|
|
|
@@ -196,7 +195,7 @@ curl -X POST http://localhost:3000/ -H 'Content-Type: application/json' -d '{"js
|
|
|
196
195
|
| Server rejects SSE connections | SSE is disabled in the protocol config or preset | Switch to `'legacy'`, `'modern'`, or `'full'` preset, or set `sse: true` in custom config |
|
|
197
196
|
| `distributedMode` startup error | Redis persistence is not configured | Add a `persistence.redis` block with valid connection details |
|
|
198
197
|
| Clients lose state after reconnect | Event store is disabled or using in-memory provider behind a load balancer | Enable event store with `provider: 'redis'` for distributed deployments |
|
|
199
|
-
| Serverless function times out on SSE | Using a stateful preset on a serverless target | Switch to `'stateless-api'` preset
|
|
198
|
+
| Serverless function times out on SSE | Using a stateful preset on a serverless target | Switch to the `'stateless-api'` preset |
|
|
200
199
|
| Session not found after server restart | In-memory sessions do not survive restarts | Enable Redis persistence with `distributedMode: true` |
|
|
201
200
|
| Streamable HTTP returns 404 | Streamable HTTP is not enabled in the current preset | Use `'modern'`, `'legacy'`, or `'full'` preset, or set `streamable: true` in custom config |
|
|
202
201
|
|
|
@@ -73,25 +73,25 @@ Entry point for deploying and building FrontMCP servers. This skill helps you ch
|
|
|
73
73
|
|
|
74
74
|
Beyond `frontmcp build`, the CLI provides commands for the full deployment lifecycle:
|
|
75
75
|
|
|
76
|
-
| Command | Description
|
|
77
|
-
| ---------------------------- |
|
|
78
|
-
| `frontmcp build -t <target>` | Build for target: `node`, `vercel`, `lambda`, `cloudflare`, `cli`, `browser`, `sdk`
|
|
79
|
-
| `frontmcp build -t cli --js` | Build CLI as JS bundle (instead of native binary via SEA)
|
|
80
|
-
| `frontmcp build --no-clean` | Keep existing output instead of clearing the target's output directory first
|
|
81
|
-
| `frontmcp start <name>` | Start a named MCP server with supervisor (process management)
|
|
82
|
-
| `frontmcp stop <name>` | Stop managed server (`-f` for force kill)
|
|
83
|
-
| `frontmcp restart <name>` | Restart managed server
|
|
84
|
-
| `frontmcp status [name]` | Show process status (detail if name given, table if omitted)
|
|
85
|
-
| `frontmcp list` | List all managed processes
|
|
86
|
-
| `frontmcp logs <name>` | Tail log output (`-F` follow, `-n` lines)
|
|
87
|
-
| `frontmcp socket <entry>` | Start Unix socket daemon for local MCP server
|
|
88
|
-
| `frontmcp service <action>` | Install/uninstall systemd (Linux) or launchd (macOS) service
|
|
89
|
-
| `frontmcp install <source>` | Install MCP app from npm, local path, git, or a project's `dist/` / `dist/node` (installs external runtime packages; `frontmcp start <name>` then runs it from its install dir) |
|
|
90
|
-
| `frontmcp uninstall <name>` | Remove installed MCP app
|
|
91
|
-
| `frontmcp configure <name>` | Re-run setup questionnaire for installed app
|
|
92
|
-
| `frontmcp doctor` | Check Node.js/npm versions and tsconfig requirements
|
|
93
|
-
| `frontmcp inspector` | Launch MCP Inspector for debugging
|
|
94
|
-
| `frontmcp init` | Create or fix tsconfig.json for FrontMCP
|
|
76
|
+
| Command | Description |
|
|
77
|
+
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
78
|
+
| `frontmcp build -t <target>` | Build for target: `node`, `vercel`, `lambda`, `cloudflare`, `cli`, `browser`, `sdk` |
|
|
79
|
+
| `frontmcp build -t cli --js` | Build CLI as JS bundle (instead of native binary via SEA) |
|
|
80
|
+
| `frontmcp build --no-clean` | Keep existing output instead of clearing the target's output directory first |
|
|
81
|
+
| `frontmcp start <name>` | Start a named MCP server with supervisor (process management) |
|
|
82
|
+
| `frontmcp stop <name>` | Stop managed server (`-f` for force kill) |
|
|
83
|
+
| `frontmcp restart <name>` | Restart managed server |
|
|
84
|
+
| `frontmcp status [name]` | Show process status (detail if name given, table if omitted) |
|
|
85
|
+
| `frontmcp list` | List all managed processes |
|
|
86
|
+
| `frontmcp logs <name>` | Tail log output (`-F` follow, `-n` lines) |
|
|
87
|
+
| `frontmcp socket <entry>` | Start Unix socket daemon for local MCP server |
|
|
88
|
+
| `frontmcp service <action>` | Install/uninstall systemd (Linux) or launchd (macOS) service |
|
|
89
|
+
| `frontmcp install <source>` | Install MCP app from npm, local path, git, or a project's `dist/` / `dist/node` (`dist/node` wins over other targets; installs the external runtime packages plus `vectoriadb`/`tslib` and optional SDK peers the project declares, those in `optionalDependencies` as optional; `frontmcp start <name>` then runs it from its install dir) |
|
|
90
|
+
| `frontmcp uninstall <name>` | Remove installed MCP app |
|
|
91
|
+
| `frontmcp configure <name>` | Re-run setup questionnaire for installed app |
|
|
92
|
+
| `frontmcp doctor` | Check Node.js/npm versions and tsconfig requirements |
|
|
93
|
+
| `frontmcp inspector` | Launch MCP Inspector for debugging |
|
|
94
|
+
| `frontmcp init` | Create or fix tsconfig.json for FrontMCP (edits in place, keeps comments; never overwrites a file it cannot parse or that declares a required option twice) |
|
|
95
95
|
|
|
96
96
|
## Target Comparison
|
|
97
97
|
|
|
@@ -79,7 +79,7 @@ RUN yarn install --frozen-lockfile --production && yarn cache clean
|
|
|
79
79
|
EXPOSE 3000
|
|
80
80
|
HEALTHCHECK --interval=30s --timeout=5s --retries=3 --start-period=10s \
|
|
81
81
|
CMD wget -qO- http://localhost:3000/healthz || exit 1
|
|
82
|
-
CMD ["node", "dist/
|
|
82
|
+
CMD ["node", "dist/node/my-server.bundle.js"]
|
|
83
83
|
```
|
|
84
84
|
|
|
85
85
|
```bash
|
|
@@ -24,7 +24,7 @@ frontmcp build --target node
|
|
|
24
24
|
npm install -g pm2
|
|
25
25
|
|
|
26
26
|
# Start with cluster mode (one instance per CPU core)
|
|
27
|
-
pm2 start dist/
|
|
27
|
+
pm2 start dist/node/my-server.bundle.js --name frontmcp-server -i max
|
|
28
28
|
|
|
29
29
|
# Save the process list for auto-restart on reboot
|
|
30
30
|
pm2 save
|
|
@@ -119,6 +119,12 @@ function ToolUI() {
|
|
|
119
119
|
|
|
120
120
|
Things that trip people up with `@frontmcp/react`:
|
|
121
121
|
|
|
122
|
+
- A plain Vite app (7 or 8, build and dev server) bundles `@frontmcp/sdk` + `@frontmcp/react` from npm with no Node polyfills, no `process` define and no `express` alias: the bundler resolves the SDK's `browser` export condition to its browser build, for an `import` and for a CommonJS `require()` alike. Don't add `vite-plugin-node-polyfills` for FrontMCP.
|
|
123
|
+
- With Vite 7 (Rollup), don't `await create()` at the top level of the entry module — call it from a function or `.then()`. Rollup can put modules the SDK imports lazily in a chunk that imports the entry back, and the top-level `await` then waits on itself.
|
|
124
|
+
- Hook options may be written inline. `useStoreResource` / `useReduxResource` / `useValtioResource` and `useApiClient` register again only when the store name, the server, the set of selector / action names (not their order), or what an API operation declares changes; functions are read from the latest render.
|
|
125
|
+
- `useApiClient` sends the arguments an operation declares `in: 'query'` as the query string and `in: 'header'` as headers. `parseOpenApiSpec` fills `operation.parameters`; a hand-written operation lists them itself (`parameters: [{ name: 'limit', in: 'query' }]`), or its non-path, non-`body` arguments are not sent. Arrays and objects follow the parameter's OpenAPI `style` / `explode` (which `parseOpenApiSpec` keeps): a query array repeats the key and a query object sends each property as its own parameter by default (`spaceDelimited`, `pipeDelimited` and `deepObject` are honored); a header array or object is comma-separated.
|
|
126
|
+
- `ToolForm` shows an optional enum without a default as unset (an empty choice), and leaves it out of the arguments until the user picks a value.
|
|
127
|
+
|
|
122
128
|
- `useCallTool`'s `data` is the whole MCP `CallToolResult` (`content`, `structuredContent`, `isError`), not the tool's return value. Render `data.structuredContent ?? data.content`, never `String(data)`. A tool-side failure resolves with `data.isError === true`; `error` is only set when the call throws (for example when the client is not connected).
|
|
123
129
|
- `z` is re-exported from `@frontmcp/react`, but `zod` remains a required peer dependency of `@frontmcp/lazy-zod` and must be installed in the consuming project.
|
|
124
130
|
- `create({ resources })` takes `@Resource` classes or `resource(...)(handler)` / `resourceTemplate(...)(handler)` values. A plain `{ uri, name, read }` object is rejected with "Expected a class or a resource function".
|
|
@@ -127,6 +133,28 @@ Things that trip people up with `@frontmcp/react`:
|
|
|
127
133
|
|
|
128
134
|
For connecting to a remote MCP server (HTTP), create a server-bound `DirectMcpServer` via `connect()` from `@frontmcp/sdk` and pass that instance to the provider.
|
|
129
135
|
|
|
136
|
+
- `useDynamicTool` registers a **real server tool** (through `server.registerTool()`), so it runs through the server's flows (plugin hooks, authorities, `availableWhen`) and every client sees it. A name a server tool already has is refused and reported through the provider's `onError` (or `console.warn`) — it no longer shadows the server tool; registering it again (a remount) retries it. The provider's tool listing follows the server's `notifications/tools/list_changed`. On a server with several local apps (`FrontMcpInstance.createDirect({ apps: [...] })`) every dynamic tool must say which app it joins: once per server with `<FrontMcpProvider dynamicToolApps={{ default: 'browser' }}>` (keyed by server name; covers `useDynamicTool`, `mcpComponent`, store actions and `useApiClient`), or per tool with `useDynamicTool({ app })`. A `create()` server has one app, so neither is needed.
|
|
137
|
+
- Page code can add tools to the running server: `const unregister = await server.registerTool({ name, description, inputSchema, execute })`. The tool joins the server's app, so it is listed and run through the server's flows (plugin hooks, authorities, `availableWhen`). Arguments are not validated against `inputSchema` — validate in `execute`. `execute` runs outside the request's turn, so it may call the server back. A taken name rejects with `ToolNameConflictError`; names are 1–64 characters.
|
|
138
|
+
- `registerTool()` checks the definition before adding it: `inputSchema` must be an object schema (`type: 'object'`), and a non-string `title`/`description` or malformed `annotations`/`availableWhen` rejects with `EntryValidationError` instead of breaking `tools/list` for every tool. Registering on a disposed server (or one disposed before the registration completes) rejects with `InternalMcpError`.
|
|
139
|
+
|
|
140
|
+
## Exposing Tools to Browser Agents (WebMCP)
|
|
141
|
+
|
|
142
|
+
Install `@frontmcp/plugin-webmcp` to register the page server's tools with WebMCP (`document.modelContext`), the API Gemini in Chrome and other in-browser agents use:
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
import { WebMcpPlugin } from '@frontmcp/plugin-webmcp';
|
|
146
|
+
|
|
147
|
+
const server = await create({
|
|
148
|
+
info: { name: 'shop', version: '1.0.0' },
|
|
149
|
+
tools: [SearchProducts, AddToCart],
|
|
150
|
+
plugins: [WebMcpPlugin.init({ prefix: 'shop.' })],
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
- Every agent call runs `tools:call-tool` on the `'webmcp'` surface; use `availableWhen: { surface: ['webmcp'] }` for agent-only tools and `['mcp']` to keep a tool away from browser agents.
|
|
155
|
+
- Tools added later (`server.registerTool()`, `useDynamicTool`) are registered automatically; `server.dispose()` unregisters everything.
|
|
156
|
+
- WebMCP is in origin trial (Chrome/Edge 149–162). Develop with `chrome://flags/#enable-webmcp-testing` and inspect with DevTools → Application → WebMCP. Without `document.modelContext` the plugin does nothing; load a polyfill such as `@mcp-b/global` for other browsers.
|
|
157
|
+
|
|
130
158
|
## Browser vs Node vs SDK Target
|
|
131
159
|
|
|
132
160
|
| Aspect | `--target browser` | `--target node` | `--target sdk` |
|
|
@@ -179,15 +207,16 @@ ls dist/browser/
|
|
|
179
207
|
|
|
180
208
|
## Troubleshooting
|
|
181
209
|
|
|
182
|
-
| Problem
|
|
183
|
-
|
|
|
184
|
-
| `Module not found: fs`
|
|
185
|
-
| `crypto is not defined`
|
|
186
|
-
| CORS errors on tool calls
|
|
187
|
-
| Bundle too large
|
|
188
|
-
| `@frontmcp/utils` fs throws
|
|
189
|
-
| `AsyncContextOverlapError`
|
|
190
|
-
| A call never returns
|
|
210
|
+
| Problem | Cause | Solution |
|
|
211
|
+
| --------------------------------- | ---------------------------------------------- | ---------------------------------------------------------------- |
|
|
212
|
+
| `Module not found: fs` | Node.js module imported in browser bundle | Use a separate browser entry point that avoids Node-only imports |
|
|
213
|
+
| `crypto is not defined` | Using `node:crypto` instead of WebCrypto | Switch to `@frontmcp/utils` crypto functions |
|
|
214
|
+
| CORS errors on tool calls | MCP server missing CORS headers | Configure CORS middleware on the MCP server |
|
|
215
|
+
| Bundle too large | All server-side code included | Use `--target browser` and a dedicated client entry file |
|
|
216
|
+
| `@frontmcp/utils` fs throws | File system ops called in browser | Remove fs calls; use API endpoints or in-memory alternatives |
|
|
217
|
+
| `AsyncContextOverlapError` | Concurrent tool calls inside one request | Await the calls one after another (no `AsyncContext` in browser) |
|
|
218
|
+
| A call never returns | A tool calls its own server via a client | Call other tools through `this.scope` flows |
|
|
219
|
+
| `create()` never settles (Vite 7) | Top-level `await create()` in the entry module | Call `create()` from a function or `.then()` |
|
|
191
220
|
|
|
192
221
|
## Examples
|
|
193
222
|
|
|
@@ -65,6 +65,11 @@ frontmcp mcpb validate dist/mcpb/my-server-1.0.0.mcpb
|
|
|
65
65
|
- `@frontmcp/sdk` must be installed and reachable at the path used by the
|
|
66
66
|
build (never exclude it from bundling).
|
|
67
67
|
- For `--sea` builds, Node.js ≥ 24 is required (FrontMCP targets the current SEA API).
|
|
68
|
+
- The SEA binary is self-contained like `server/index.js` (FrontMCP runtime and
|
|
69
|
+
`reflect-metadata` inlined): an SEA binary resolves a bare `require()` against
|
|
70
|
+
Node's built-ins only. Smoke-test it from the extracted archive with
|
|
71
|
+
`FRONTMCP_STDIO=1 ./bin/<platform>/<name>`; `frontmcp mcpb validate` flags a
|
|
72
|
+
binary that still `require()`s a runtime package.
|
|
68
73
|
|
|
69
74
|
## What the Archive Contains
|
|
70
75
|
|
|
@@ -174,15 +179,16 @@ bundled JS, so a partial matrix is fine.
|
|
|
174
179
|
|
|
175
180
|
## Troubleshooting
|
|
176
181
|
|
|
177
|
-
| Problem
|
|
178
|
-
|
|
|
179
|
-
| `@frontmcp/sdk is required for schema extraction`
|
|
180
|
-
| `… requires "@frontmcp/sdk" but the archive has no node_modules`
|
|
181
|
-
|
|
|
182
|
-
|
|
|
183
|
-
| `
|
|
184
|
-
|
|
|
185
|
-
|
|
|
182
|
+
| Problem | Cause | Solution |
|
|
183
|
+
| --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
|
184
|
+
| `@frontmcp/sdk is required for schema extraction` | SDK missing or externalized from bundle | Ensure `@frontmcp/sdk` is installed |
|
|
185
|
+
| `… requires "@frontmcp/sdk" but the archive has no node_modules` | Server entry still `require()`s a runtime package | Rebuild with `frontmcp build --target mcpb` so runtime packages are bundled; `validate` reports archives that are not self-contained |
|
|
186
|
+
| `bin/… requires "reflect-metadata", which a single-executable binary cannot load` | SEA binary built with the runtime left external (dies with `No such built-in module`) | Rebuild with `frontmcp build --target mcpb --sea` |
|
|
187
|
+
| Archive > 100 MB | node_modules bundled or node runtime bloated | Tune `build.esbuild.external`, drop `--sea`, or disable `includeNodeModules` |
|
|
188
|
+
| `Unknown substitution variable` on validate | Typo in `mcp_config.args` / `env` | Only `__dirname`, `HOME`, `DESKTOP`, `DOCUMENTS`, `DOWNLOADS`, `pathSeparator`, and declared `user_config` keys are allowed |
|
|
189
|
+
| `entry_point is not present in archive` | Custom `--entry` flag or bundler moved the file | Re-run without the override, or update the config's `entry` |
|
|
190
|
+
| Two builds produce different SHA-256 | `--no-deterministic` set, or inputs embed a changing timestamp | Restore deterministic mode; scan your sources for live date/time values |
|
|
191
|
+
| `platform_overrides.{platform}.command` missing binary | `--merge-from` folders don't match MCPB platform keys | See the expected layout below |
|
|
186
192
|
|
|
187
193
|
Expected `--merge-from` layout (platform dirs must match MCPB platform keys):
|
|
188
194
|
|
|
@@ -98,7 +98,8 @@ const server = await create({
|
|
|
98
98
|
// Call tools directly
|
|
99
99
|
const result = await server.callTool('calculate', { a: 2, b: 2, operation: 'add' });
|
|
100
100
|
|
|
101
|
-
// List available tools
|
|
101
|
+
// List available tools: every page is read, so this is the whole list (no `nextCursor`).
|
|
102
|
+
// To page yourself: `listTools({ paginate: true })`, then `listTools({ cursor: page.nextCursor })`.
|
|
102
103
|
const { tools } = await server.listTools();
|
|
103
104
|
|
|
104
105
|
// Clean up
|
|
@@ -125,6 +125,8 @@ Copy the returned `id` into your `wrangler.toml`.
|
|
|
125
125
|
|
|
126
126
|
Cloudflare storage: `redis: { provider: 'vercel-kv' }` (the HTTP-based Upstash/Vercel KV client) is accepted by `--target cloudflare`; only TCP `redis` and `sqlite` configs are rejected at build time, since Workers cannot open raw sockets or load native modules.
|
|
127
127
|
|
|
128
|
+
To use it for sessions, install `@vercel/kv` in the project (the build bundles it into the worker), set `KV_REST_API_URL` / `KV_REST_API_TOKEN` as `[vars]` or secrets, and set `compatibility_date` to `2024-11-11` or later: the Upstash client sends `cache: 'no-store'` on every request, which Workers reject before that date (`The 'cache' field on 'RequestInitializerDict' is not implemented`). The build accepts the provider when it can read it, either from the evaluated config or written literally as `redis: { provider: 'vercel-kv' }` in `@FrontMcp({...})`; a `redis` whose provider it can read neither way is refused, and the error says so.
|
|
129
|
+
|
|
128
130
|
## Step 4: Configure the Server
|
|
129
131
|
|
|
130
132
|
```typescript
|
|
@@ -151,7 +153,7 @@ For session storage, use Upstash Redis (HTTP) via `redis: { provider: 'vercel-kv
|
|
|
151
153
|
|
|
152
154
|
### Secrets, vars and `process.env`
|
|
153
155
|
|
|
154
|
-
Worker bindings arrive as an argument to `fetch`, not as environment variables. The generated entry copies every **string** binding into `process.env` on the first request (existing values are never overwritten), so ordinary `process.env.MY_API_KEY` reads behave the same on Workers as under `frontmcp dev`. Non-string bindings (KV, D1, R2, Durable Objects) stay on `env`, which the entry forwards to the handler along with `ctx`. Inside a tool, resource, prompt or
|
|
156
|
+
Worker bindings arrive as an argument to `fetch`, not as environment variables. The generated entry copies every **string** binding into `process.env` on the first request (existing values are never overwritten), so ordinary `process.env.MY_API_KEY` reads behave the same on Workers as under `frontmcp dev`. Non-string bindings (KV, D1, R2, Durable Objects) stay on `env`, which the entry forwards to the handler along with `ctx`. Inside a tool, resource, prompt, agent or job, read them with `this.workerEnv` (e.g. `this.workerEnv?.MY_KV as KVNamespace | undefined`) — it is the request's Worker `env` (string bindings included), read-only, and `undefined` where the request carries no `env` (Node/Express, stdio, `create()`/`connect()` direct servers); a job run by `execute_job` reads the `env` of the request that ran it. The `process.env` copy is the generated entry's doing: the SDK never writes `process.env`, so a hand-written entry around `createWebFetchHandler` / `getServerlessHandlerAsync()` does not get it. `NODE_ENV = "production"` set in `wrangler.toml` `[vars]` is read live by the runtime context, so production mode takes effect even though the value reaches `process.env` only on the first request. Wrangler inlines the literal `process.env.NODE_ENV` at build time (`"development"` under `wrangler dev`, `"production"` under `wrangler deploy`, unless the shell sets `NODE_ENV`); FrontMCP reads the `[vars]` value past that, so `this.runtimeContext.env` / `this.isEnv('production')` follow `[vars]` under `wrangler dev` too. A `process.env.NODE_ENV` in your own tool code is still wrangler's constant — read `this.runtimeContext.env`, or run `NODE_ENV=production npx wrangler dev`.
|
|
155
157
|
|
|
156
158
|
A value read at module-eval time — inside the `@FrontMcp({...})` argument itself — is still `undefined`, because the copy happens on the first request. Read configuration inside `execute()` / `read()`, or rely on `nodejs_compat_populate_process_env` (emitted by default), which populates `process.env` before your module evaluates.
|
|
157
159
|
|
|
@@ -36,13 +36,13 @@ This skill walks you through deploying a FrontMCP server to AWS Lambda with API
|
|
|
36
36
|
- SAM CLI installed: `brew install aws-sam-cli` (macOS) or see AWS docs
|
|
37
37
|
- Node.js 24 or later
|
|
38
38
|
- A FrontMCP project ready to build
|
|
39
|
-
- **Peer dependency:** `@codegenie/serverless-express` installed in your project. The Lambda adapter
|
|
39
|
+
- **Peer dependency:** `@codegenie/serverless-express` installed in your project. The Lambda adapter validates its presence at build time and bundles it into `handler.cjs`, so `dist/lambda/` deploys on its own (`CodeUri: dist/lambda/`, no `node_modules` or Layer needed):
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
42
|
npm install @codegenie/serverless-express
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
If it isn't installed, `frontmcp build --target lambda` fails with a clear error before producing artifacts.
|
|
45
|
+
If it isn't installed, `frontmcp build --target lambda` fails with a clear error before producing artifacts. `frontmcp create --target lambda` adds it to `dependencies` for you.
|
|
46
46
|
|
|
47
47
|
## Step 1: Build for Lambda
|
|
48
48
|
|