@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.
- package/README.md +107 -155
- package/catalog/create-tool/SKILL.md +24 -24
- package/catalog/create-tool/examples/21-tool-with-availability-constraints.md +1 -1
- package/catalog/create-tool/examples/22-tool-with-ui-html-template.md +0 -1
- package/catalog/create-tool/examples/23-tool-with-ui-filesource-tsx.md +1 -3
- package/catalog/create-tool/examples/24-tool-with-ui-csp-and-bridge.md +4 -6
- package/catalog/create-tool/references/availability.md +10 -10
- package/catalog/create-tool/references/decorator-options.md +1 -1
- package/catalog/create-tool/references/ui-widgets.md +91 -42
- package/catalog/frontmcp-authorities/SKILL.md +5 -0
- package/catalog/frontmcp-channels/SKILL.md +17 -16
- package/catalog/frontmcp-channels/references/channel-sources.md +4 -4
- package/catalog/frontmcp-channels/references/channel-two-way.md +1 -1
- package/catalog/frontmcp-config/examples/configure-deployment-targets/distributed-ha-config.md +2 -0
- package/catalog/frontmcp-config/examples/configure-session/vercel-kv-session.md +1 -2
- package/catalog/frontmcp-config/examples/configure-throttle/distributed-redis-throttle.md +3 -3
- package/catalog/frontmcp-config/examples/configure-transport/custom-protocol-flags.md +0 -1
- package/catalog/frontmcp-config/examples/configure-transport/distributed-sessions-redis.md +0 -1
- package/catalog/frontmcp-config/examples/configure-transport/stateless-serverless.md +2 -3
- package/catalog/frontmcp-config/examples/configure-transport-protocol-presets/stateless-api-serverless.md +2 -3
- package/catalog/frontmcp-config/references/configure-auth.md +1 -1
- package/catalog/frontmcp-config/references/configure-deployment-targets.md +68 -16
- package/catalog/frontmcp-config/references/configure-http.md +11 -6
- package/catalog/frontmcp-config/references/configure-security-headers.md +18 -13
- package/catalog/frontmcp-config/references/configure-throttle-guard-config.md +4 -4
- package/catalog/frontmcp-config/references/configure-throttle.md +4 -2
- package/catalog/frontmcp-config/references/configure-transport.md +4 -5
- package/catalog/frontmcp-deployment/SKILL.md +19 -19
- package/catalog/frontmcp-deployment/examples/build-for-browser/react-provider-setup.md +5 -3
- package/catalog/frontmcp-deployment/examples/deploy-to-node/docker-compose-with-redis.md +1 -1
- package/catalog/frontmcp-deployment/examples/deploy-to-node/pm2-with-nginx.md +1 -1
- package/catalog/frontmcp-deployment/examples/deploy-to-vercel/vercel-with-kv.md +2 -0
- package/catalog/frontmcp-deployment/references/build-for-browser.md +46 -9
- package/catalog/frontmcp-deployment/references/build-for-mcpb.md +15 -8
- package/catalog/frontmcp-deployment/references/build-for-sdk.md +11 -10
- package/catalog/frontmcp-deployment/references/deploy-to-cloudflare.md +5 -1
- package/catalog/frontmcp-deployment/references/deploy-to-lambda.md +11 -10
- package/catalog/frontmcp-deployment/references/deploy-to-node.md +11 -11
- package/catalog/frontmcp-deployment/references/deploy-to-vercel.md +15 -7
- package/catalog/frontmcp-deployment/references/mcp-client-integration.md +12 -7
- package/catalog/frontmcp-deployment/references/protocol-versions.md +7 -3
- package/catalog/frontmcp-development/examples/create-agent/nested-agents-with-swarm.md +50 -10
- package/catalog/frontmcp-development/examples/openapi-adapter/ref-security-and-filtering.md +13 -7
- package/catalog/frontmcp-development/references/create-adapter.md +14 -0
- package/catalog/frontmcp-development/references/create-agent.md +82 -48
- package/catalog/frontmcp-development/references/create-plugin-hooks.md +15 -5
- package/catalog/frontmcp-development/references/create-plugin.md +23 -2
- package/catalog/frontmcp-development/references/create-skill-with-tools.md +7 -2
- package/catalog/frontmcp-development/references/create-skill.md +4 -0
- package/catalog/frontmcp-development/references/decorators-guide.md +10 -11
- package/catalog/frontmcp-development/references/official-adapters.md +1 -1
- package/catalog/frontmcp-development/references/official-plugins.md +138 -28
- package/catalog/frontmcp-development/references/openapi-adapter.md +52 -2
- package/catalog/frontmcp-guides/references/example-task-manager.md +2 -2
- package/catalog/frontmcp-guides/references/example-weather-api.md +2 -2
- package/catalog/frontmcp-observability/references/metrics-endpoint.md +3 -1
- package/catalog/frontmcp-production-readiness/examples/distributed-ha/ha-kubernetes-3-replicas.md +13 -1
- package/catalog/frontmcp-production-readiness/examples/production-node-sdk/package-json-config.md +2 -2
- package/catalog/frontmcp-production-readiness/references/common-checklist.md +3 -2
- package/catalog/frontmcp-production-readiness/references/distributed-ha.md +54 -24
- package/catalog/frontmcp-production-readiness/references/health-readiness-endpoints.md +3 -1
- package/catalog/frontmcp-setup/examples/nx-workflow/build-test-affected.md +2 -2
- package/catalog/frontmcp-setup/examples/nx-workflow/multi-server-deployment.md +9 -5
- package/catalog/frontmcp-setup/examples/nx-workflow/scaffold-and-generate.md +1 -1
- package/catalog/frontmcp-setup/examples/project-structure-nx/nx-generator-scaffolding.md +2 -2
- package/catalog/frontmcp-setup/references/frontmcp-skills-usage.md +28 -15
- package/catalog/frontmcp-setup/references/multi-app-composition.md +11 -8
- package/catalog/frontmcp-setup/references/nx-workflow.md +68 -28
- package/catalog/frontmcp-setup/references/project-structure-nx.md +7 -4
- package/catalog/frontmcp-setup/references/setup-project.md +15 -0
- package/catalog/frontmcp-setup/references/setup-redis.md +12 -3
- package/catalog/frontmcp-setup/references/setup-sqlite.md +21 -14
- package/catalog/frontmcp-testing/SKILL.md +28 -23
- package/catalog/frontmcp-testing/references/setup-testing.md +39 -2
- package/catalog/frontmcp-testing/references/test-auth.md +8 -0
- package/catalog/skills-manifest.json +14 -12
- 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
|
|
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
|
-
| `
|
|
135
|
-
| `
|
|
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
|
-
| `
|
|
412
|
-
|
|
|
413
|
-
|
|
|
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
|
|
150
|
-
|
|
|
151
|
-
| Jest not finding test files
|
|
152
|
-
| `SyntaxError: Unexpected token 'export'`
|
|
153
|
-
| Coverage below 95%
|
|
154
|
-
| E2E test timeout
|
|
155
|
-
| DI resolution fails in tests
|
|
156
|
-
| Istanbul shows 0% on async methods
|
|
157
|
-
| Specs see no `.env` values
|
|
158
|
-
| `Invalid first argument, true` at collection time
|
|
159
|
-
|
|
|
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.
|
|
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,
|
|
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", "
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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 `
|
|
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
|
|
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 `
|
|
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
|
|
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",
|