@frontmcp/skills 1.8.6 → 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.
Files changed (77) hide show
  1. package/README.md +107 -155
  2. package/catalog/create-tool/SKILL.md +24 -24
  3. package/catalog/create-tool/examples/21-tool-with-availability-constraints.md +1 -1
  4. package/catalog/create-tool/examples/22-tool-with-ui-html-template.md +0 -1
  5. package/catalog/create-tool/examples/23-tool-with-ui-filesource-tsx.md +1 -3
  6. package/catalog/create-tool/examples/24-tool-with-ui-csp-and-bridge.md +4 -6
  7. package/catalog/create-tool/references/availability.md +10 -10
  8. package/catalog/create-tool/references/decorator-options.md +1 -1
  9. package/catalog/create-tool/references/ui-widgets.md +91 -42
  10. package/catalog/frontmcp-authorities/SKILL.md +5 -0
  11. package/catalog/frontmcp-channels/SKILL.md +17 -16
  12. package/catalog/frontmcp-channels/references/channel-sources.md +4 -4
  13. package/catalog/frontmcp-channels/references/channel-two-way.md +1 -1
  14. package/catalog/frontmcp-config/examples/configure-deployment-targets/distributed-ha-config.md +2 -0
  15. package/catalog/frontmcp-config/examples/configure-session/vercel-kv-session.md +1 -2
  16. package/catalog/frontmcp-config/examples/configure-throttle/distributed-redis-throttle.md +3 -3
  17. package/catalog/frontmcp-config/examples/configure-transport/custom-protocol-flags.md +0 -1
  18. package/catalog/frontmcp-config/examples/configure-transport/distributed-sessions-redis.md +0 -1
  19. package/catalog/frontmcp-config/examples/configure-transport/stateless-serverless.md +2 -3
  20. package/catalog/frontmcp-config/examples/configure-transport-protocol-presets/stateless-api-serverless.md +2 -3
  21. package/catalog/frontmcp-config/references/configure-auth.md +1 -1
  22. package/catalog/frontmcp-config/references/configure-deployment-targets.md +68 -16
  23. package/catalog/frontmcp-config/references/configure-http.md +11 -6
  24. package/catalog/frontmcp-config/references/configure-security-headers.md +18 -13
  25. package/catalog/frontmcp-config/references/configure-throttle-guard-config.md +4 -4
  26. package/catalog/frontmcp-config/references/configure-throttle.md +4 -2
  27. package/catalog/frontmcp-config/references/configure-transport.md +4 -5
  28. package/catalog/frontmcp-deployment/SKILL.md +19 -19
  29. package/catalog/frontmcp-deployment/examples/build-for-browser/react-provider-setup.md +5 -3
  30. package/catalog/frontmcp-deployment/examples/deploy-to-node/docker-compose-with-redis.md +1 -1
  31. package/catalog/frontmcp-deployment/examples/deploy-to-node/pm2-with-nginx.md +1 -1
  32. package/catalog/frontmcp-deployment/examples/deploy-to-vercel/vercel-with-kv.md +2 -0
  33. package/catalog/frontmcp-deployment/references/build-for-browser.md +46 -9
  34. package/catalog/frontmcp-deployment/references/build-for-mcpb.md +15 -8
  35. package/catalog/frontmcp-deployment/references/build-for-sdk.md +11 -10
  36. package/catalog/frontmcp-deployment/references/deploy-to-cloudflare.md +5 -1
  37. package/catalog/frontmcp-deployment/references/deploy-to-lambda.md +11 -10
  38. package/catalog/frontmcp-deployment/references/deploy-to-node.md +11 -11
  39. package/catalog/frontmcp-deployment/references/deploy-to-vercel.md +15 -7
  40. package/catalog/frontmcp-deployment/references/mcp-client-integration.md +12 -7
  41. package/catalog/frontmcp-deployment/references/protocol-versions.md +7 -3
  42. package/catalog/frontmcp-development/examples/create-agent/nested-agents-with-swarm.md +50 -10
  43. package/catalog/frontmcp-development/examples/openapi-adapter/ref-security-and-filtering.md +13 -7
  44. package/catalog/frontmcp-development/references/create-adapter.md +14 -0
  45. package/catalog/frontmcp-development/references/create-agent.md +82 -48
  46. package/catalog/frontmcp-development/references/create-plugin-hooks.md +15 -5
  47. package/catalog/frontmcp-development/references/create-plugin.md +23 -2
  48. package/catalog/frontmcp-development/references/create-skill-with-tools.md +7 -2
  49. package/catalog/frontmcp-development/references/create-skill.md +4 -0
  50. package/catalog/frontmcp-development/references/decorators-guide.md +10 -11
  51. package/catalog/frontmcp-development/references/official-adapters.md +1 -1
  52. package/catalog/frontmcp-development/references/official-plugins.md +138 -28
  53. package/catalog/frontmcp-development/references/openapi-adapter.md +52 -2
  54. package/catalog/frontmcp-guides/references/example-task-manager.md +2 -2
  55. package/catalog/frontmcp-guides/references/example-weather-api.md +2 -2
  56. package/catalog/frontmcp-observability/references/metrics-endpoint.md +3 -1
  57. package/catalog/frontmcp-production-readiness/examples/distributed-ha/ha-kubernetes-3-replicas.md +13 -1
  58. package/catalog/frontmcp-production-readiness/examples/production-node-sdk/package-json-config.md +2 -2
  59. package/catalog/frontmcp-production-readiness/references/common-checklist.md +3 -2
  60. package/catalog/frontmcp-production-readiness/references/distributed-ha.md +54 -24
  61. package/catalog/frontmcp-production-readiness/references/health-readiness-endpoints.md +3 -1
  62. package/catalog/frontmcp-setup/examples/nx-workflow/build-test-affected.md +2 -2
  63. package/catalog/frontmcp-setup/examples/nx-workflow/multi-server-deployment.md +9 -5
  64. package/catalog/frontmcp-setup/examples/nx-workflow/scaffold-and-generate.md +1 -1
  65. package/catalog/frontmcp-setup/examples/project-structure-nx/nx-generator-scaffolding.md +2 -2
  66. package/catalog/frontmcp-setup/references/frontmcp-skills-usage.md +28 -15
  67. package/catalog/frontmcp-setup/references/multi-app-composition.md +11 -8
  68. package/catalog/frontmcp-setup/references/nx-workflow.md +68 -28
  69. package/catalog/frontmcp-setup/references/project-structure-nx.md +7 -4
  70. package/catalog/frontmcp-setup/references/setup-project.md +15 -0
  71. package/catalog/frontmcp-setup/references/setup-redis.md +12 -3
  72. package/catalog/frontmcp-setup/references/setup-sqlite.md +21 -14
  73. package/catalog/frontmcp-testing/SKILL.md +28 -23
  74. package/catalog/frontmcp-testing/references/setup-testing.md +39 -2
  75. package/catalog/frontmcp-testing/references/test-auth.md +8 -0
  76. package/catalog/skills-manifest.json +14 -12
  77. package/package.json +1 -1
@@ -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`
@@ -230,25 +264,43 @@ Within a directory:
230
264
  For every CLI option that's also expressible in the config:
231
265
 
232
266
  ```
233
- explicit CLI flag > FRONTMCP_<NAME> env var > frontmcp.config field > built-in default
267
+ explicit CLI flag > frontmcp.config field > built-in default
234
268
  ```
235
269
 
270
+ There are no per-field `FRONTMCP_<NAME>` environment overrides. The only environment variable the CLI reads for configuration is `FRONTMCP_CONFIG`, which selects the config file (an explicit `--config <path>` flag wins over it).
271
+
236
272
  ## Per-command consumption (issue #400)
237
273
 
238
274
  The config is consumed by every `frontmcp` command, not just `build`:
239
275
 
240
- | Command | Config fields consumed |
241
- | --------------------------------- | --------------------------------------------------------------------------------------------------- |
242
- | `build` | `name`, `version`, `entry`, `deployments`, `build`, `nodeVersion` |
243
- | `dev` | `entry`, `transport.http.port`, `env.shared` ⊕ `env.dev` |
244
- | `test` | `test.timeoutMs` / `test.runInBand` / `test.coverage` / `test.testMatch`, `env.shared` ⊕ `env.test` |
245
- | `inspector` | `transport.default`, `transport.http.port`, `transport.stdio` |
246
- | `pm start` / `socket` / `service` | `name`, `entry`, `transport.http.port`, `transport.http.socketPath`, `env.shared` ⊕ `env.ship` |
247
- | `skills install` / `export` | `skills.provider`, `skills.bundle`, `skills.install`, `skills.exportTarget` |
248
- | `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`) |
249
285
 
250
286
  See `transport`, `env`, `clients`, `test`, `skills` field reference in [docs/frontmcp/deployment/frontmcp-config](https://docs.agentfront.dev/frontmcp/deployment/frontmcp-config).
251
287
 
288
+ ## `transport.http.path` for every build target
289
+
290
+ `transport.http.path` is the mount path of the MCP endpoint for **every** build target, not only `frontmcp dev`:
291
+
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 |
297
+
298
+ A `@FrontMcp({ http: { entryPath } })` value still wins over the config. When the two differ, `frontmcp build` warns.
299
+
300
+ ## `eject-mcp-config --out` merges
301
+
302
+ `--out` merges into an existing client config instead of replacing it: the parent folder is created when missing, other top-level keys and other `mcpServers` entries are kept, and only this server's entry is replaced. A file that is not valid JSON (or not a JSON object) is refused and left untouched. `--dry-run` prints the merged result and writes nothing.
303
+
252
304
  ## JSON Schema for IDE Support
253
305
 
254
306
  For JSON configs, add `$schema` for autocomplete:
@@ -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.
@@ -260,10 +263,12 @@ http: {
260
263
  }
261
264
  ```
262
265
 
263
- | Option | Type | Default | Notes |
264
- | ----------------- | ------------------ | ------------------------- | ---------------------------------------------------------------------- |
265
- | `bodyLimit` | `number \| string` | `'4mb'` | Bytes (number) or body-parser string (`'4mb'`, `'500kb'`, `'2gb'`, …). |
266
- | `urlencodedLimit` | `number \| string` | falls back to `bodyLimit` | Independent override for `application/x-www-form-urlencoded` bodies. |
266
+ | Option | Type | Default | Notes |
267
+ | ----------------- | ------------------ | ------------------------- | ---------------------------------------------------------------------------------------- |
268
+ | `bodyLimit` | `number \| string` | `'4mb'` | Bytes (number) or body-parser string (`'4mb'`, `'500kb'`, `'2gb'`, …). |
269
+ | `urlencodedLimit` | `number \| string` | falls back to `bodyLimit` | Independent override for `application/x-www-form-urlencoded` bodies (Express host only). |
270
+
271
+ The fetch handler used on Cloudflare Workers, Vercel Edge and Deno enforces `bodyLimit` too: it answers 413 for an oversized `Content-Length` without reading the body and stops reading a chunked body at the limit.
267
272
 
268
273
  Requests exceeding the configured limit receive a structured JSON-RPC 413
269
274
  response — never an Express HTML error page:
@@ -5,7 +5,7 @@ description: Configure CSP, HSTS, X-Frame-Options, and X-Content-Type-Options vi
5
5
 
6
6
  # Configure Security Headers
7
7
 
8
- Set Content Security Policy (CSP), HSTS, and other security headers on every HTTP response. Configure them in `frontmcp.config` per deployment target — the build adapter injects them as environment variables that the built-in middleware reads at runtime.
8
+ Set Content Security Policy (CSP), HSTS, and other security headers on every HTTP response. Configure them in `frontmcp.config` per deployment target — `frontmcp dev` and the `cloudflare`, `vercel`, `lambda` and `distributed` builds pass them to the server as `FRONTMCP_*` environment variables (set only when the platform has not already defined them). The `node` target has no setup file: set the variables where the server runs or use `@FrontMcp({ http: { securityHeaders } })`. `X-Content-Type-Options: nosniff` and `X-Frame-Options: DENY` are on by default and `X-Powered-By` is never sent; the Express host and the Workers/Vercel Edge fetch handler share one resolver.
9
9
 
10
10
  ## When to Use This Skill
11
11
 
@@ -109,7 +109,9 @@ curl -I http://localhost:3000/healthz
109
109
  | `frameOptions` | `string \| false` | `DENY` | `X-Frame-Options` |
110
110
  | `custom` | `Record<string,string>` | --- | Any custom headers |
111
111
 
112
- Set any of the first three fields to `false` to explicitly disable that header.
112
+ Set any of the first three fields to `false` to omit that header (env var value: `off`).
113
+
114
+ The same settings work on the decorator: `@FrontMcp({ http: { securityHeaders: { hsts, contentTypeOptions, frameOptions, csp, custom } } })`. Precedence: decorator, then `FRONTMCP_*` variables, then defaults.
113
115
 
114
116
  ### Value-Less CSP Directives
115
117
 
@@ -137,17 +139,20 @@ csp: {
137
139
 
138
140
  ### Environment Variables
139
141
 
140
- The build adapter converts config to these env vars (can also be overridden at runtime):
141
-
142
- | Variable | Config Path |
143
- | ------------------------------- | ------------------------------------ |
144
- | `FRONTMCP_CSP_ENABLED` | `server.csp.enabled` |
145
- | `FRONTMCP_CSP_DIRECTIVES` | `server.csp.directives` (serialized) |
146
- | `FRONTMCP_CSP_REPORT_URI` | `server.csp.reportUri` |
147
- | `FRONTMCP_CSP_REPORT_ONLY` | `server.csp.reportOnly` |
148
- | `FRONTMCP_HSTS` | `server.headers.hsts` |
149
- | `FRONTMCP_CONTENT_TYPE_OPTIONS` | `server.headers.contentTypeOptions` |
150
- | `FRONTMCP_FRAME_OPTIONS` | `server.headers.frameOptions` |
142
+ The CLI converts config to these env vars for `dev` and the serverless/distributed builds (you can also set them yourself at runtime):
143
+
144
+ | Variable | Config Path |
145
+ | ------------------------------- | ------------------------------------- |
146
+ | `FRONTMCP_CSP_ENABLED` | `server.csp.enabled` |
147
+ | `FRONTMCP_CSP_DIRECTIVES` | `server.csp.directives` (serialized) |
148
+ | `FRONTMCP_CSP_REPORT_URI` | `server.csp.reportUri` |
149
+ | `FRONTMCP_CSP_REPORT_ONLY` | `server.csp.reportOnly` |
150
+ | `FRONTMCP_HSTS` | `server.headers.hsts` |
151
+ | `FRONTMCP_CONTENT_TYPE_OPTIONS` | `server.headers.contentTypeOptions` |
152
+ | `FRONTMCP_FRAME_OPTIONS` | `server.headers.frameOptions` |
153
+ | `FRONTMCP_HEADERS_CUSTOM` | `server.headers.custom` (JSON object) |
154
+
155
+ `off`, `false` or `none` in `FRONTMCP_HSTS`, `FRONTMCP_CONTENT_TYPE_OPTIONS` or `FRONTMCP_FRAME_OPTIONS` omits that header.
151
156
 
152
157
  ## Common Patterns
153
158
 
@@ -21,9 +21,9 @@ interface GuardConfig {
21
21
  | { url: string };
22
22
  vercelKv?: { url?: string; token?: string };
23
23
  upstash?: { url?: string; token?: string };
24
- // What to do when the backend is unreachable at startup:
25
- // 'error' (default in production) -- startup fails with GuardStorageUnavailableError (rate limits fail closed)
26
- // 'memory' (default otherwise) -- start with per-instance counters
24
+ // What to do when the backend is unreachable, at startup or while running:
25
+ // 'error' (default in production) -- startup fails, and a limited call is refused, with GuardStorageUnavailableError (rate limits fail closed)
26
+ // 'memory' (default otherwise) -- use per-instance counters (and go back to the backend once it answers)
27
27
  fallback?: 'error' | 'memory';
28
28
  };
29
29
 
@@ -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. Set `storage.fallback: 'memory'` to start with per-instance counters instead. (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. To start with per-instance counters instead, 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, or git |
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
 
@@ -23,7 +23,7 @@ Connect a React application to a FrontMCP server using `@frontmcp/react`. `Front
23
23
 
24
24
  ```typescript
25
25
  // src/server.ts — create a DirectMcpServer (in-memory) for the React app to consume.
26
- import { create, tool, z } from '@frontmcp/sdk';
26
+ import { create, tool, z } from '@frontmcp/react'; // `z` is re-exported; `@frontmcp/sdk` works too
27
27
 
28
28
  export const server = await create({
29
29
  info: { name: 'browser-app', version: '1.0.0' },
@@ -70,10 +70,12 @@ function ToolUI() {
70
70
  // useCallTool requires the tool name as a hook arg, so each row owns its own
71
71
  // hook instance. The mutate fn takes just the arguments object — not `{ name, arguments }`.
72
72
  function ToolButton({ tool }: { tool: { name: string; description?: string } }) {
73
- const [callTool] = useCallTool<{ name: string }>(tool.name);
73
+ const [callTool, { data }] = useCallTool<{ name: string }>(tool.name);
74
+ // `data` is the full CallToolResult; failures arrive as `data.isError`, not in `error`.
75
+ const text = data?.content?.[0]?.type === 'text' ? data.content[0].text : null;
74
76
  return (
75
77
  <button onClick={() => callTool({ name: 'World' })}>
76
- {tool.name}: {tool.description}
78
+ {tool.name}: {tool.description} {data?.isError ? '(failed)' : text}
77
79
  </button>
78
80
  );
79
81
  }
@@ -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
@@ -82,3 +82,5 @@ curl https://your-project.vercel.app/healthz
82
82
  ## Related
83
83
 
84
84
  - See `deploy-to-vercel` for KV provisioning, environment variables, and cold start optimization
85
+
86
+ > **Tasks and elicitation on Vercel KV:** Vercel KV has no pub/sub, so background tasks and elicitation cannot use it. The server still starts — tasks are skipped with a `[tasks]` startup warning unless `tasks: { enabled: true }` is set (which keeps `TaskStoreNotSupportedError`), and elicitation throws `ElicitationNotSupportedError` only when its store actually resolves to Vercel KV. Give tasks their own backend with `tasks: { redis }` or `tasks: { sqlite }` (an explicit backend ignores the ambient `KV_REST_API_URL`).
@@ -117,8 +117,44 @@ function ToolUI() {
117
117
  }
118
118
  ```
119
119
 
120
+ Things that trip people up with `@frontmcp/react`:
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
+
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).
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.
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".
131
+ - The subpath entries (`@frontmcp/react/state`, `/api`, `/ai`, `/router`) share the root entry's provider and server registry, so `useStoreResource`, `useApiClient`, `useAITools` and `useTools` register against the same `FrontMcpProvider`.
132
+ - `createRouterEntries()` from `@frontmcp/react/router` returns `{ tools, resources }` that go straight into `create({ tools, resources })`.
133
+
120
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.
121
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
+
122
158
  ## Browser vs Node vs SDK Target
123
159
 
124
160
  | Aspect | `--target browser` | `--target node` | `--target sdk` |
@@ -171,15 +207,16 @@ ls dist/browser/
171
207
 
172
208
  ## Troubleshooting
173
209
 
174
- | Problem | Cause | Solution |
175
- | --------------------------- | ----------------------------------------- | ---------------------------------------------------------------- |
176
- | `Module not found: fs` | Node.js module imported in browser bundle | Use a separate browser entry point that avoids Node-only imports |
177
- | `crypto is not defined` | Using `node:crypto` instead of WebCrypto | Switch to `@frontmcp/utils` crypto functions |
178
- | CORS errors on tool calls | MCP server missing CORS headers | Configure CORS middleware on the MCP server |
179
- | Bundle too large | All server-side code included | Use `--target browser` and a dedicated client entry file |
180
- | `@frontmcp/utils` fs throws | File system ops called in browser | Remove fs calls; use API endpoints or in-memory alternatives |
181
- | `AsyncContextOverlapError` | Concurrent tool calls inside one request | Await the calls one after another (no `AsyncContext` in browser) |
182
- | 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()` |
183
220
 
184
221
  ## Examples
185
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,14 +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
- | Archive > 100 MB | node_modules bundled or node runtime bloated | Tune `build.esbuild.external`, drop `--sea`, or disable `includeNodeModules` |
181
- | `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 |
182
- | `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` |
183
- | 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 |
184
- | `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 |
185
192
 
186
193
  Expected `--merge-from` layout (platform dirs must match MCPB platform keys):
187
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
@@ -208,15 +209,15 @@ const client = await connectOpenAI(config, {
208
209
 
209
210
  All `connect*()` functions return a `DirectClient` with these methods:
210
211
 
211
- | Method | Description |
212
- | ----------------------- | -------------------------------------- |
213
- | `listTools()` | List tools in platform-specific format |
214
- | `callTool(name, args)` | Execute a tool |
215
- | `listResources()` | List available resources |
216
- | `readResource(uri)` | Read a resource |
217
- | `listPrompts()` | List available prompts |
218
- | `getPrompt(name, args)` | Get a prompt |
219
- | `close()` | Clean up connection |
212
+ | Method | Description |
213
+ | ----------------------- | --------------------------------------- |
214
+ | `listTools()` | List tools in platform-specific format |
215
+ | `callTool(name, args)` | Execute a tool |
216
+ | `listResources()` | List all resources (follows every page) |
217
+ | `readResource(uri)` | Read a resource |
218
+ | `listPrompts()` | List all prompts (follows every page) |
219
+ | `getPrompt(name, args)` | Get a prompt |
220
+ | `close()` | Clean up connection |
220
221
 
221
222
  ## SDK vs Node Target
222
223