@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
@@ -167,8 +167,11 @@ nx g @frontmcp/nx:lib shared-utils
167
167
  ## Build and Test Commands
168
168
 
169
169
  ```bash
170
- # Build a specific server
171
- nx build gateway
170
+ # Build a specific server (server projects are named server-<name>)
171
+ nx build server-gateway
172
+
173
+ # Type-check a project
174
+ nx typecheck billing
172
175
 
173
176
  # Test a specific app
174
177
  nx test billing
@@ -233,8 +236,8 @@ Use `nx graph` to visualize the dependency graph and ensure no circular imports
233
236
 
234
237
  ### Build and Test
235
238
 
236
- - [ ] `nx build gateway` (or server name) succeeds without errors
237
- - [ ] `nx test billing` (or app name) passes all tests
239
+ - [ ] `nx build server-gateway` (or `server-<name>`) succeeds without errors
240
+ - [ ] `nx test billing` (or app name) passes all tests; new apps and libraries start with a spec
238
241
  - [ ] `nx run-many -t test` runs all tests across the workspace
239
242
  - [ ] `nx graph` shows no circular dependencies between apps
240
243
 
@@ -404,6 +404,19 @@ frontmcp dev --show-conflict
404
404
  > `http.port: 4000` in metadata makes the child bind to 4000 regardless of
405
405
  > `--port`/`PORT`, so the pre-flight probe becomes advisory only.
406
406
 
407
+ Ctrl+C, or `SIGINT` / `SIGTERM` sent to the `frontmcp dev` process alone
408
+ (`kill <pid>`), stops the whole tree — tsx, the server it forks and the type
409
+ checker — and the command returns once the port is free.
410
+
411
+ `frontmcp dev` and `frontmcp build` work from any subfolder: they find
412
+ `frontmcp.config.*` by walking up and run from its folder, so `entry`,
413
+ `tsconfig.json` and `.env` resolve from the project root.
414
+
415
+ `frontmcp init` edits `tsconfig.json` in place, keeping comments and trailing
416
+ commas; a file that is not valid JSON(C) is reported (line and column) and left
417
+ untouched, never overwritten. So is one that declares an option `init` must set
418
+ more than once — only the last occurrence takes effect, so remove the duplicate.
419
+
407
420
  Test with curl:
408
421
 
409
422
  ```bash
@@ -468,6 +481,8 @@ nx g @frontmcp/nx:app my-app --directory apps/my-app
468
481
  nx g @frontmcp/nx:server my-server --directory servers/my-server
469
482
  ```
470
483
 
484
+ This works in `create-nx-workspace --preset=ts` workspaces too: the generated project `tsconfig.json` compiles CommonJS JavaScript whatever the TS-solution base inherits (`composite`, `emitDeclarationOnly`, `customConditions`), resolving with `node10` on TypeScript 5 and `bundler` on TypeScript 6+, and library path aliases are written as `./libs/<name>/src/index.ts`, which needs no `baseUrl`.
485
+
471
486
  ### 7c. Nx project.json example
472
487
 
473
488
  If manually configuring, add a `project.json`:
@@ -138,6 +138,15 @@ redis: {
138
138
  },
139
139
  ```
140
140
 
141
+ From a single connection URL (what managed Redis providers hand out). It is read into `host` / `port` / `password` / `db` at parse time; `rediss://` sets `tls: true`; the user part must be empty or `default`; percent-encode reserved password characters (`@` as `%40`, `%` as `%25`), since a malformed escape is a validation error:
142
+
143
+ ```typescript
144
+ redis: {
145
+ url: process.env['REDIS_URL'], // redis://:password@host:6379/0 (or rediss:// for TLS)
146
+ keyPrefix: 'mcp:', // optional, as with the other forms
147
+ },
148
+ ```
149
+
141
150
  ### For Vercel KV
142
151
 
143
152
  ```typescript
@@ -313,13 +322,13 @@ You should see session keys like `mcp:session:<session-id>`.
313
322
 
314
323
  Every instance behind the load balancer needs the **same** values:
315
324
 
316
- - `MCP_SESSION_SECRET` -- session ids are encrypted with it. An id minted under a different secret is answered with HTTP 404 and the client re-initializes (every auth mode, including `public`, where the id is the caller's only credential). An anonymous session minted by one instance is honored by any instance with the same secret.
325
+ - `MCP_SESSION_SECRET` -- session ids are encrypted with it. An id minted under a different secret is answered with HTTP 404 and the client re-initializes (every auth mode, including `public`, where the id is the caller's only credential). An anonymous session minted by one instance is honored by any instance with the same secret: the receiving instance recreates the transport from the stored session (in distributed mode it relays the request to the live node that owns the session, or takes the session over from a node that stopped).
317
326
  - `VAULT_SECRET` (or `JWT_SECRET`) -- signs MCP 2026-07-28 `requestState`. Without either, each instance uses a random per-process key and a multi-round tool (`elicit()` / `sample()`) whose next round lands elsewhere asks its first question again. In production, `redis` or `transport.persistence` without either secret logs a startup warning; each rejected round logs `mcp-20260728: rejected requestState` with `reason: 'bad-signature'` and a `hint` naming `VAULT_SECRET`.
318
327
 
319
328
  ### What happens when Redis is down at startup
320
329
 
321
330
  - `redis` and `transport.persistence` fall back to in-memory storage and log the failure (`[TransportService] Failed to connect to redis - session persistence disabled`); the server starts.
322
- - `throttle.storage` fails closed: startup aborts with `GuardStorageUnavailableError` (`throttle.storage (redis) is unavailable: …`), the default in production. Opt in to per-instance counters explicitly:
331
+ - `throttle.storage` fails closed: startup aborts with `GuardStorageUnavailableError` (`throttle.storage (redis) is unavailable: …`), the default in production. If Redis goes away while the server runs, a rate-limited call is refused with the same error, not `Internal FrontMCP error` (HTTP 503 from the `global` check; an `isError` result with `_meta.code: 'GUARD_STORAGE_UNAVAILABLE'` inside `tools/call`). Opt in to per-instance counters explicitly (they also cover a mid-run outage):
323
332
 
324
333
  ```typescript
325
334
  throttle: {
@@ -332,7 +341,7 @@ throttle: {
332
341
  },
333
342
  ```
334
343
 
335
- `throttle.storage` takes the `@frontmcp/utils` storage shape (`{ type: 'redis', redis: { config } }` or `{ type: 'redis', redis: { url } }`), not the top-level `redis` shape.
344
+ `throttle.storage` takes the `@frontmcp/utils` storage shape (`{ type: 'redis', redis: { config } }` or `{ type: 'redis', redis: { url } }`), not the top-level `redis` shape (which takes `{ host, ... }` or `{ url }`).
336
345
 
337
346
  ## Common Patterns
338
347
 
@@ -77,6 +77,9 @@ interface SqliteOptionsInput {
77
77
 
78
78
  /** Interval in ms for purging expired keys (default: 60000) */
79
79
  ttlCleanupIntervalMs?: number;
80
+
81
+ /** Ms to wait for a lock held by another process before SQLITE_BUSY (default: 5000) */
82
+ busyTimeoutMs?: number;
80
83
  }
81
84
  ```
82
85
 
@@ -127,12 +130,13 @@ export default class Server {}
127
130
 
128
131
  Configuration reference:
129
132
 
130
- | Option | Type | Default | Description |
131
- | ---------------------- | -------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------- |
132
- | `path` | `string` | (optional) | Absolute or `~`-prefixed path to the `.sqlite` file. Auto-resolved when omitted — see the default-path policy below. |
133
- | `walMode` | `boolean` | `true` | Enable WAL mode for better read concurrency |
134
- | `encryption` | `{ secret: string }` | `undefined` | AES-256-GCM encryption for values at rest |
135
- | `ttlCleanupIntervalMs` | `number` | `60000` | Interval for purging expired keys (milliseconds) |
133
+ | Option | Type | Default | Description |
134
+ | ---------------------- | -------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
135
+ | `path` | `string` | (optional) | Absolute or `~`-prefixed path to the `.sqlite` file. Auto-resolved when omitted — see the default-path policy below. |
136
+ | `walMode` | `boolean` | `true` | Enable WAL mode for better read concurrency |
137
+ | `busyTimeoutMs` | `number` | `5000` | How long a connection waits on a lock held by another process before `SQLITE_BUSY`. Set before WAL and table creation, so two processes starting on one file wait for each other |
138
+ | `encryption` | `{ secret: string }` | `undefined` | AES-256-GCM encryption for values at rest |
139
+ | `ttlCleanupIntervalMs` | `number` | `60000` | Interval for purging expired keys (milliseconds) |
136
140
 
137
141
  ### With at-rest encryption
138
142
 
@@ -155,6 +159,8 @@ If the database stores sensitive session data (tokens, credentials), enable encr
155
159
  export default class Server {}
156
160
  ```
157
161
 
162
+ Reading a value written under a different secret throws `SqliteDecryptionError` (code `SQLITE_DECRYPTION_FAILED`) whose message names the secret as the likely cause; restore the original secret or delete the database file.
163
+
158
164
  The encryption uses HKDF-SHA256 for key derivation and AES-256-GCM for value encryption. The secret should be at least 32 characters. Store it in environment variables, never in source code.
159
165
 
160
166
  ### For a unix-socket daemon
@@ -403,14 +409,15 @@ The change in `src/main.ts`:
403
409
 
404
410
  ## Troubleshooting
405
411
 
406
- | Problem | Cause | Solution |
407
- | --------------------------------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
408
- | `Cannot find module 'better-sqlite3'` | Native module not installed | Run `yarn add @frontmcp/storage-sqlite better-sqlite3` |
409
- | `Could not locate the bindings file` | Native compilation failed | Ensure build tools are installed (Xcode CLI on macOS, `build-essential` on Linux), delete `node_modules` and reinstall |
410
- | `SQLITE_BUSY` errors | Multiple processes accessing the same database file | Enable WAL mode (`walMode: true`) or ensure only one process writes to the database |
411
- | `SQLITE_READONLY` | Insufficient file permissions | Check write permissions on the database file and its parent directory |
412
- | WAL errors on network mount | WAL mode requires a local filesystem with shared-memory support | Move the database to a local disk or set `walMode: false` |
413
- | Encrypted data unreadable after restart | Encryption secret changed or missing | The secret must be identical across restarts; if the original secret is lost, delete the database and let it be recreated |
412
+ | Problem | Cause | Solution |
413
+ | --------------------------------------- | --------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
414
+ | `Cannot find module 'better-sqlite3'` | Native module not installed | Run `yarn add @frontmcp/storage-sqlite better-sqlite3` |
415
+ | `Could not locate the bindings file` | Native compilation failed | Ensure build tools are installed (Xcode CLI on macOS, `build-essential` on Linux), delete `node_modules` and reinstall |
416
+ | `SQLITE_BUSY` errors | Multiple processes accessing the same database file | Enable WAL mode (`walMode: true`), raise `busyTimeoutMs` (default 5000), or ensure only one process writes to the database |
417
+ | `Failed to persist session to SQLite` | A session write waited longer than `busyTimeoutMs` for the lock | Raise `busyTimeoutMs`, or use Redis for many writer processes |
418
+ | `SQLITE_READONLY` | Insufficient file permissions | Check write permissions on the database file and its parent directory |
419
+ | WAL errors on network mount | WAL mode requires a local filesystem with shared-memory support | Move the database to a local disk or set `walMode: false` |
420
+ | Encrypted data unreadable after restart | Encryption secret changed or missing | The secret must be identical across restarts; if the original secret is lost, delete the database and let it be recreated |
414
421
 
415
422
  ## Examples
416
423
 
@@ -95,18 +95,18 @@ This is a router skill. Follow this order to pick a testing approach, then move
95
95
 
96
96
  ## Cross-Cutting Testing Patterns
97
97
 
98
- | Pattern | Rule |
99
- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
100
- | File naming | Always `.spec.ts` (not `.test.ts`); E2E uses `.e2e.spec.ts` |
101
- | File organization | Split E2E tests by app/feature: `e2e/calc.e2e.spec.ts`, `e2e/ecommerce.e2e.spec.ts`. Never put all tests in a single `server.e2e.spec.ts` |
102
- | Environment | `frontmcp test` loads `.env` / `.env.local` into the Jest child, same precedence as `dev` (real environment wins over the files, config `env.shared` + `env.test` underneath). `--no-env` skips it |
103
- | Test runner | Standalone projects: use `frontmcp test` (auto-generates Jest/SWC config; discovers `src/**/*.spec.ts(x)`, `__tests__/**/*.spec.ts(x)`, and `e2e/**/*.e2e.spec.ts(x)`; transforms both `.ts` and `.tsx` with the automatic JSX runtime; transpiles ESM-only deps such as `jose` under npm, yarn AND pnpm's `node_modules/.pnpm/` store — add your own via `test.esmPackages` in `frontmcp.config.ts`; delegates to a user-provided `jest.config.{ts,js,mjs,cjs,json}` if present, which drops the injected ESM transforms). Nx monorepos: use `nx test <lib>` (resolves the project's `jest.config.ts`). Never invoke `jest --config ...` directly |
104
- | Coverage threshold | 95%+ across statements, branches, functions, lines |
105
- | Test descriptions | Plain English, no prefixes like "PT-001"; describe behavior not implementation |
106
- | Mocking | Mock providers via DI token replacement, never mock the framework |
107
- | httpMock scope | `httpMock` intercepts HTTP in the **test process** only, NOT in the MCP server subprocess. Do not use httpMock to intercept server-to-API calls — those happen in the child process. Use httpMock for verifying client-to-server request shapes or mocking external APIs called from the test itself |
108
- | Error testing | Assert `instanceof` specific error class AND MCP error code |
109
- | Async | Always `await` async operations; use `expect(...).rejects.toThrow()` for async errors |
98
+ | Pattern | Rule |
99
+ | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
100
+ | File naming | Always `.spec.ts` (not `.test.ts`); E2E uses `.e2e.spec.ts` |
101
+ | File organization | Split E2E tests by app/feature: `e2e/calc.e2e.spec.ts`, `e2e/ecommerce.e2e.spec.ts`. Never put all tests in a single `server.e2e.spec.ts` |
102
+ | Environment | `frontmcp test` loads `.env` / `.env.local` into the Jest child, same precedence as `dev` (real environment wins over the files, config `env.shared` + `env.test` underneath). `--no-env` skips it |
103
+ | Test runner | Standalone projects: use `frontmcp test` (auto-generates Jest/SWC config; discovers `src/**/*.spec.ts(x)`, `__tests__/**/*.spec.ts(x)`, and `e2e/**/*.e2e.spec.ts(x)`; transforms both `.ts` and `.tsx` with the automatic JSX runtime; transpiles ESM-only deps such as `jose`, `@noble/hashes` and `@noble/ciphers` (CodeCall) under npm, yarn AND pnpm's `node_modules/.pnpm/` store — add your own via `test.esmPackages` in `frontmcp.config.ts`; does not force `NODE_OPTIONS=--experimental-vm-modules` (it would bypass the ESM transforms; scaffolded projects use Jest 30); delegates to a user-provided `jest.config.{ts,js,mjs,cjs,json}` if present, which drops the injected ESM transforms). Nx monorepos: use `nx test <lib>` (resolves the project's `jest.config.ts`). Never invoke `jest --config ...` directly |
104
+ | Coverage threshold | 95%+ across statements, branches, functions, lines |
105
+ | Test descriptions | Plain English, no prefixes like "PT-001"; describe behavior not implementation |
106
+ | Mocking | Mock providers via DI token replacement, never mock the framework |
107
+ | httpMock scope | `httpMock` intercepts HTTP in the **test process** only, NOT in the MCP server subprocess. Do not use httpMock to intercept server-to-API calls — those happen in the child process. Use httpMock for verifying client-to-server request shapes or mocking external APIs called from the test itself |
108
+ | Error testing | Assert `instanceof` specific error class AND MCP error code |
109
+ | Async | Always `await` async operations; use `expect(...).rejects.toThrow()` for async errors |
110
110
 
111
111
  ## Common Patterns
112
112
 
@@ -146,17 +146,22 @@ This is a router skill. Follow this order to pick a testing approach, then move
146
146
 
147
147
  ## Troubleshooting
148
148
 
149
- | Problem | Cause | Solution |
150
- | --------------------------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
151
- | Jest not finding test files | Wrong file extension (`.test.ts` instead of `.spec.ts`) | Rename to `.spec.ts`; check `testMatch` in jest.config |
152
- | `SyntaxError: Unexpected token 'export'` | An ESM-only dependency is being ignored instead of transpiled | Add it to `test.esmPackages` in `frontmcp.config.ts`. With a hand-written `jest.config.ts`, use the pnpm-safe `transformIgnorePatterns` in [`setup-testing`](./references/setup-testing.md#jest-configuration) AND make sure `transform` matches `.js` (`^.+\.[tj]sx?$` + `allowJs`) — un-ignoring a file does nothing if no transform matches it |
153
- | Coverage below 95% | Untested error paths or conditional branches | Run `frontmcp test --coverage` and inspect uncovered lines in the report |
154
- | E2E test timeout | Server startup too slow or port conflict | Increase Jest timeout; use random port allocation |
155
- | DI resolution fails in tests | Provider not registered in test scope | Register mock providers before creating the test context |
156
- | Istanbul shows 0% on async methods | TypeScript source-map mismatch with Istanbul | Known issue with some TS compilation settings; verify coverage with actual test output |
157
- | Specs see no `.env` values | An older CLI did not load `.env` for `frontmcp test`, only for `dev` | Upgrade the CLI — `frontmcp test` now loads `.env` / `.env.local` with the same precedence as `dev` (real environment wins, so CI secrets still override). Pass `--no-env` for a hermetic run |
158
- | `Invalid first argument, true` at collection time | `test.skip(condition, reason)` reached Jest's `skip(name, fn)` | Upgrade the CLI — the Playwright signature is supported: `test.skip(!hasCredentials, 'credentials not set')` skips every test registered after it in the enclosing block |
159
- | Every spec fails with `HTTP 404` after setting `http.entryPath` | The test client always connected to the server root | Upgrade the CLI — the client now follows the `entryPaths` a 404 reports, and `test.use({ entryPath: '/mcp' })` sets it explicitly |
149
+ | Problem | Cause | Solution |
150
+ | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
151
+ | Jest not finding test files | Wrong file extension (`.test.ts` instead of `.spec.ts`) | Rename to `.spec.ts`; check `testMatch` in jest.config |
152
+ | `SyntaxError: Unexpected token 'export'` | An ESM-only dependency is being ignored instead of transpiled | Add it to `test.esmPackages` in `frontmcp.config.ts`. With a hand-written `jest.config.ts`, use the pnpm-safe `transformIgnorePatterns` in [`setup-testing`](./references/setup-testing.md#jest-configuration) AND make sure `transform` matches `.js` (`^.+\.[tj]sx?$` + `allowJs`) — un-ignoring a file does nothing if no transform matches it |
153
+ | Coverage below 95% | Untested error paths or conditional branches | Run `frontmcp test --coverage` and inspect uncovered lines in the report |
154
+ | E2E test timeout | Server startup too slow or port conflict | Increase Jest timeout; use random port allocation |
155
+ | DI resolution fails in tests | Provider not registered in test scope | Register mock providers before creating the test context |
156
+ | Istanbul shows 0% on async methods | TypeScript source-map mismatch with Istanbul | Known issue with some TS compilation settings; verify coverage with actual test output |
157
+ | Specs see no `.env` values | An older CLI did not load `.env` for `frontmcp test`, only for `dev` | Upgrade the CLI — `frontmcp test` now loads `.env` / `.env.local` with the same precedence as `dev` (real environment wins, so CI secrets still override). Pass `--no-env` for a hermetic run |
158
+ | `Invalid first argument, true` at collection time | `test.skip(condition, reason)` reached Jest's `skip(name, fn)` | Upgrade the CLI — the Playwright signature is supported: `test.skip(!hasCredentials, 'credentials not set')` skips every test registered after it in the enclosing block |
159
+ | `test.use()` in one `describe` leaks into another, or two files fight over a port | Older `@frontmcp/testing` kept one global config and per-process ports | Upgrade — `test.use()` is scoped per `describe` and ports are locked across Jest workers; use `port: 0` |
160
+ | Every spec fails with `HTTP 404` after setting `http.entryPath` | The test client always connected to the server root | Upgrade the CLI — the client now follows the `entryPaths` a 404 reports, and `test.use({ entryPath: '/mcp' })` sets it explicitly |
161
+ | `test.beforeEach(async ({ mcp }) => …)` times out waiting for `done` | Older `@frontmcp/testing` handed the hook to Jest, which read the parameter as `done` | Upgrade — hooks that take a parameter now receive the test's fixtures (same `mcp` as the test); `beforeAll` / `afterAll` never get fixtures |
162
+ | `test.use({ transport: 'sse' })` throws "SSE transport not yet implemented" | Older `@frontmcp/testing` had no legacy SSE client | Upgrade — `'sse'` is the legacy HTTP+SSE transport; the server must enable it with `transport: { protocol: { legacy: true } }` |
163
+ | `Module @swc/jest in the transform option was not found` from `frontmcp test` | Older `@frontmcp/testing` did not install the transformer the injected config uses | Upgrade — `@frontmcp/testing` depends on `@swc/jest` / `@swc/core`, and `frontmcp test` resolves the transformer through it |
164
+ | `ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG` from a server started inside a spec | Older SDKs loaded the optional `vectoriadb` with `import()` at boot | Upgrade the SDK — `vectoriadb` loads on the first skill search, through `require` where available; no `--experimental-vm-modules` needed |
160
165
 
161
166
  ## Examples
162
167
 
@@ -396,6 +396,37 @@ You usually do not need to set this: when the client's first request 404s and th
396
396
 
397
397
  `baseUrl` may also be supplied alongside `server` to override the booted server's own URL (for a proxy, or a different host than it binds).
398
398
 
399
+ ### Scoping config, parameterised tests, ports and restarts
400
+
401
+ - `test.use()` is scoped to the `describe` block it is called in. Nested blocks merge over outer ones (`env` key by key); each distinct configuration gets its own server, stopped by the block that configured it. At file level it applies to the whole file.
402
+ - The `env` object is read when the server starts, so a `beforeAll` may fill in values known only later (e.g. the port of an upstream `TestServer` it started).
403
+ - `test.beforeEach` / `test.afterEach` callbacks that take a parameter get the test's fixtures, Playwright-style — the **same** `mcp` / `server` / `auth` as the test. `beforeEach` runs outer blocks first, `afterEach` inner blocks first, and fixtures are torn down after the last `afterEach`. A callback without parameters is a plain Jest hook; `test.beforeAll` / `test.afterAll` never receive fixtures. Jest's `done` style is not supported on `test.beforeEach` / `test.afterEach`.
404
+
405
+ ```typescript
406
+ test.beforeEach(async ({ mcp }) => {
407
+ await mcp.tools.call('reset_state', {});
408
+ });
409
+ ```
410
+
411
+ - `transport: 'sse'` (in `test.use()` or `server.createClient()`) uses the legacy HTTP+SSE transport: `GET <entryPath>/sse`, messages POSTed to the endpoint the server names, responses read from the stream. The server must enable it (`transport: { protocol: { legacy: true } }`); otherwise connecting fails with the HTTP status.
412
+ - `port: 0` (or omitting `port`) picks any free port. Ports are reserved with a cross-process lock, so parallel Jest workers do not collide.
413
+ - `auth: { mode, type }` reaches the server process as `FRONTMCP_TEST_AUTH_MODE` / `FRONTMCP_TEST_AUTH_TYPE`; `mode: 'public'` also keeps the `mcp` client anonymous.
414
+ - `test.each` / `test.describe.each` pass the row values to the callback (fixtures first for `test.each`):
415
+
416
+ ```typescript
417
+ test.each([
418
+ ['read', 1],
419
+ ['write', 2],
420
+ ])('scope %s', async ({ mcp }, scope, level) => {
421
+ expect(mcp.isConnected()).toBe(true);
422
+ });
423
+ ```
424
+
425
+ - `server.info.pid` is the pid of the process that listens on the port. `server.restart()` reconnects `mcp` and every client made with `server.createClient()`.
426
+ - `mcp.logs` holds the log notifications the client received; `server.getLogs()` holds the server process output. They are different sources.
427
+ - `mcp.intercept.failMethod(method, message)` makes matching calls reject with `message`; the returned function removes it.
428
+ - `httpMock` patches `fetch` in the test process only (see above) and `interceptor.restore()` puts the original `fetch` back.
429
+
399
430
  ### Gating a block on credentials
400
431
 
401
432
  `test.skip(condition, reason)` is the Playwright signature and skips every test registered after it in the enclosing block:
@@ -494,7 +525,13 @@ matches at the first `node_modules/`, and the run fails with
494
525
  `SyntaxError: Unexpected token 'export'`.
495
526
 
496
527
  In standalone projects driven by `frontmcp test`, prefer `test.esmPackages` in
497
- `frontmcp.config.ts` — the injected config already carries the pattern above:
528
+ `frontmcp.config.ts` — the injected config already carries the pattern above,
529
+ and already transpiles `jose`, `@noble/hashes` and `@noble/ciphers` (CodeCall).
530
+ `frontmcp test` does not set `NODE_OPTIONS=--experimental-vm-modules` (it would make Jest
531
+ load ESM natively and bypass these transforms); use Jest 30 (what `frontmcp create` scaffolds).
532
+ A server created inside the test process (`FrontMcpInstance.createHandler()`, `createDirect()`,
533
+ `create()`) works without the flag too. `@frontmcp/testing` brings `@swc/jest` and `@swc/core`, so a
534
+ bare project needs only `@frontmcp/testing` and `jest` installed:
498
535
 
499
536
  ```typescript
500
537
  // frontmcp.config.ts
@@ -607,7 +644,7 @@ node scripts/fix-unused-imports.mjs feature/my-branch
607
644
  | `toContainTool` matcher not found | Using `expect` from Jest instead of `@frontmcp/testing` | Import `expect` from `@frontmcp/testing` to get MCP-specific matchers |
608
645
  | `McpTestClient.create()` connection refused | Test server not running or wrong `baseUrl` | Ensure `TestServer.start()` completes before creating client; verify port matches |
609
646
  | Istanbul shows 0% coverage for async methods | TypeScript compilation source-map mismatch | Known issue with `ts-jest` and certain async patterns; check `tsconfig.spec.json` source-map settings |
610
- | Auth E2E test returns 401 unexpectedly | Token not set or expired | Call `mcp.setAuthToken(token)` before the tool call; use `auth.createToken()` with valid claims |
647
+ | Auth E2E test returns 401 unexpectedly | Token not set or expired | Call `await mcp.authenticate(token)` before the tool call; use `auth.createToken()` with valid claims |
611
648
 
612
649
  ## Examples
613
650
 
@@ -94,6 +94,14 @@ describe('Authenticated Server', () => {
94
94
  });
95
95
  ```
96
96
 
97
+ ## Token semantics
98
+
99
+ - Default issuer `https://test.frontmcp.local`, default audience `frontmcp-test` (override with `new TestTokenFactory({ issuer, audience })`).
100
+ - `expiresIn` is counted from now and rounded up to whole seconds, so `expiresIn: 1` is valid for at least one second. `createAnonymousToken(expiresIn?)` takes a lifetime too.
101
+ - `await mcp.authenticate(token)` opens a new session for the token and **rejects** if the server refuses it (expired, bad signature); the client then keeps its previous identity. On an unconnected client it only stores the token. 401/403 are never retried.
102
+ - Tools read token scopes through `this.auth` (e.g. `this.auth.scopes`).
103
+ - Access tokens from `MockOAuthServer` carry the granted `scope` claim and `exp = accessTokenTtlSeconds`, also after a refresh.
104
+
97
105
  ## Mock OAuth / OIDC server
98
106
 
99
107
  `MockOAuthServer` serves JWKS, OAuth metadata, authorization, token, and userinfo endpoints. Construct it with a `TestTokenFactory`, call `.start()` to bind a port, and configure your MCP server to point at the returned `info.issuer` / `info.jwksUrl`.
@@ -74,7 +74,7 @@
74
74
  },
75
75
  {
76
76
  "name": "ui-widgets",
77
- "description": "@Tool({ ui }) \u2014 template formats, trusted markup (html / escapeStringResults), servingMode, host-detect resourceMode, CSP, widgetAccessible, MCP Apps spec."
77
+ "description": "@Tool({ ui }) \u2014 template formats, trusted markup (html / escapeStringResults), servingMode, host-detect resourceMode, CSP, ignored options, MCP Apps spec."
78
78
  },
79
79
  {
80
80
  "name": "annotations",
@@ -362,10 +362,10 @@
362
362
  "name": "24-tool-with-ui-csp-and-bridge",
363
363
  "level": "advanced",
364
364
  "description": "Interactive tool widget that fetches from an allow-listed CSP origin and invokes another tool via `window.FrontMcpBridge.callTool` \u2014 the full pattern for live-data widgets that need cross-tool composition.",
365
- "tags": ["ui", "csp", "widgetAccessible", "FrontMcpBridge", "interactive-widget"],
365
+ "tags": ["ui", "csp", "callTool", "FrontMcpBridge", "interactive-widget"],
366
366
  "features": [
367
367
  "Restricting the widget's outbound `fetch` via `ui.csp.connectDomains` (emitted on the resource per #455)",
368
- "Opting the widget into cross-tool calls with `widgetAccessible: true` and using `window.FrontMcpBridge.callTool(name, args)` instead of host-specific APIs",
368
+ "Calling other tools from the widget with `window.FrontMcpBridge.callTool(name, args)` instead of host-specific APIs",
369
369
  "Building the markup with `ctx.helpers.html` and embedding initial data into the inline `<script>` via `trustedHtml(jsonEmbed(...))` (`jsonEmbed` escapes `<`, `>` and `&`)",
370
370
  "Surfacing in-flight status via `invocationStatus.invoking` / `invoked` so the host UI shows feedback"
371
371
  ]
@@ -944,7 +944,7 @@
944
944
  "Using `keyPrefix` to namespace guard keys in a shared Redis instance",
945
945
  "Combining `partitionBy: 'ip'` for global limits with `partitionBy: 'session'` per tool",
946
946
  "In-memory counters are per-process and would allow N times the intended rate with N instances",
947
- "Startup fails closed with `GuardStorageUnavailableError` when Redis is down, unless `fallback: 'memory'`"
947
+ "Startup fails closed, and a limited call is refused, with `GuardStorageUnavailableError` when Redis is down, unless `fallback: 'memory'`"
948
948
  ]
949
949
  },
950
950
  {
@@ -998,7 +998,7 @@
998
998
  "features": [
999
999
  "The `'stateless-api'` preset disables SSE, streaming, and sessions entirely",
1000
1000
  "Each request is standalone with no server-side state",
1001
- "Pair with `sessionMode: 'stateless'` for serverless execution",
1001
+ "No `sessionMode` needed: sessions follow the protocol preset",
1002
1002
  "Required for Vercel, Lambda, Cloudflare Workers where persistent connections are not allowed"
1003
1003
  ]
1004
1004
  }
@@ -1040,7 +1040,7 @@
1040
1040
  "level": "basic",
1041
1041
  "tags": ["config", "vercel", "lambda", "cloudflare", "session", "transport"],
1042
1042
  "features": [
1043
- "Using `sessionMode: 'stateless'` to disable session management",
1043
+ "Serving without sessions: whether the server keeps sessions follows `transport.protocol` (`sessionMode` has no effect)",
1044
1044
  "Using the `'stateless-api'` preset: no SSE, no streaming, pure request/response",
1045
1045
  "Each request is standalone with no server-side state between invocations",
1046
1046
  "Required for serverless targets (Vercel, Lambda, Cloudflare Workers)"
@@ -1619,13 +1619,15 @@
1619
1619
  },
1620
1620
  {
1621
1621
  "name": "nested-agents-with-swarm",
1622
- "description": "Composing specialized agents into a swarm where an orchestrator can discover and call peers at runtime as `use-agent:<id>` tools. Routing is driven by the orchestrator's LLM, not a declarative handoff table.",
1622
+ "description": "Composing specialized agents into a swarm where an orchestrator can discover and call peers at runtime as `invoke_<id>` tools, plus a nested sub-agent and `this.invokeAgent()`. Routing is driven by the orchestrator's LLM, not a declarative handoff table.",
1623
1623
  "level": "advanced",
1624
1624
  "tags": ["development", "agent", "nested", "agents", "swarm"],
1625
1625
  "features": [
1626
- "Setting `swarm: { canSeeOtherAgents: true, visibleAgents: [...] }` on the orchestrator so peers appear as `use-agent:*` tools",
1626
+ "Setting `swarm: { canSeeOtherAgents: true, visibleAgents: [...] }` on the orchestrator so peers are offered to its model as `invoke_*` tools",
1627
1627
  "Setting `swarm: { isVisible: true }` (the default) on specialist peers so they can be called",
1628
- "Routing is driven by the orchestrator LLM choosing among `use-agent:<peer>` tools, not by a declarative handoff table",
1628
+ "Routing is driven by the orchestrator LLM choosing among `invoke_<peer>` tools, not by a declarative handoff table",
1629
+ "A nested sub-agent (`agents: [...]`) private to the orchestrator, called from code with `this.invokeAgent()`",
1630
+ "`swarm.maxCallDepth` bounding how deep agents may call each other",
1629
1631
  "Each agent has its own `llm` config, `tools`, and `systemInstructions` for specialization"
1630
1632
  ]
1631
1633
  }
@@ -2061,7 +2063,7 @@
2061
2063
  },
2062
2064
  {
2063
2065
  "name": "official-plugins",
2064
- "description": "Guide to the 6 official plugins for discovery, memory, auth, caching, flags, and monitoring",
2066
+ "description": "Guide to the 7 official plugins for discovery, memory, auth, caching, flags, monitoring, and WebMCP",
2065
2067
  "examples": [
2066
2068
  {
2067
2069
  "name": "cache-and-feature-flags",
@@ -2173,7 +2175,7 @@
2173
2175
  "Secure defaults: external `$ref` resolution off, spec-URL redirects not followed, internal/private targets blocked (DNS-resolved) \u2014 on `mcp-from-openapi` >= 2.5.0",
2174
2176
  "Opting back into external refs with `allowedProtocols`, and restricting the spec URL + `$ref`s with `allowedHosts`",
2175
2177
  "Using `allowInternalIPs` for trusted internal/local targets (governs the spec URL and `$ref`s)",
2176
- "Filtering operations with `includeOperations`, `excludeOperations`, and `filterFn`",
2178
+ "Filtering operations with `readOnlyOnly`, `includeTags`, `includePaths`, `excludeMethods`, `includeOperations`, `excludeOperations`, and `filterFn`",
2177
2179
  "Combining security hardening with operation filtering for a production-ready setup"
2178
2180
  ]
2179
2181
  }
@@ -2474,7 +2476,7 @@
2474
2476
  },
2475
2477
  {
2476
2478
  "name": "distributed-ha",
2477
- "description": "Deploy FrontMCP across multiple pods with heartbeat, session takeover, and notification relay for zero-downtime failover",
2479
+ "description": "Deploy FrontMCP across multiple pods with heartbeat, cross-pod request relay, session takeover, and notification relay for zero-downtime failover",
2478
2480
  "examples": [
2479
2481
  {
2480
2482
  "name": "ha-kubernetes-3-replicas",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/skills",
3
- "version": "1.8.6",
3
+ "version": "1.9.0",
4
4
  "description": "Curated skills catalog for FrontMCP projects",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "homepage": "https://docs.agentfront.dev",