@frontmcp/skills 1.8.6 → 1.8.7

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 (40) hide show
  1. package/catalog/create-tool/SKILL.md +24 -24
  2. package/catalog/create-tool/examples/22-tool-with-ui-html-template.md +0 -1
  3. package/catalog/create-tool/examples/23-tool-with-ui-filesource-tsx.md +1 -3
  4. package/catalog/create-tool/examples/24-tool-with-ui-csp-and-bridge.md +4 -6
  5. package/catalog/create-tool/references/decorator-options.md +1 -1
  6. package/catalog/create-tool/references/ui-widgets.md +68 -41
  7. package/catalog/frontmcp-config/examples/configure-throttle/distributed-redis-throttle.md +3 -3
  8. package/catalog/frontmcp-config/references/configure-deployment-targets.md +19 -1
  9. package/catalog/frontmcp-config/references/configure-http.md +6 -4
  10. package/catalog/frontmcp-config/references/configure-security-headers.md +18 -13
  11. package/catalog/frontmcp-config/references/configure-throttle-guard-config.md +4 -4
  12. package/catalog/frontmcp-config/references/configure-throttle.md +1 -1
  13. package/catalog/frontmcp-deployment/SKILL.md +19 -19
  14. package/catalog/frontmcp-deployment/examples/build-for-browser/react-provider-setup.md +5 -3
  15. package/catalog/frontmcp-deployment/examples/deploy-to-vercel/vercel-with-kv.md +2 -0
  16. package/catalog/frontmcp-deployment/references/build-for-browser.md +8 -0
  17. package/catalog/frontmcp-deployment/references/build-for-mcpb.md +9 -8
  18. package/catalog/frontmcp-deployment/references/build-for-sdk.md +9 -9
  19. package/catalog/frontmcp-deployment/references/deploy-to-cloudflare.md +3 -1
  20. package/catalog/frontmcp-deployment/references/deploy-to-lambda.md +9 -8
  21. package/catalog/frontmcp-deployment/references/deploy-to-vercel.md +15 -7
  22. package/catalog/frontmcp-development/references/create-agent.md +8 -7
  23. package/catalog/frontmcp-development/references/create-plugin.md +17 -0
  24. package/catalog/frontmcp-development/references/official-plugins.md +12 -5
  25. package/catalog/frontmcp-guides/references/example-task-manager.md +2 -2
  26. package/catalog/frontmcp-guides/references/example-weather-api.md +2 -2
  27. package/catalog/frontmcp-production-readiness/examples/distributed-ha/ha-kubernetes-3-replicas.md +1 -0
  28. package/catalog/frontmcp-production-readiness/examples/production-node-sdk/package-json-config.md +2 -2
  29. package/catalog/frontmcp-production-readiness/references/common-checklist.md +1 -1
  30. package/catalog/frontmcp-production-readiness/references/distributed-ha.md +17 -7
  31. package/catalog/frontmcp-production-readiness/references/health-readiness-endpoints.md +3 -1
  32. package/catalog/frontmcp-setup/examples/project-structure-nx/nx-generator-scaffolding.md +1 -1
  33. package/catalog/frontmcp-setup/references/nx-workflow.md +25 -17
  34. package/catalog/frontmcp-setup/references/setup-redis.md +1 -1
  35. package/catalog/frontmcp-setup/references/setup-sqlite.md +9 -6
  36. package/catalog/frontmcp-testing/SKILL.md +24 -23
  37. package/catalog/frontmcp-testing/references/setup-testing.md +26 -2
  38. package/catalog/frontmcp-testing/references/test-auth.md +8 -0
  39. package/catalog/skills-manifest.json +4 -4
  40. package/package.json +1 -1
@@ -32,8 +32,8 @@ when_to_use: |
32
32
  Trigger when creating or editing a `*.tool.ts` / `*.tool.tsx` file, adding a `@Tool`
33
33
  decorator, defining `inputSchema` / `outputSchema` for a tool, deriving `execute()`
34
34
  parameter or return types, wiring dependency injection into a tool, returning
35
- structured / media / resource content, adding a `ui:` block (HTML / MDX / React /
36
- FileSource), configuring throttling, declaring auth providers, restricting platforms
35
+ structured / media / resource content, adding a `ui:` block (FileSource / React /
36
+ HTML / Markdown), configuring throttling, declaring auth providers, restricting platforms
37
37
  via `availableWhen`, requesting interactive input via `this.elicit`, adding tool
38
38
  `annotations`, or registering a tool in `@App({ tools })`.
39
39
 
@@ -203,7 +203,7 @@ If a request seems to conflict with an inherited default (e.g., "wrap `inputSche
203
203
  │ See: examples/22-tool-with-ui-html-template.md
204
204
  ├── React widget (file) → ui: { template: { file: widgetPath } }
205
205
  │ See: examples/23-tool-with-ui-filesource-tsx.md
206
- ├── Calls other tools → widgetAccessible: true + window.FrontMcpBridge
206
+ ├── Calls other tools → window.FrontMcpBridge.callTool
207
207
  │ See: examples/24-tool-with-ui-csp-and-bridge.md
208
208
  └── Claude target → resourceMode is auto-detected; do not set
209
209
  See: references/ui-widgets.md
@@ -240,7 +240,7 @@ If a request seems to conflict with an inherited default (e.g., "wrap `inputSche
240
240
  | Tool restricted to one OS / runtime / target | [`21-tool-with-availability-constraints`](./examples/21-tool-with-availability-constraints.md) | `availableWhen` axes |
241
241
  | Tool with a quick inline HTML widget | [`22-tool-with-ui-html-template`](./examples/22-tool-with-ui-html-template.md) | `ui: { template: (ctx) => '<div>…</div>' }` |
242
242
  | Tool with a separate `.tsx` widget file | [`23-tool-with-ui-filesource-tsx`](./examples/23-tool-with-ui-filesource-tsx.md) | `FileSource` + `import.meta.url` anchoring |
243
- | Tool widget that calls other tools | [`24-tool-with-ui-csp-and-bridge`](./examples/24-tool-with-ui-csp-and-bridge.md) | `widgetAccessible: true` + `window.FrontMcpBridge.callTool` |
243
+ | Tool widget that calls other tools | [`24-tool-with-ui-csp-and-bridge`](./examples/24-tool-with-ui-csp-and-bridge.md) | `window.FrontMcpBridge.callTool` |
244
244
  | Tool that triggers a job + tracks it | [`25-tool-handing-off-to-job`](./examples/25-tool-handing-off-to-job.md) | Thin tool + heavy job — the right split |
245
245
  | Tool that returns a resource handle | [`26-tool-with-resource-link-output`](./examples/26-tool-with-resource-link-output.md) | `outputSchema: 'resource_link'` — the host fetches the resource |
246
246
  | Tool with `examples` metadata for discovery | [`27-tool-with-examples-metadata`](./examples/27-tool-with-examples-metadata.md) | `examples: [{ description, input, output? }]` |
@@ -269,26 +269,26 @@ Before considering a tool "done":
269
269
 
270
270
  ## References (deep dives)
271
271
 
272
- | Reference | Covers |
273
- | --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
274
- | [`quick-start.md`](./references/quick-start.md) | 60-second tour: minimal tool, registration, calling it from a test |
275
- | [`decorator-options.md`](./references/decorator-options.md) | Every field on `@Tool({...})` — what it does, default, when to set it |
276
- | [`input-schema.md`](./references/input-schema.md) | Raw shape vs `z.object`, refinements, defaults, optional, describe |
277
- | [`output-schema.md`](./references/output-schema.md) | All supported output types: Zod shape, Zod schema, primitives, media, arrays |
278
- | [`derived-types.md`](./references/derived-types.md) | `ToolInputOf` / `ToolOutputOf` patterns, file layout, schema hoisting |
279
- | [`execution-context.md`](./references/execution-context.md) | `ToolContext` methods + properties — `this.get`, `this.fetch`, `this.notify`, `this.context`, etc. |
280
- | [`error-handling.md`](./references/error-handling.md) | `this.fail`, MCP error classes (`PublicMcpError`, `ResourceNotFoundError`), error flow, when to throw vs `fail` |
281
- | [`throttling.md`](./references/throttling.md) | `rateLimit`, `concurrency`, `timeout` — semantics, interaction, defaults |
282
- | [`auth-providers.md`](./references/auth-providers.md) | `authProviders` string shorthand vs full mapping, scopes, alias, credential vault basics |
283
- | [`availability.md`](./references/availability.md) | `availableWhen` axes (os / runtime / deployment / provider / target / surface / env), `missingAxes`, `isPlatform` |
284
- | [`elicitation.md`](./references/elicitation.md) | `this.elicit`, server-level enable, `ElicitationDisabledError`, accept / decline / cancel |
285
- | [`ui-widgets.md`](./references/ui-widgets.md) | `@Tool({ ui })` — template formats, `servingMode`, `resourceMode` host-detect, CSP, `widgetAccessible`, MCP Apps spec |
286
- | [`annotations.md`](./references/annotations.md) | `readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`, `title` |
287
- | [`function-style-builder.md`](./references/function-style-builder.md) | `tool({...})(handler)` — when to pick over a class, register, ctx parameter |
288
- | [`remote-and-esm.md`](./references/remote-and-esm.md) | `Tool.esm(...)` / `Tool.remote(...)` — load tools from ESM URLs or remote MCP servers |
289
- | [`registration.md`](./references/registration.md) | `@App({ tools })` vs `@FrontMcp({ tools })`, multi-app composition |
290
- | [`file-layout.md`](./references/file-layout.md) | Flat-sibling vs folder-per-tool, `<name>.schema.ts` / `<name>.tool.ts` / `<name>.tool.spec.ts` |
291
- | [`testing.md`](./references/testing.md) | Per-tool unit tests — `@frontmcp/testing`, mocking DI, asserting output validation |
272
+ | Reference | Covers |
273
+ | --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
274
+ | [`quick-start.md`](./references/quick-start.md) | 60-second tour: minimal tool, registration, calling it from a test |
275
+ | [`decorator-options.md`](./references/decorator-options.md) | Every field on `@Tool({...})` — what it does, default, when to set it |
276
+ | [`input-schema.md`](./references/input-schema.md) | Raw shape vs `z.object`, refinements, defaults, optional, describe |
277
+ | [`output-schema.md`](./references/output-schema.md) | All supported output types: Zod shape, Zod schema, primitives, media, arrays |
278
+ | [`derived-types.md`](./references/derived-types.md) | `ToolInputOf` / `ToolOutputOf` patterns, file layout, schema hoisting |
279
+ | [`execution-context.md`](./references/execution-context.md) | `ToolContext` methods + properties — `this.get`, `this.fetch`, `this.notify`, `this.context`, etc. |
280
+ | [`error-handling.md`](./references/error-handling.md) | `this.fail`, MCP error classes (`PublicMcpError`, `ResourceNotFoundError`), error flow, when to throw vs `fail` |
281
+ | [`throttling.md`](./references/throttling.md) | `rateLimit`, `concurrency`, `timeout` — semantics, interaction, defaults |
282
+ | [`auth-providers.md`](./references/auth-providers.md) | `authProviders` string shorthand vs full mapping, scopes, alias, credential vault basics |
283
+ | [`availability.md`](./references/availability.md) | `availableWhen` axes (os / runtime / deployment / provider / target / surface / env), `missingAxes`, `isPlatform` |
284
+ | [`elicitation.md`](./references/elicitation.md) | `this.elicit`, server-level enable, `ElicitationDisabledError`, accept / decline / cancel |
285
+ | [`ui-widgets.md`](./references/ui-widgets.md) | `@Tool({ ui })` — template formats, `servingMode`, `resourceMode` host-detect, CSP, ignored options, MCP Apps spec |
286
+ | [`annotations.md`](./references/annotations.md) | `readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`, `title` |
287
+ | [`function-style-builder.md`](./references/function-style-builder.md) | `tool({...})(handler)` — when to pick over a class, register, ctx parameter |
288
+ | [`remote-and-esm.md`](./references/remote-and-esm.md) | `Tool.esm(...)` / `Tool.remote(...)` — load tools from ESM URLs or remote MCP servers |
289
+ | [`registration.md`](./references/registration.md) | `@App({ tools })` vs `@FrontMcp({ tools })`, multi-app composition |
290
+ | [`file-layout.md`](./references/file-layout.md) | Flat-sibling vs folder-per-tool, `<name>.schema.ts` / `<name>.tool.ts` / `<name>.tool.spec.ts` |
291
+ | [`testing.md`](./references/testing.md) | Per-tool unit tests — `@frontmcp/testing`, mocking DI, asserting output validation |
292
292
 
293
293
  ## Rules (constraints — read these once, then they're enforced)
294
294
 
@@ -38,7 +38,6 @@ type Out = { city: string; temperatureF: number; conditions: string };
38
38
  inputSchema,
39
39
  outputSchema,
40
40
  ui: {
41
- widgetDescription: 'Current weather card',
42
41
  // `html` escapes interpolated values — no manual escapeHtml needed
43
42
  template: (ctx: TemplateContext<In, Out>) => ctx.helpers.html`
44
43
  <div style="padding:16px;font-family:system-ui;border-radius:12px;background:#f5f7fa">
@@ -53,12 +53,10 @@ const widgetPath = fileURLToPath(new URL('./sales-chart.widget.tsx', import.meta
53
53
  inputSchema,
54
54
  outputSchema,
55
55
  ui: {
56
- widgetDescription: 'Monthly revenue chart',
57
56
  template: { file: widgetPath },
58
57
  // resourceMode is intentionally UNSET — framework host-detects: 'inline' for Claude
59
58
  // (React bundled in, widget renders under Claude's CSP), 'cdn' for OpenAI / ChatGPT /
60
59
  // Cursor / MCP Inspector (smaller payload from esm.sh). Issue #456.
61
- hydrate: false, // SSR-only — dodges React error #418 in iframe sandboxes
62
60
  },
63
61
  })
64
62
  export class SalesChartTool extends ToolContext {
@@ -111,5 +109,5 @@ export default function SalesChartWidget({ output }: Props) {
111
109
  - **`import.meta.url` anchoring** — relative paths in `FileSource` resolve against `process.cwd()`, not the tool file (#444). Running the server from a different directory breaks the widget at tool-call time. Anchoring fixes it once.
112
110
  - **Ship the widget** — the anchored path points next to the **compiled** file after a build, and tsc doesn't emit `*.widget.tsx`. `frontmcp build` copies widget files into the output (directly next to the bundle for the bundled `node` / `cli` / `lambda` / `vercel` targets, so keep widget file names unique); a plain `tsc` build needs its own copy step (#649).
113
111
  - **`resourceMode` unset** — leave it. The framework picks `'inline'` for Claude (React bundled into the widget — actually renders) and `'cdn'` for everyone else (smaller payload via esm.sh). Setting it explicitly only locks in one behavior across all clients.
114
- - **`hydrate: false`** — default. React SSR output is static HTML; the bridge IIFE handles any interactivity. Enabling hydration creates React error #418 in Claude's iframe sandbox where the client-side render diverges from the SSR render.
112
+ - **No `hydrate` option** — `ui.hydrate` is accepted but has no effect yet (startup logs a warning). The `.tsx` widget is bundled and mounted on the client by the generated entry.
115
113
  - **`*.widget.tsx` naming** — the scaffolded `tsconfig.json` excludes `**/*.widget.tsx` from the server typecheck (#445). The widget compiles via uipack/esbuild at render time with its own React-aware config.
@@ -2,10 +2,10 @@
2
2
  name: 24-tool-with-ui-csp-and-bridge
3
3
  level: advanced
4
4
  description: 'Interactive tool widget that fetches from an allow-listed CSP origin and invokes another tool via `window.FrontMcpBridge.callTool` — the full pattern for live-data widgets that need cross-tool composition.'
5
- tags: [ui, csp, widgetAccessible, FrontMcpBridge, interactive-widget]
5
+ tags: [ui, csp, callTool, FrontMcpBridge, interactive-widget]
6
6
  features:
7
7
  - "Restricting the widget's outbound `fetch` via `ui.csp.connectDomains` (emitted on the resource per #455)"
8
- - 'Opting the widget into cross-tool calls with `widgetAccessible: true` and using `window.FrontMcpBridge.callTool(name, args)` instead of host-specific APIs'
8
+ - 'Calling other tools from the widget with `window.FrontMcpBridge.callTool(name, args)` instead of host-specific APIs'
9
9
  - 'Building the markup with `ctx.helpers.html` and embedding initial data into the inline `<script>` via `trustedHtml(jsonEmbed(...))` (`jsonEmbed` escapes `<`, `>` and `&`)'
10
10
  - 'Surfacing in-flight status via `invocationStatus.invoking` / `invoked` so the host UI shows feedback'
11
11
  ---
@@ -58,8 +58,6 @@ type Out = { symbol: string; priceUsd: number; asOf: string };
58
58
  inputSchema,
59
59
  outputSchema,
60
60
  ui: {
61
- widgetDescription: 'Live stock quote with refresh',
62
- widgetAccessible: true, // required for window.FrontMcpBridge.callTool
63
61
  invocationStatus: { invoking: 'Fetching quote…', invoked: 'Quote loaded' },
64
62
  csp: {
65
63
  // CSP applies to the widget iframe — only allow fetches to our own market-data API.
@@ -117,13 +115,13 @@ export class ShowQuoteTool extends ToolContext {
117
115
  ## What This Demonstrates
118
116
 
119
117
  - Restricting the widget's outbound `fetch` via `ui.csp.connectDomains` (emitted on the resource per #455)
120
- - Opting the widget into cross-tool calls with `widgetAccessible: true` and using `window.FrontMcpBridge.callTool(name, args)` instead of host-specific APIs
118
+ - Calling other tools from the widget with `window.FrontMcpBridge.callTool(name, args)` instead of host-specific APIs
121
119
  - Building the markup with `ctx.helpers.html` and embedding initial data into the inline `<script>` via `trustedHtml(jsonEmbed(...))` (`jsonEmbed` escapes `<`, `>` and `&`)
122
120
  - Surfacing in-flight status via `invocationStatus.invoking` / `invoked` so the host UI shows feedback
123
121
 
124
122
  ## Why these choices
125
123
 
126
- - **`widgetAccessible: true`** — required for `window.FrontMcpBridge.callTool`. Without it, the bridge is read-only (the widget can read `getToolInput` / `getToolOutput` but can't invoke tools).
124
+ - **No `widgetAccessible` / `widgetDescription`** — both are accepted by the schema but nothing reads them yet (startup logs a warning), so they are left out. Whether the widget may call tools is decided by the host.
127
125
  - **`csp.connectDomains`** — limits what the widget can `fetch` to. Without a CSP, the host's default applies (which may block everything in Claude). With `connectDomains: ['https://api.market.example']`, only that origin is reachable.
128
126
  - **`window.FrontMcpBridge.callTool` not `window.openai.callTool`** — the bridge handles host detection. `window.openai.*` works on OpenAI Apps SDK but breaks everywhere else.
129
127
  - **`jsonEmbed` not `JSON.stringify`** — `JSON.stringify` doesn't escape `</script>` or `<!--` and can break out of the inline script tag. `jsonEmbed` writes `<`, `>` and `&` as `\u003c`, `\u003e`, `\u0026`.
@@ -43,7 +43,7 @@ Full surface of the `@Tool` decorator. Mandatory fields are bolded.
43
43
 
44
44
  - **`rateLimit` + `concurrency`** — independent. Rate-limit caps invocations over time; concurrency caps simultaneous in-flight. A "1 req/s with max 2 concurrent" tool is fine: bursts can run two at once, then back off.
45
45
  - **`timeout` + `rateLimit`** — orthogonal. Timeout wraps a single call; rate-limit wraps the rate of calls.
46
- - **`authProviders` + `ui.widgetAccessible`** — the widget bridge respects the tool's auth requirements. A widget that calls back to a tool requiring `authProviders: ['github']` will fail in the bridge if no GitHub session exists.
46
+ - **`authProviders` + widget tool calls** — a widget that calls back to a tool requiring `authProviders: ['github']` fails if no GitHub session exists. (`ui.widgetAccessible` is accepted but has no effect yet.)
47
47
  - **`availableWhen` + `visibility`** — `availableWhen` is a hard constraint (filtered out of `tools/list` AND blocked from execution when context doesn't match); `visibility: 'hidden'` is a soft hide (filtered from `tools/list` but still callable by name). `visibility: 'internal'` blocks external `tools/call` entirely (in-process `this.callTool` only).
48
48
  - **`ui.servingMode === 'static'` + `availableWhen`** — static widgets pre-compile at startup. If a tool is filtered out by `availableWhen`, its static widget isn't compiled either.
49
49
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: ui-widgets
3
- description: @Tool({ ui }) — template formats, trusted markup (html / escapeStringResults), servingMode, host-detect resourceMode, CSP, widgetAccessible, MCP Apps spec.
3
+ description: @Tool({ ui }) — template formats, trusted markup (html / escapeStringResults), servingMode, host-detect resourceMode, CSP, ignored options, MCP Apps spec.
4
4
  ---
5
5
 
6
6
  # Tool UI widgets
@@ -39,12 +39,12 @@ That's it. The framework:
39
39
 
40
40
  ## Template formats
41
41
 
42
- | Format | Shape | When |
43
- | ---------------------------- | ----------------------------------------- | --------------------------------------------------------------------------------------------------------- |
44
- | **FileSource (recommended)** | `{ file: widgetPath }` | `.tsx` / `.jsx` / `.html` source files. Anchor with `import.meta.url`. |
45
- | **Function** | `` (ctx) => ctx.helpers.html`…` `` | Quick demo / one-liner HTML. Annotate `ctx: TemplateContext<In, Out>` ([why](#typescript-gotcha-ts7006)). |
46
- | **HTML / MDX string** | `'<div>…</div>'` or `'# Title\n<Card />'` | Static markup; pair with `mdxComponents` for MDX. |
47
- | **React component** | `MyWidget` | SSR React. Set `hydrate: false` (default) for Claude/ChatGPT. |
42
+ | Format | Shape | When |
43
+ | ---------------------------- | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
44
+ | **FileSource (recommended)** | `{ file: widgetPath }` | `.tsx` / `.jsx` / `.html` source files. Anchor with `import.meta.url`. |
45
+ | **Function** | `` (ctx) => ctx.helpers.html`…` `` | Quick demo / one-liner HTML. Annotate `ctx: TemplateContext<In, Out>` ([why](#typescript-gotcha-ts7006)). |
46
+ | **HTML / Markdown string** | `'<div>…</div>'` or `'# Title\n- item'` | A string with both `<` and `>` is HTML as written; any other string is Markdown, converted on the server. MDX is **not** compiled. |
47
+ | **React component** | `MyWidget` | A bare component reference cannot be bundled for the widget; use the `{ file }` form instead. |
48
48
 
49
49
  The renderer auto-detects which one you passed.
50
50
 
@@ -64,26 +64,54 @@ Or use the FileSource form — it sidesteps the issue.
64
64
 
65
65
  ## `ToolUIConfig` fields
66
66
 
67
- | Field | Default | Purpose |
68
- | --------------------------------------------------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------- |
69
- | `template` | — | Required. Function / HTML-string / React component / `{ file }` FileSource. |
70
- | `widgetDescription` | — | Human-readable description surfaced to the host UI. |
71
- | `servingMode` | `'auto'` | `'inline'` / `'static'` / `'hybrid'` / `'direct-url'` / `'custom-url'`. `'auto'` picks the best per-host. |
72
- | `displayMode` | `'inline'` | `'inline'` / `'fullscreen'` / `'pip'` — host display hint. |
73
- | `preferredHeight` | — | `number` (px) or CSS string (`'50vh'`). Initial widget height; auto-resize grows/shrinks from this baseline. |
74
- | `minHeight` / `maxHeight` | — | `number` (px) or CSS string. Clamp the widget height; auto-resize never reports outside this range. |
75
- | `aspectRatio` | — | CSS `aspect-ratio` (`'16 / 9'` or `1.5`). Hosts that honor it size by ratio instead of measured height. |
76
- | `autoResize` | `true` | Report the document height (margins included) to the host after the handshake. Set `false` to opt out (CSS still applies). |
77
- | `csp` | — | `{ connectDomains?, resourceDomains? }` — emitted on the resource content's `_meta.ui.csp` (#455). Claude honors CSP only here. |
78
- | `contentSecurity` | strict | `{ allowUnsafeLinks?, allowInlineScripts?, bypassSanitization? }` — keep defaults. |
79
- | `escapeStringResults` | unset | `true` escapes plain string results of a template function; `html` / `trustedHtml` stay markup. Default in 1.9. |
80
- | `widgetAccessible` | `false` | `true` exposes `window.FrontMcpBridge.callTool` in the widget. |
81
- | `resourceUri` | auto | Override the `ui://widget/{toolName}.html` URI. |
82
- | `uiType` | `'auto'` | Force `'html'` / `'react'` / `'mdx'` / `'markdown'`. |
83
- | `resourceMode` | host-detect | `'cdn'` / `'inline'`. Leave unset — the framework host-detects (Claude → `'inline'`, #456). |
84
- | `hydrate` | `false` | Enable React hydration after SSR. Off by default — avoids React error #418 in Claude. |
85
- | `externals`, `dependencies` | — | CDN externals for FileSource widgets. |
86
- | `customShell`, `invocationStatus`, `widgetCapabilities`, `prefersBorder`, `sandboxDomain`, `htmlResponsePrefix` | — | Platform-specific knobs. |
67
+ | Field | Default | Purpose |
68
+ | --------------------------------------------------------------------------------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
69
+ | `template` | — | Required. `{ file }` FileSource (recommended) / function / HTML or Markdown string / React component. |
70
+ | `widgetDescription` | — | **No effect yet** — accepted, not read. |
71
+ | `servingMode` | `'auto'` | `'inline'` / `'static'` / `'hybrid'`. `'auto'` picks the best per-host. `'direct-url'` / `'custom-url'` are not implemented (served inline, startup warning). `'hybrid'` sends only `_meta['ui/component']` = `{ type, hash, toolName }`, no code. |
72
+ | `displayMode` | `'inline'` | **No effect yet.** |
73
+ | `preferredHeight` | — | `number` (px) or CSS string (`'50vh'`). Initial widget height; auto-resize grows/shrinks from this baseline. |
74
+ | `minHeight` / `maxHeight` | — | `number` (px) or CSS string. Clamp the widget height; auto-resize never reports outside this range. |
75
+ | `aspectRatio` | — | CSS `aspect-ratio` (`'16 / 9'` or `1.5`). Hosts that honor it size by ratio instead of measured height. |
76
+ | `autoResize` | `true` | Report the document height (margins included) to the host after the handshake. Set `false` to opt out (CSS still applies). |
77
+ | `csp` | — | `{ connectDomains?, resourceDomains? }` — emitted on the resource content's `_meta.ui.csp` (#455). Claude honors CSP only here. |
78
+ | `contentSecurity` | strict | **No effect yet.** |
79
+ | `escapeStringResults` | unset | `true` escapes plain string results of a template function; `html` / `trustedHtml` stay markup. Default in 1.9. |
80
+ | `widgetAccessible` | `false` | **No effect yet.** |
81
+ | `resourceUri` | auto | Override the `ui://widget/{toolName}.html` URI. |
82
+ | `uiType` | `'auto'` | **No effect yet** — the type is auto-detected. |
83
+ | `resourceMode` | host-detect | `'cdn'` / `'inline'`. Leave unset — the framework host-detects (Claude → `'inline'`, #456). |
84
+ | `hydrate` | `false` | **No effect yet.** |
85
+ | `externals`, `dependencies` | — | CDN externals for FileSource widgets. |
86
+ | `customShell`, `invocationStatus`, `widgetCapabilities` | — | Platform-specific knobs. |
87
+ | `prefersBorder`, `sandboxDomain`, `runtimeOptions`, `mdxComponents`, `bundlingMode`, `htmlResponsePrefix` | — | **No effect yet.** There is no dual HTML payload for Claude. |
88
+
89
+ ## Components and hooks
90
+
91
+ `@frontmcp/ui` has **no** `card()`, `badge()`, `descriptionList()`, `button()`, `form()` or `input()` functions. Widgets use React: components from `@frontmcp/ui/components` (`Alert`, `Avatar`, `Badge`, `Button`, `Card`, `List`, `Loader`, `Modal`, `Select`, `Table`, `TextField`) and bridge hooks from `@frontmcp/ui/react` (`useToolInput`, `useToolOutput`, `useCallTool`, `useTheme`, `useHostContext`, ...). A `.tsx` widget receives `{ output, loading }`.
92
+
93
+ `useCallTool` returns a **tuple**, not an object:
94
+
95
+ ```tsx
96
+ import { Card } from '@frontmcp/ui/components';
97
+ import { useCallTool } from '@frontmcp/ui/react';
98
+
99
+ export default function Widget({ output }: { output: { id: string } | null }) {
100
+ const [refresh, { data, loading, error, called }, reset] = useCallTool('get_order');
101
+ if (!output) return <Card title="Loading..." />;
102
+ return (
103
+ <button disabled={loading} onClick={() => refresh({ orderId: output.id })}>
104
+ Refresh
105
+ </button>
106
+ );
107
+ }
108
+ ```
109
+
110
+ ## Markdown, MDX and sanitization
111
+
112
+ - A string template with both `<` and `>` is HTML and is used as written. Any other string is Markdown: headings, paragraphs, fenced code, lists, bold, italic, inline code and links are converted server-side; raw HTML is escaped; a link survives only if its target starts with `http:`, `https:`, `mailto:`, `/` or `#`.
113
+ - MDX is not compiled: `{output.field}` expressions are not evaluated and `mdxComponents` is ignored. Use a `.tsx` FileSource widget for interactivity.
114
+ - Author-written markup (template literals, HTML strings, `.tsx`) is trusted and not sanitized. Values interpolated into `` html`…` `` are escaped; plain string results are escaped only with `escapeStringResults: true`.
87
115
 
88
116
  ## Widget resources and per-call renders
89
117
 
@@ -169,7 +197,7 @@ Projects created with `frontmcp create` already have it through the `frontmcp` p
169
197
 
170
198
  ## Widget bridge — `window.FrontMcpBridge`
171
199
 
172
- When the widget needs to read tool data or invoke other tools, the bridge IIFE is injected automatically. Set `widgetAccessible: true` to enable `callTool`:
200
+ When the widget needs to read tool data or invoke other tools, the bridge IIFE is injected automatically; no option is needed (`widgetAccessible` is accepted but has no effect yet):
173
201
 
174
202
  ```typescript
175
203
  ui: {
@@ -182,19 +210,18 @@ ui: {
182
210
  };
183
211
  </script>
184
212
  `,
185
- widgetAccessible: true,
186
213
  }
187
214
  ```
188
215
 
189
- | Bridge method | Purpose |
190
- | --------------------------------------------------------------- | ------------------------------------------------------- |
191
- | `callTool(name, args)` | Invoke another tool (requires `widgetAccessible: true`) |
192
- | `getToolInput()` / `getToolOutput()` / `getStructuredContent()` | Read the tool data |
193
- | `getWidgetState()` / `setWidgetState(state)` | Persisted per-widget state |
194
- | `getHostContext()` / `getTheme()` / `getDisplayMode()` | Host context |
195
- | `onContextChange(cb)` | Subscribe to host context changes (handshake included) |
196
- | `hasCapability(cap)` | Probe adapter capabilities |
197
- | `onToolResponseMetadata(cb)` | Subscribe to `ui/html` arrival (inline mode) |
216
+ | Bridge method | Purpose |
217
+ | --------------------------------------------------------------- | ------------------------------------------------------ |
218
+ | `callTool(name, args)` | Invoke another tool (the host may still refuse) |
219
+ | `getToolInput()` / `getToolOutput()` / `getStructuredContent()` | Read the tool data |
220
+ | `getWidgetState()` / `setWidgetState(state)` | Persisted per-widget state |
221
+ | `getHostContext()` / `getTheme()` / `getDisplayMode()` | Host context |
222
+ | `onContextChange(cb)` | Subscribe to host context changes (handshake included) |
223
+ | `hasCapability(cap)` | Probe adapter capabilities |
224
+ | `onToolResponseMetadata(cb)` | Subscribe to `ui/html` arrival (inline mode) |
198
225
 
199
226
  The bridge routes to the right host adapter (OpenAI SDK / Claude postMessage / FrontMCP direct) automatically. **Never call `window.openai.*` directly** — it works on OpenAI but breaks everywhere else.
200
227
 
@@ -238,13 +265,13 @@ What FrontMCP does with it:
238
265
  - **`_meta` hints** — the same values ride along on the response/discovery `_meta` as `ui/preferredHeight`, `ui/minHeight`, `ui/maxHeight`, `ui/aspectRatio` (and nested under `_meta.ui` in `tools/list`), so hosts that read sizing from metadata pick it up.
239
266
  - **Runtime auto-resize** — when `autoResize !== false` and `ResizeObserver` is available, the bridge observes `<html>`, `<body>` and `#root` and reports the page height to the host (debounced via `requestAnimationFrame`), also firing a `widget:resize` event you can listen for. Call `window.FrontMcpBridge.setSize({ height, width, aspectRatio })` to report manually.
240
267
  - **What is measured** — the whole document: `<html>` at `height: fit-content`, plus any content overflowing a fixed-height `<body>`, clamped by a px `max-height` on `<html>`. Body margins and margins collapsed through the body (an `<h2>` or `<ul>` at the edge) are counted, and the height shrinks when content does. `preferredHeight` / `minHeight` / `maxHeight` act as the floor and ceiling.
241
- - **When it is sent** — reports wait for the bridge to initialize. In an ext-apps host the first report goes out once the `ui/initialize` handshake completes (a request sent earlier would be rejected); a report the host rejects is sent again on the next observation, even for the same height. A manual `setSize` called before the handshake is held and delivered right after it (only the latest size).
268
+ - **When it is sent** — auto-resize reports wait for the bridge to initialize. In an ext-apps host the first report is sent after the `ui/initialize` handshake settles. `ui/notifications/size-changed` is a notification, so the host never answers it; it is refused locally (the promise rejects) only when no trusted origin exists, and auto-resize then retries that height on a later observation. A manual `setSize` called before the handshake, with no trusted origin configured, is held and only the latest size is sent right after it. `aspectRatio` stays part of the cross-platform `FrontMcpBridge.setSize` API; the ext-apps adapter leaves it out of the notification payload.
242
269
 
243
270
  Per-host behavior:
244
271
 
245
272
  - **Claude / static widgets** — the host measures the iframe DOM height itself, so auto-resize is effectively CSS-only (the `setSize` report is a no-op). The injected CSS is what makes a fixed-tall widget (media players, canvases) open without clipping.
246
273
  - **OpenAI ChatGPT** — auto-resize forwards to the Apps SDK sizing API when one is exposed; otherwise the SDK's own DOM measurement applies.
247
- - **ext-apps hosts** — the measured size is reported via a `ui/setSize` request (parallels `ui/setDisplayMode`).
274
+ - **ext-apps hosts** — the measured size is reported with the standard `ui/notifications/size-changed` notification (`{ width, height }` in px), which any spec-compliant host handles.
248
275
  - **Gemini / generic / unknown** — `setSize` is a no-op; only the static CSS applies.
249
276
 
250
277
  `displayMode: 'fullscreen'` remains a separate, best-effort hint a host may ignore.
@@ -257,7 +284,7 @@ Per-host behavior:
257
284
 
258
285
  - [`22-tool-with-ui-html-template`](../examples/22-tool-with-ui-html-template.md) — inline function template
259
286
  - [`23-tool-with-ui-filesource-tsx`](../examples/23-tool-with-ui-filesource-tsx.md) — `.tsx` widget, host-detect
260
- - [`24-tool-with-ui-csp-and-bridge`](../examples/24-tool-with-ui-csp-and-bridge.md) — CSP + `widgetAccessible` + bridge
287
+ - [`24-tool-with-ui-csp-and-bridge`](../examples/24-tool-with-ui-csp-and-bridge.md) — CSP + bridge `callTool`
261
288
 
262
289
  ## Related rules
263
290
 
@@ -9,7 +9,7 @@ features:
9
9
  - 'Using `keyPrefix` to namespace guard keys in a shared Redis instance'
10
10
  - "Combining `partitionBy: 'ip'` for global limits with `partitionBy: 'session'` per tool"
11
11
  - 'In-memory counters are per-process and would allow N times the intended rate with N instances'
12
- - "Startup fails closed with `GuardStorageUnavailableError` when Redis is down, unless `fallback: 'memory'`"
12
+ - "Startup fails closed, and a limited call is refused, with `GuardStorageUnavailableError` when Redis is down, unless `fallback: 'memory'`"
13
13
  ---
14
14
 
15
15
  # Distributed Rate Limiting with Redis
@@ -87,13 +87,13 @@ class Server {}
87
87
  - Using `keyPrefix` to namespace guard keys in a shared Redis instance
88
88
  - Combining `partitionBy: 'ip'` for global limits with `partitionBy: 'session'` per tool
89
89
  - In-memory counters are per-process and would allow N times the intended rate with N instances
90
- - Startup fails closed with `GuardStorageUnavailableError` when Redis is down, unless `fallback: 'memory'`
90
+ - Startup fails closed, and a limited call is refused, with `GuardStorageUnavailableError` when Redis is down, unless `fallback: 'memory'`
91
91
 
92
92
  ## Notes
93
93
 
94
94
  - `storage` is the `@frontmcp/utils` storage shape (`type` + `redis: { config }` or `redis: { url }`), not the top-level `redis` shape. A block without `type` is auto-detected from the environment and otherwise runs in memory.
95
95
  - Keys read `payments:guard:process_payment:<partition>:rl:...`: a trailing `:` on `keyPrefix` is dropped. Before 1.8.6 it was doubled (`payments:guard::...`), so counters briefly split between versions during a rolling deploy.
96
- - To keep serving with per-instance counters while Redis is down, add `fallback: 'memory'` to `storage`.
96
+ - To keep serving with per-instance counters while Redis is down (at startup or mid-run), add `fallback: 'memory'` to `storage`.
97
97
 
98
98
  ## Related
99
99
 
@@ -230,9 +230,11 @@ Within a directory:
230
230
  For every CLI option that's also expressible in the config:
231
231
 
232
232
  ```
233
- explicit CLI flag > FRONTMCP_<NAME> env var > frontmcp.config field > built-in default
233
+ explicit CLI flag > frontmcp.config field > built-in default
234
234
  ```
235
235
 
236
+ 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).
237
+
236
238
  ## Per-command consumption (issue #400)
237
239
 
238
240
  The config is consumed by every `frontmcp` command, not just `build`:
@@ -249,6 +251,22 @@ The config is consumed by every `frontmcp` command, not just `build`:
249
251
 
250
252
  See `transport`, `env`, `clients`, `test`, `skills` field reference in [docs/frontmcp/deployment/frontmcp-config](https://docs.agentfront.dev/frontmcp/deployment/frontmcp-config).
251
253
 
254
+ ## `transport.http.path` for every build target
255
+
256
+ `transport.http.path` is the mount path of the MCP endpoint for **every** build target, not only `frontmcp dev`:
257
+
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 |
263
+
264
+ A `@FrontMcp({ http: { entryPath } })` value still wins over the config. When the two differ, `frontmcp build` warns.
265
+
266
+ ## `eject-mcp-config --out` merges
267
+
268
+ `--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.
269
+
252
270
  ## JSON Schema for IDE Support
253
271
 
254
272
  For JSON configs, add `$schema` for autocomplete:
@@ -260,10 +260,12 @@ http: {
260
260
  }
261
261
  ```
262
262
 
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. |
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 (Express host only). |
267
+
268
+ 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
269
 
268
270
  Requests exceeding the configured limit receive a structured JSON-RPC 413
269
271
  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 (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.)
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
@@ -246,7 +246,7 @@ throttle: {
246
246
 
247
247
  `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
248
 
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:
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:
250
250
 
251
251
  ```typescript
252
252
  storage: {
@@ -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` (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 |
95
95
 
96
96
  ## Target Comparison
97
97