@frontmcp/skills 1.8.7 → 1.9.1-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +107 -155
  2. package/catalog/create-tool/examples/21-tool-with-availability-constraints.md +1 -1
  3. package/catalog/create-tool/references/availability.md +10 -10
  4. package/catalog/create-tool/references/ui-widgets.md +30 -8
  5. package/catalog/frontmcp-authorities/SKILL.md +5 -0
  6. package/catalog/frontmcp-channels/SKILL.md +17 -16
  7. package/catalog/frontmcp-channels/references/channel-sources.md +4 -4
  8. package/catalog/frontmcp-channels/references/channel-two-way.md +1 -1
  9. package/catalog/frontmcp-config/examples/configure-deployment-targets/distributed-ha-config.md +2 -0
  10. package/catalog/frontmcp-config/examples/configure-session/vercel-kv-session.md +1 -2
  11. package/catalog/frontmcp-config/examples/configure-transport/custom-protocol-flags.md +0 -1
  12. package/catalog/frontmcp-config/examples/configure-transport/distributed-sessions-redis.md +0 -1
  13. package/catalog/frontmcp-config/examples/configure-transport/stateless-serverless.md +2 -3
  14. package/catalog/frontmcp-config/examples/configure-transport-protocol-presets/stateless-api-serverless.md +2 -3
  15. package/catalog/frontmcp-config/references/configure-auth.md +1 -1
  16. package/catalog/frontmcp-config/references/configure-deployment-targets.md +54 -20
  17. package/catalog/frontmcp-config/references/configure-http.md +5 -2
  18. package/catalog/frontmcp-config/references/configure-throttle-guard-config.md +1 -1
  19. package/catalog/frontmcp-config/references/configure-throttle.md +4 -2
  20. package/catalog/frontmcp-config/references/configure-transport.md +4 -5
  21. package/catalog/frontmcp-deployment/SKILL.md +19 -19
  22. package/catalog/frontmcp-deployment/examples/deploy-to-node/docker-compose-with-redis.md +1 -1
  23. package/catalog/frontmcp-deployment/examples/deploy-to-node/pm2-with-nginx.md +1 -1
  24. package/catalog/frontmcp-deployment/references/build-for-browser.md +38 -9
  25. package/catalog/frontmcp-deployment/references/build-for-mcpb.md +15 -9
  26. package/catalog/frontmcp-deployment/references/build-for-sdk.md +2 -1
  27. package/catalog/frontmcp-deployment/references/deploy-to-cloudflare.md +3 -1
  28. package/catalog/frontmcp-deployment/references/deploy-to-lambda.md +2 -2
  29. package/catalog/frontmcp-deployment/references/deploy-to-node.md +11 -11
  30. package/catalog/frontmcp-deployment/references/mcp-client-integration.md +12 -7
  31. package/catalog/frontmcp-deployment/references/protocol-versions.md +7 -3
  32. package/catalog/frontmcp-development/examples/create-agent/nested-agents-with-swarm.md +50 -10
  33. package/catalog/frontmcp-development/examples/openapi-adapter/ref-security-and-filtering.md +13 -7
  34. package/catalog/frontmcp-development/references/create-adapter.md +14 -0
  35. package/catalog/frontmcp-development/references/create-agent.md +82 -49
  36. package/catalog/frontmcp-development/references/create-plugin-hooks.md +15 -5
  37. package/catalog/frontmcp-development/references/create-plugin.md +8 -4
  38. package/catalog/frontmcp-development/references/create-skill-with-tools.md +7 -2
  39. package/catalog/frontmcp-development/references/create-skill.md +4 -0
  40. package/catalog/frontmcp-development/references/decorators-guide.md +10 -11
  41. package/catalog/frontmcp-development/references/official-adapters.md +1 -1
  42. package/catalog/frontmcp-development/references/official-plugins.md +127 -24
  43. package/catalog/frontmcp-development/references/openapi-adapter.md +52 -2
  44. package/catalog/frontmcp-observability/references/metrics-endpoint.md +3 -1
  45. package/catalog/frontmcp-production-readiness/examples/distributed-ha/ha-kubernetes-3-replicas.md +12 -1
  46. package/catalog/frontmcp-production-readiness/references/common-checklist.md +2 -1
  47. package/catalog/frontmcp-production-readiness/references/distributed-ha.md +48 -28
  48. package/catalog/frontmcp-setup/examples/multi-app-composition/local-apps-with-shared-tools.md +10 -6
  49. package/catalog/frontmcp-setup/examples/nx-workflow/build-test-affected.md +2 -2
  50. package/catalog/frontmcp-setup/examples/nx-workflow/multi-server-deployment.md +9 -5
  51. package/catalog/frontmcp-setup/examples/nx-workflow/scaffold-and-generate.md +1 -1
  52. package/catalog/frontmcp-setup/examples/project-structure-nx/nx-generator-scaffolding.md +1 -1
  53. package/catalog/frontmcp-setup/references/frontmcp-skills-usage.md +28 -15
  54. package/catalog/frontmcp-setup/references/multi-app-composition.md +25 -16
  55. package/catalog/frontmcp-setup/references/nx-workflow.md +53 -21
  56. package/catalog/frontmcp-setup/references/project-structure-nx.md +7 -4
  57. package/catalog/frontmcp-setup/references/setup-project.md +15 -0
  58. package/catalog/frontmcp-setup/references/setup-redis.md +12 -3
  59. package/catalog/frontmcp-setup/references/setup-sqlite.md +12 -8
  60. package/catalog/frontmcp-testing/SKILL.md +16 -12
  61. package/catalog/frontmcp-testing/references/setup-testing.md +14 -1
  62. package/catalog/skills-manifest.json +13 -11
  63. 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
- - "Using `sessionMode: 'stateless'` to disable session management"
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
- - Using `sessionMode: 'stateless'` to disable session management
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
- - "Pair with `sessionMode: 'stateless'` for serverless execution"
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
- - Pair with `sessionMode: 'stateless'` for serverless execution
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` | `name`, `entry`, `transport.http.port`, `transport.http.socketPath`, `env.shared` ⊕ `env.ship` |
249
- | `skills install` / `export` | `skills.provider`, `skills.bundle`, `skills.install`, `skills.exportTarget` |
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 generated runner script exports `FRONTMCP_HTTP_ENTRY_PATH` (default only; a real env var wins) |
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. Name the public host to turn it on:
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 (a readable 503, not `Internal FrontMCP error`). 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.)
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`. To use per-instance counters instead (at startup and during a mid-run outage, going back to the store once it answers), opt in:
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'` with `sessionMode: 'stateless'` | `protocol: 'legacy'` on Lambda | Legacy preset creates sessions that serverless cannot maintain between invocations |
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, do not conflict with the selected `sessionMode`
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
- - [ ] `sessionMode` is `'stateless'` for serverless deployments
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 and set `sessionMode: 'stateless'` |
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/main.js"]
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/main.js --name frontmcp-server -i max
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 | Cause | Solution |
183
- | --------------------------- | ----------------------------------------- | ---------------------------------------------------------------- |
184
- | `Module not found: fs` | Node.js module imported in browser bundle | Use a separate browser entry point that avoids Node-only imports |
185
- | `crypto is not defined` | Using `node:crypto` instead of WebCrypto | Switch to `@frontmcp/utils` crypto functions |
186
- | CORS errors on tool calls | MCP server missing CORS headers | Configure CORS middleware on the MCP server |
187
- | Bundle too large | All server-side code included | Use `--target browser` and a dedicated client entry file |
188
- | `@frontmcp/utils` fs throws | File system ops called in browser | Remove fs calls; use API endpoints or in-memory alternatives |
189
- | `AsyncContextOverlapError` | Concurrent tool calls inside one request | Await the calls one after another (no `AsyncContext` in browser) |
190
- | A call never returns | A tool calls its own server via a client | Call other tools through `this.scope` flows |
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 | Cause | Solution |
178
- | ---------------------------------------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
179
- | `@frontmcp/sdk is required for schema extraction` | SDK missing or externalized from bundle | Ensure `@frontmcp/sdk` is installed |
180
- | `… 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 |
181
- | Archive > 100 MB | node_modules bundled or node runtime bloated | Tune `build.esbuild.external`, drop `--sea`, or disable `includeNodeModules` |
182
- | `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 |
183
- | `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` |
184
- | 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 |
185
- | `platform_overrides.{platform}.command` missing binary | `--merge-from` folders don't match MCPB platform keys | See the expected layout below |
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 agent, read them with `this.workerEnv` (e.g. `this.workerEnv?.MY_KV as KVNamespace | undefined`) — it is the request's Worker `env`, read-only, and `undefined` outside a Worker request. `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.
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 externalizes it at bundle time and validates its presence at build time:
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