@frontmcp/skills 1.8.7 → 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 (62) hide show
  1. package/README.md +107 -155
  2. package/catalog/create-tool/examples/21-tool-with-availability-constraints.md +1 -1
  3. package/catalog/create-tool/references/availability.md +10 -10
  4. package/catalog/create-tool/references/ui-widgets.md +30 -8
  5. package/catalog/frontmcp-authorities/SKILL.md +5 -0
  6. package/catalog/frontmcp-channels/SKILL.md +17 -16
  7. package/catalog/frontmcp-channels/references/channel-sources.md +4 -4
  8. package/catalog/frontmcp-channels/references/channel-two-way.md +1 -1
  9. package/catalog/frontmcp-config/examples/configure-deployment-targets/distributed-ha-config.md +2 -0
  10. package/catalog/frontmcp-config/examples/configure-session/vercel-kv-session.md +1 -2
  11. package/catalog/frontmcp-config/examples/configure-transport/custom-protocol-flags.md +0 -1
  12. package/catalog/frontmcp-config/examples/configure-transport/distributed-sessions-redis.md +0 -1
  13. package/catalog/frontmcp-config/examples/configure-transport/stateless-serverless.md +2 -3
  14. package/catalog/frontmcp-config/examples/configure-transport-protocol-presets/stateless-api-serverless.md +2 -3
  15. package/catalog/frontmcp-config/references/configure-auth.md +1 -1
  16. package/catalog/frontmcp-config/references/configure-deployment-targets.md +54 -20
  17. package/catalog/frontmcp-config/references/configure-http.md +5 -2
  18. package/catalog/frontmcp-config/references/configure-throttle-guard-config.md +1 -1
  19. package/catalog/frontmcp-config/references/configure-throttle.md +4 -2
  20. package/catalog/frontmcp-config/references/configure-transport.md +4 -5
  21. package/catalog/frontmcp-deployment/SKILL.md +19 -19
  22. package/catalog/frontmcp-deployment/examples/deploy-to-node/docker-compose-with-redis.md +1 -1
  23. package/catalog/frontmcp-deployment/examples/deploy-to-node/pm2-with-nginx.md +1 -1
  24. package/catalog/frontmcp-deployment/references/build-for-browser.md +38 -9
  25. package/catalog/frontmcp-deployment/references/build-for-mcpb.md +15 -9
  26. package/catalog/frontmcp-deployment/references/build-for-sdk.md +2 -1
  27. package/catalog/frontmcp-deployment/references/deploy-to-cloudflare.md +3 -1
  28. package/catalog/frontmcp-deployment/references/deploy-to-lambda.md +2 -2
  29. package/catalog/frontmcp-deployment/references/deploy-to-node.md +11 -11
  30. package/catalog/frontmcp-deployment/references/mcp-client-integration.md +12 -7
  31. package/catalog/frontmcp-deployment/references/protocol-versions.md +7 -3
  32. package/catalog/frontmcp-development/examples/create-agent/nested-agents-with-swarm.md +50 -10
  33. package/catalog/frontmcp-development/examples/openapi-adapter/ref-security-and-filtering.md +13 -7
  34. package/catalog/frontmcp-development/references/create-adapter.md +14 -0
  35. package/catalog/frontmcp-development/references/create-agent.md +82 -49
  36. package/catalog/frontmcp-development/references/create-plugin-hooks.md +15 -5
  37. package/catalog/frontmcp-development/references/create-plugin.md +8 -4
  38. package/catalog/frontmcp-development/references/create-skill-with-tools.md +7 -2
  39. package/catalog/frontmcp-development/references/create-skill.md +4 -0
  40. package/catalog/frontmcp-development/references/decorators-guide.md +10 -11
  41. package/catalog/frontmcp-development/references/official-adapters.md +1 -1
  42. package/catalog/frontmcp-development/references/official-plugins.md +127 -24
  43. package/catalog/frontmcp-development/references/openapi-adapter.md +52 -2
  44. package/catalog/frontmcp-observability/references/metrics-endpoint.md +3 -1
  45. package/catalog/frontmcp-production-readiness/examples/distributed-ha/ha-kubernetes-3-replicas.md +12 -1
  46. package/catalog/frontmcp-production-readiness/references/common-checklist.md +2 -1
  47. package/catalog/frontmcp-production-readiness/references/distributed-ha.md +48 -28
  48. package/catalog/frontmcp-setup/examples/nx-workflow/build-test-affected.md +2 -2
  49. package/catalog/frontmcp-setup/examples/nx-workflow/multi-server-deployment.md +9 -5
  50. package/catalog/frontmcp-setup/examples/nx-workflow/scaffold-and-generate.md +1 -1
  51. package/catalog/frontmcp-setup/examples/project-structure-nx/nx-generator-scaffolding.md +1 -1
  52. package/catalog/frontmcp-setup/references/frontmcp-skills-usage.md +28 -15
  53. package/catalog/frontmcp-setup/references/multi-app-composition.md +11 -8
  54. package/catalog/frontmcp-setup/references/nx-workflow.md +53 -21
  55. package/catalog/frontmcp-setup/references/project-structure-nx.md +7 -4
  56. package/catalog/frontmcp-setup/references/setup-project.md +15 -0
  57. package/catalog/frontmcp-setup/references/setup-redis.md +12 -3
  58. package/catalog/frontmcp-setup/references/setup-sqlite.md +12 -8
  59. package/catalog/frontmcp-testing/SKILL.md +16 -12
  60. package/catalog/frontmcp-testing/references/setup-testing.md +14 -1
  61. package/catalog/skills-manifest.json +10 -8
  62. package/package.json +1 -1
@@ -29,6 +29,8 @@ class Server {}
29
29
 
30
30
  Then scrape: `curl http://localhost:3000/metrics` (the default port is `PORT`, else 3000) — Content-Type is the canonical Prometheus `text/plain; version=0.0.4; charset=utf-8`.
31
31
 
32
+ `FrontMcpInstance.createFetchHandler(config)` (the Web-standard `(Request) => Response` handler) answers `GET /metrics` too — same body, auth and headers as the Express listener (it needs `@frontmcp/observability` installed, like the Express endpoint).
33
+
32
34
  ## Configuration
33
35
 
34
36
  ```typescript
@@ -105,7 +107,7 @@ Keep label values bounded (status codes, enum members, tool names) — unbounded
105
107
 
106
108
  ## Path conflict guard
107
109
 
108
- `metrics.path` MUST NOT collide with MCP transport paths (`/mcp`, `/sse`, `/messages`). The service constructor throws `MetricsPathConflictError` at startup if it detects an overlap.
110
+ `metrics.path` MUST NOT collide with MCP transport paths (`/mcp`, `/sse`, `/messages`). The service constructor throws `MetricsPathConflictError` at startup if it detects an overlap. `createFetchHandler()` also throws `MetricsPathConflictError` when `metrics.path` equals its MCP entry path (`http.entryPath`, `/` when unset), since the metrics route would otherwise answer the MCP `GET` that opens the event stream.
109
111
 
110
112
  ## Common Patterns
111
113
 
@@ -135,6 +135,12 @@ spec:
135
135
  value: distributed
136
136
  - name: REDIS_HOST
137
137
  value: redis
138
+ # Same secret on every pod: session ids are encrypted with it.
139
+ - name: MCP_SESSION_SECRET
140
+ valueFrom:
141
+ secretKeyRef:
142
+ name: mcp-server
143
+ key: session-secret
138
144
  ports:
139
145
  - containerPort: 3000
140
146
  livenessProbe:
@@ -211,9 +217,14 @@ kubectl exec -it deploy/redis -- redis-cli KEYS "mcp:ha:heartbeat:*"
211
217
  # 2) "mcp:ha:heartbeat:mcp-server-7b8f9-def34"
212
218
  # 3) "mcp:ha:heartbeat:mcp-server-7b8f9-ghi56"
213
219
 
220
+ # Every pod listens on its relay channel: a request that reaches a pod which
221
+ # does not own the session is relayed to the owner and answered from there.
222
+ kubectl exec -it deploy/redis -- redis-cli PUBSUB CHANNELS "mcp:ha:notify:*"
223
+
214
224
  # Kill a pod and watch takeover
215
225
  kubectl delete pod mcp-server-7b8f9-abc12
216
- # After ~30s, its heartbeat expires and sessions are claimed by surviving pods
226
+ # Until its heartbeat expires (~30s) its sessions answer 503 + Retry-After;
227
+ # then surviving pods take them over and serve them.
217
228
  ```
218
229
 
219
230
  ## What This Demonstrates
@@ -36,7 +36,8 @@ These checks apply to ALL deployment targets. Run them first, then proceed to yo
36
36
  - [ ] `security.dnsRebindingProtection.allowedHosts` (or `FRONTMCP_ALLOWED_HOSTS`) names the public
37
37
  hostname(s) — on a routable bind the derived default is **not** enforced, and FrontMCP logs a
38
38
  warning saying so
39
- - [ ] Startup logs show no `DNS-rebinding protection is not enforcing a Host allow-list` warning
39
+ - [ ] Startup logs show no `DNS-rebinding protection is not enforcing a Host allow-list` warning, and
40
+ the production audit reports `[Security] DNS_REBINDING_PROTECTED` (not `DNS_REBINDING_NOT_ENFORCED`)
40
41
  - [ ] Include the port when the public URL uses a non-default one (`api.example.com:8443`)
41
42
  - [ ] `allowedOrigins` is set when a browser client connects, so a foreign `Origin` is refused
42
43
 
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: distributed-ha
3
- description: Deploy FrontMCP across multiple pods with heartbeat, session takeover, and notification relay for zero-downtime failover
3
+ description: Deploy FrontMCP across multiple pods with heartbeat, cross-pod request relay, session takeover, and notification relay for zero-downtime failover
4
4
  ---
5
5
 
6
6
  # Distributed High Availability
7
7
 
8
- FrontMCP's HA module provides automatic session failover across multiple pods using Redis. Three components work together: HeartbeatService (liveness detection), session takeover (atomic CAS), and NotificationRelay (cross-pod MCP notifications).
8
+ FrontMCP's HA module lets any pod receive any request of an MCP session, using Redis. Four components work together: HeartbeatService (liveness detection), request relay (a request for a session another live pod owns is served by that pod), session takeover (atomic CAS — a session whose owner stopped is served by the receiving pod), and NotificationRelay (cross-pod MCP notifications).
9
9
 
10
10
  ## When to Use This Skill
11
11
 
@@ -33,6 +33,7 @@ FrontMCP's HA module provides automatic session failover across multiple pods us
33
33
  - Redis 6+ accessible from all pods
34
34
  - `@frontmcp/sdk` and `@frontmcp/cli` installed
35
35
  - `FRONTMCP_DEPLOYMENT_MODE=distributed` environment variable
36
+ - The same `MCP_SESSION_SECRET` on every pod (session ids are encrypted with it; a pod with another secret answers other pods' sessions with `404`)
36
37
 
37
38
  ## Step 1: Configure @FrontMcp Decorator
38
39
 
@@ -79,9 +80,12 @@ export default defineConfig({
79
80
 
80
81
  ```bash
81
82
  export FRONTMCP_DEPLOYMENT_MODE=distributed
83
+ export MCP_SESSION_SECRET='<same value on every pod>' # replace with your shared secret
82
84
  frontmcp build --target distributed
83
85
  ```
84
86
 
87
+ The build writes the `ha` block to `FRONTMCP_HA_*` variables in the generated setup file (only when the platform has not set them), and every pod reads them at startup.
88
+
85
89
  Deploy with Docker or Kubernetes (see example below).
86
90
 
87
91
  ## Step 4: Verify Heartbeats
@@ -97,12 +101,14 @@ redis-cli GET "mcp:ha:heartbeat:mcp-server-7b8f9-abc12"
97
101
 
98
102
  ## Configuration
99
103
 
100
- | Field | Type | Default | Description |
101
- | ----------------------- | ------ | --------- | ------------------------------------------------ |
102
- | `heartbeatIntervalMs` | number | 10000 | How often each pod writes its heartbeat to Redis |
103
- | `heartbeatTtlMs` | number | 30000 | TTL for heartbeat key (should be 2-3x interval) |
104
- | `takeoverGracePeriodMs` | number | 5000 | Wait time before claiming orphaned sessions |
105
- | `redisKeyPrefix` | string | `mcp:ha:` | Redis key prefix for all HA keys |
104
+ | Field | Environment variable | Type | Default | Description |
105
+ | ----------------------- | ----------------------------------- | ------ | --------- | ------------------------------------------------ |
106
+ | `heartbeatIntervalMs` | `FRONTMCP_HA_HEARTBEAT_INTERVAL_MS` | number | 10000 | How often each pod writes its heartbeat to Redis |
107
+ | `heartbeatTtlMs` | `FRONTMCP_HA_HEARTBEAT_TTL_MS` | number | 30000 | TTL for heartbeat key (should be 2-3x interval) |
108
+ | `takeoverGracePeriodMs` | `FRONTMCP_HA_TAKEOVER_GRACE_MS` | number | 5000 | Wait time before claiming orphaned sessions |
109
+ | `redisKeyPrefix` | `FRONTMCP_HA_KEY_PREFIX` | string | `mcp:ha:` | Redis key prefix for all HA keys |
110
+
111
+ `heartbeatTtlMs` is also how long a request for a stopped pod's session is answered with `503` + `Retry-After` before another pod takes it over.
106
112
 
107
113
  ## Architecture
108
114
 
@@ -110,22 +116,30 @@ redis-cli GET "mcp:ha:heartbeat:mcp-server-7b8f9-abc12"
110
116
 
111
117
  Each pod writes `mcp:ha:heartbeat:{nodeId}` to Redis every `heartbeatIntervalMs` with PX TTL of `heartbeatTtlMs`. The value contains `{ nodeId, startedAt, lastBeat, sessionCount }`. When a pod dies, the key expires.
112
118
 
119
+ ### Request Relay
120
+
121
+ The owner of each session is recorded on the transport bus (`mcp:bus:session:{sessionId}`) and in the persisted session record. The hookable `relayToSessionOwner` stage of `http:request` (after the IP filter, before quota and auth) finds it — only for session ids the deployment minted (they decrypt under `MCP_SESSION_SECRET`), so other ids cost no Redis lookup; when it is another **live** pod the request (method, URL, headers, parsed body, client address) is published to `mcp:ha:notify:{ownerNodeId}`. The owner runs it through its own full `http:request` flow — auth, quota, transport and hooks run there — and streams the response (status, headers, each chunk, end; SSE included, with keepalive frames while it is quiet) back. A client disconnect aborts it on the owner. An owner that does not listen, does not acknowledge within 5s, loses its heartbeat mid-request, or sends nothing for three heartbeat intervals yields `503` + `Retry-After` (`SessionOwnerUnreachableError`), never a 500 (a response already started is ended). A response frame the owner cannot publish aborts the response there and ends it on the relaying pod. A relayed request is never relayed again.
122
+
113
123
  ### Session Takeover
114
124
 
115
125
  When a request arrives for a session owned by a dead pod:
116
126
 
117
127
  1. The live pod checks if the owner's heartbeat key exists
118
128
  2. If missing, runs an atomic Lua CAS script: verifies `expectedOldNodeId`, updates `nodeId` + `reassignedAt`
119
- 3. Returns `{ claimed: true }` on success, `{ claimed: false }` if another pod won the race
129
+ 3. On success it recreates the transport from the persisted session (keeping `reassignedAt` / `reassignedFrom`), records itself as owner on the bus, and serves the request; if another pod won the race, it relays the request to that pod
130
+
131
+ A pod whose heartbeat lapsed (Redis unreachable for `heartbeatTtlMs`) may have lost sessions it still holds. Before serving one again it re-reads the persisted record (once per lapse); if another pod owns it now, it drops its transport, leaves the record to the new owner, and relays the request there. While Redis stays unreachable it keeps serving what it holds.
132
+
133
+ Takeover needs `transport.persistence` (Streamable HTTP only — an SSE stream cannot move to another pod).
120
134
 
121
135
  ### Notification Relay
122
136
 
123
- Each pod subscribes to `mcp:ha:notify:{nodeId}` via Redis Pub/Sub. Cross-pod MCP notifications (progress updates, resource changes) are published to the target pod's channel for local delivery.
137
+ Each pod subscribes to `mcp:ha:notify:{nodeId}` via Redis Pub/Sub. A notification for a session on another pod is published to the channel of the pod that owns it (looked up on the bus) and delivered there; it is never relayed twice.
124
138
 
125
139
  ## Redis Connection, TTL and Recovery
126
140
 
127
- - HA uses one dedicated ioredis client built from the top-level `redis` config (host/port/password/db/tls or `url`). It reconnects on its own, logs errors at a rate-limited interval, and is closed on shutdown. Vercel KV cannot back HA.
128
- - The orphan scanner reads `<keyPrefix>session:` (default `mcp:transport:session:`), the same prefix the session store writes, and only runs when `transport.persistence.redis` is set.
141
+ - HA uses one dedicated ioredis client built from the top-level `redis` config (host/port/password/db/tls or `url`) for commands and publishing, plus a second connection for the relay channel subscription (retried 1s→30s until it succeeds). Both reconnect on their own, log errors at a rate-limited interval, and are closed on shutdown. Vercel KV cannot back HA.
142
+ - The orphan scanner reads `<keyPrefix>session:` (default `mcp:session:`, as `keyPrefix` defaults to `mcp:`), the same prefix the session store writes, and only runs when `transport.persistence.redis` is set. A claimed session is re-advertised on the bus, so every pod relays its next requests to the claimer, which recreates the transport on the first one.
129
143
  - Session TTL is `persistence.defaultTtlMs`, then `persistence.redis.defaultTtlMs`, then 1 hour. The pod serving a session refreshes it at most once per quarter TTL.
130
144
  - If Redis is unreachable at startup the server still starts; the session store retries with exponential backoff (1s doubling to 30s) and persistence resumes without a restart.
131
145
  - `.frontmcp/machine-id` is only read/written in standalone development (never in `distributed` or `serverless`); in Kubernetes the machine ID is `HOSTNAME`.
@@ -134,10 +148,10 @@ Each pod subscribes to `mcp:ha:notify:{nodeId}` via Redis Pub/Sub. Cross-pod MCP
134
148
 
135
149
  FrontMCP sets:
136
150
 
137
- - **Cookie**: `__frontmcp_node` on Streamable HTTP initialize
138
- - **Header**: `X-FrontMCP-Machine-Id` on every distributed response (initialize, message POSTs, DELETE, stateless requests and SSE), applied by the hookable `applyNodeHeaders` flow stage
151
+ - **Cookie**: `__frontmcp_node` on Streamable HTTP initialize — name, `Domain` and `SameSite` come from the deployment's `server.cookies` (`affinity`, `domain`, `sameSite`) in `frontmcp.config`; keep the load balancer's cookie name in step
152
+ - **Header**: `X-FrontMCP-Machine-Id` on every distributed response (initialize, message POSTs, DELETE, stateless and MCP 2026-07-28 requests, SSE, `/healthz`, `/readyz`, `/metrics` and 404s). The Express host and the web-fetch handler add it next to the security headers; the session flows also set it in the hookable `applyNodeHeaders` stage. Only distributed mode (`FRONTMCP_DEPLOYMENT_MODE=distributed`) sends it
139
153
 
140
- NGINX sticky session example:
154
+ Affinity is an optimization: without it a request on the wrong pod is relayed to the owner (one Redis round trip each way). NGINX sticky session example:
141
155
 
142
156
  ```nginx
143
157
  upstream mcp_backend {
@@ -158,16 +172,18 @@ upstream mcp_backend {
158
172
 
159
173
  ## Errors
160
174
 
161
- | Error | When | Solution |
162
- | --------------------------- | ------------------------------------------ | ------------------------------------------------------- |
163
- | `SessionClaimConflictError` | Session claimed by another pod during race | Retry --- the load balancer will route to the new owner |
164
- | `HaConfigurationError` | Redis not configured for distributed mode | Add `redis` to `@FrontMcp()` config |
175
+ | Error | When | Solution |
176
+ | ------------------------------ | ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
177
+ | `SessionOwnerUnreachableError` | `503` + `Retry-After`: owner alive by heartbeat but did not answer the relay (just stopped) | Retry after `Retry-After` seconds; by then the owner answers or is taken over |
178
+ | `SessionClaimConflictError` | Takeover lost to a pod that is gone too, or the session expired (client gets `404`) | None — a lost race against a live pod is relayed to it instead |
179
+ | `HaConfigurationError` | Redis not configured for distributed mode | Add `redis` to `@FrontMcp()` config |
165
180
 
166
181
  ## Verification Checklist
167
182
 
168
183
  ### Configuration
169
184
 
170
185
  - [ ] `FRONTMCP_DEPLOYMENT_MODE=distributed` set in deployment
186
+ - [ ] Same `MCP_SESSION_SECRET` on every pod
171
187
  - [ ] Redis accessible from all pods
172
188
  - [ ] `heartbeatTtlMs` >= 2x `heartbeatIntervalMs`
173
189
  - [ ] Transport persistence configured with Redis
@@ -175,20 +191,24 @@ upstream mcp_backend {
175
191
  ### Runtime
176
192
 
177
193
  - [ ] `redis-cli --scan --pattern "mcp:ha:heartbeat:*"` shows entries for each pod
194
+ - [ ] `redis-cli PUBSUB CHANNELS "mcp:ha:notify:*"` lists one channel per pod
195
+ - [ ] A request sent to a pod that does not own the session is answered (relayed; `X-FrontMCP-Machine-Id` names the owner)
178
196
  - [ ] Killing a pod results in its heartbeat expiring within TTL
179
- - [ ] Surviving pods claim orphaned sessions after takeover grace period
197
+ - [ ] Surviving pods claim orphaned sessions after takeover grace period (`redis-cli HGETALL "mcp:bus:session:<id>"` names the new owner)
180
198
  - [ ] `/healthz` and `/readyz` return healthy on all pods
181
199
 
182
200
  ## Troubleshooting
183
201
 
184
- | Problem | Cause | Solution |
185
- | ---------------------------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
186
- | Sessions not transferred after pod death | `heartbeatTtlMs` too high | Lower TTL while keeping >= 2x interval (e.g., 20-30s for a 10s interval) |
187
- | `HaConfigurationError` on startup | Missing Redis config | Add `redis` to `@FrontMcp()` decorator |
188
- | Duplicate notifications | Shared Redis subscriber connection | Use dedicated connections per relay |
189
- | Sessions expire too early or too late | TTL not configured | Set `transport.persistence.defaultTtlMs` (or `persistence.redis.defaultTtlMs`); default is 1 hour and slides while the owning pod serves requests |
190
- | Redis was down when pods started | Startup connect failed | Nothing to do: the session store reconnects with backoff (1s to 30s) and `/readyz` turns 200 |
191
- | Session takeover race failures | High pod count + simultaneous restarts | Increase `takeoverGracePeriodMs` |
202
+ | Problem | Cause | Solution |
203
+ | ------------------------------------------ | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
204
+ | `503` "did not answer the relayed request" | Owner just stopped (heartbeat not expired yet) | Expected for up to `heartbeatTtlMs`; the client retries after `Retry-After` and the session is taken over |
205
+ | `404` on another pod for a live session | Pods use different `MCP_SESSION_SECRET` | Give every pod the same `MCP_SESSION_SECRET` |
206
+ | Sessions not transferred after pod death | `heartbeatTtlMs` too high | Lower TTL while keeping >= 2x interval (e.g., 20-30s for a 10s interval) |
207
+ | `HaConfigurationError` on startup | Missing Redis config | Add `redis` to `@FrontMcp()` decorator |
208
+ | Duplicate notifications | Shared Redis subscriber connection | Use dedicated connections per relay |
209
+ | Sessions expire too early or too late | TTL not configured | Set `transport.persistence.defaultTtlMs` (or `persistence.redis.defaultTtlMs`); default is 1 hour and slides while the owning pod serves requests |
210
+ | Redis was down when pods started | Startup connect failed | Nothing to do: the session store reconnects with backoff (1s to 30s) and `/readyz` turns 200 |
211
+ | Session takeover race failures | High pod count + simultaneous restarts | Increase `takeoverGracePeriodMs` |
192
212
 
193
213
  ## Examples
194
214
 
@@ -19,7 +19,7 @@ Use Nx commands for efficient building, testing, and CI with affected-only execu
19
19
 
20
20
  ```bash
21
21
  # Build a single server (builds all dependencies in correct order)
22
- nx build gateway
22
+ nx build server-gateway
23
23
 
24
24
  # Test a single app
25
25
  nx test billing
@@ -59,7 +59,7 @@ nx g @frontmcp/nx:tool calculate-tax --project=billing
59
59
  nx test billing
60
60
 
61
61
  # 4. Build the server that includes this app
62
- nx build gateway
62
+ nx build server-gateway
63
63
 
64
64
  # 5. Or test everything affected by your changes
65
65
  nx affected -t test
@@ -36,10 +36,12 @@ nx g @frontmcp/nx:lib shared-db
36
36
  ```typescript
37
37
  // servers/public-gateway/src/main.ts
38
38
  import 'reflect-metadata';
39
- import { FrontMcp } from '@frontmcp/sdk';
39
+
40
40
  import { BillingApp } from '@my-workspace/billing';
41
41
  import { CrmApp } from '@my-workspace/crm';
42
42
 
43
+ import { FrontMcp } from '@frontmcp/sdk';
44
+
43
45
  @FrontMcp({
44
46
  info: { name: 'public-gateway', version: '1.0.0' },
45
47
  apps: [BillingApp, CrmApp],
@@ -58,9 +60,11 @@ export default PublicGateway;
58
60
  ```typescript
59
61
  // servers/admin-portal/src/main.ts
60
62
  import 'reflect-metadata';
61
- import { FrontMcp } from '@frontmcp/sdk';
63
+
62
64
  import { AdminApp } from '@my-workspace/admin';
63
65
 
66
+ import { FrontMcp } from '@frontmcp/sdk';
67
+
64
68
  @FrontMcp({
65
69
  info: { name: 'admin-portal', version: '1.0.0' },
66
70
  apps: [AdminApp],
@@ -73,9 +77,9 @@ export default AdminPortal;
73
77
  ```
74
78
 
75
79
  ```bash
76
- # Build each server independently
77
- nx build public-gateway
78
- nx build admin-portal
80
+ # Build each server independently (server projects are named server-<name>)
81
+ nx build server-public-gateway
82
+ nx build server-admin-portal
79
83
 
80
84
  # Test all projects
81
85
  nx run-many -t test
@@ -47,7 +47,7 @@ nx g @frontmcp/nx:server gateway --apps=billing --deploymentTarget=node
47
47
  ```bash
48
48
  # Verify the generated structure
49
49
  nx test billing
50
- nx build gateway
50
+ nx build server-gateway
51
51
  ```
52
52
 
53
53
  ## What This Demonstrates
@@ -58,7 +58,7 @@ apps/billing/
58
58
  ```bash
59
59
  # Build and test the app
60
60
  nx test billing
61
- nx build gateway
61
+ nx build server-gateway
62
62
  ```
63
63
 
64
64
  ## What This Demonstrates
@@ -146,15 +146,15 @@ Install one or many skills to a provider-specific directory. `[name]` is
146
146
  optional when one of `--all`, `--tag`, or `--category` is supplied —
147
147
  those flags select skills in bulk.
148
148
 
149
- | Flag | Description | Default |
150
- | --------------------------- | ------------------------------------------------------------------------------------------------------ | -------- |
151
- | `-p, --provider <provider>` | Target provider: `claude` or `codex` | `claude` |
152
- | `-d, --dir <directory>` | Custom install directory (overrides provider default) | — |
153
- | `-a, --all` | Install **every** skill in the catalog (or every `@Skill` entry when `--from-*` is set) | `false` |
154
- | `-t, --tag <tag>` | Install every skill matching a tag (catalog only) | — |
155
- | `-c, --category <c>` | Install every skill in a category (catalog only) | — |
156
- | `--from-entry <path>` | Install `@Skill` entries discovered in a **local project entry file** instead of the framework catalog | — |
157
- | `--from-package <pkg>` | Install `@Skill` entries discovered in a **published package's** main entry | — |
149
+ | Flag | Description | Default |
150
+ | --------------------------- | ------------------------------------------------------------------------------------------------------ | ----------------------------------------------------- |
151
+ | `-p, --provider <provider>` | Target provider: `claude` or `codex` | `skills.provider` in `frontmcp.config`, else `claude` |
152
+ | `-d, --dir <directory>` | Custom install directory (overrides provider default) | — |
153
+ | `-a, --all` | Install **every** skill in the catalog (or every `@Skill` entry when `--from-*` is set) | `false` |
154
+ | `-t, --tag <tag>` | Install every skill matching a tag (catalog only) | — |
155
+ | `-c, --category <c>` | Install every skill in a category (catalog only) | — |
156
+ | `--from-entry <path>` | Install `@Skill` entries discovered in a **local project entry file** instead of the framework catalog | — |
157
+ | `--from-package <pkg>` | Install `@Skill` entries discovered in a **published package's** main entry | — |
158
158
 
159
159
  ```bash
160
160
  # Single-skill install (positional name)
@@ -172,18 +172,31 @@ frontmcp skills install --from-entry src/main.ts --all -p claude
172
172
  frontmcp skills install --from-package my-frontmcp-server my-skill -p claude
173
173
  ```
174
174
 
175
+ With no name and no `--all` / `--tag` / `--category` / `--from-*`, the command installs the
176
+ `skills.install` list of the nearest `frontmcp.config`, or else the catalog skills of `skills.bundle`
177
+ (`'none'` installs nothing). Explicit flags always win.
178
+
179
+ ```ts
180
+ // frontmcp.config.ts
181
+ export default defineConfig({
182
+ name: 'my-server',
183
+ deployments: [{ target: 'node' }],
184
+ skills: { provider: 'claude', install: ['create-tool', 'setup-testing'], exportTarget: 'cursor' },
185
+ });
186
+ ```
187
+
175
188
  ### `frontmcp skills export`
176
189
 
177
190
  Convert one or many catalog skills into a rule file for IDEs that
178
191
  **don't** speak the skills protocol (Cursor, Windsurf, Copilot). The
179
192
  emitted file lives in the current directory by default.
180
193
 
181
- | Flag | Description | Default |
182
- | ----------------------- | ----------------------------------------------------- | -------- |
183
- | `-t, --target <target>` | Target IDE: `cursor`, `windsurf`, or `copilot` | `cursor` |
184
- | `-n, --name <name>` | Skill name to export (required unless `--all` is set) | — |
185
- | `-a, --all` | Export **every** skill in the catalog | `false` |
186
- | `-d, --out <directory>` | Output directory | `cwd` |
194
+ | Flag | Description | Default |
195
+ | ----------------------- | ----------------------------------------------------- | --------------------------------------------------------- |
196
+ | `-t, --target <target>` | Target IDE: `cursor`, `windsurf`, or `copilot` | `skills.exportTarget` in `frontmcp.config`, else `cursor` |
197
+ | `-n, --name <name>` | Skill name to export (required unless `--all` is set) | — |
198
+ | `-a, --all` | Export **every** skill in the catalog | `false` |
199
+ | `-d, --out <directory>` | Output directory | `cwd` |
187
200
 
188
201
  ```bash
189
202
  frontmcp skills export --name frontmcp-development --target cursor
@@ -138,7 +138,7 @@ export default class Server {}
138
138
  | `namespace` | `string` | Namespace prefix for tools, resources, and prompts |
139
139
  | `description` | `string` | Human-readable description |
140
140
  | `standalone` | `boolean \| 'includeInParent'` | Scope isolation mode (default: `false`) |
141
- | `transportOptions` | `RemoteTransportOptions` | Timeout, retries, headers, SSE fallback |
141
+ | `transportOptions` | `RemoteTransportOptions` | Timeout, retries, headers, SSE fallback, MCP revision |
142
142
  | `remoteAuth` | `RemoteAuthConfig` | Auth config: `'static'`, `'forward'`, or `'oauth'` |
143
143
  | `refreshInterval` | `number` | Interval (ms) to refresh capabilities from remote |
144
144
  | `cacheTTL` | `number` | TTL (ms) for cached capabilities (default: 60000) |
@@ -146,13 +146,16 @@ export default class Server {}
146
146
 
147
147
  `RemoteTransportOptions` fields:
148
148
 
149
- | Field | Type | Default | Description |
150
- | --------------- | ------------------------ | ------- | ---------------------------------------- |
151
- | `timeout` | `number` | `30000` | Request timeout in ms |
152
- | `retryAttempts` | `number` | `3` | Retry attempts for failed requests |
153
- | `retryDelayMs` | `number` | `1000` | Delay between retries in ms |
154
- | `fallbackToSSE` | `boolean` | `true` | Fallback to SSE if Streamable HTTP fails |
155
- | `headers` | `Record<string, string>` | - | Additional headers for all requests |
149
+ | Field | Type | Default | Description |
150
+ | ----------------- | ------------------------------------ | ---------- | -------------------------------------------------------------------------------------------------------------------------- |
151
+ | `timeout` | `number` | `30000` | Request timeout in ms |
152
+ | `retryAttempts` | `number` | `3` | Retry attempts for failed requests |
153
+ | `retryDelayMs` | `number` | `1000` | Delay between retries in ms |
154
+ | `fallbackToSSE` | `boolean` | `true` | Fallback to SSE if Streamable HTTP fails |
155
+ | `headers` | `Record<string, string>` | - | Additional headers for all requests |
156
+ | `protocolVersion` | `'legacy' \| '2026-07-28' \| 'auto'` | `'legacy'` | MCP revision: session + `initialize`, the stateless 2026-07-28 client (URL remotes only), or probe `server/discover` first |
157
+
158
+ Each remote tool, resource, resource template and prompt is listed once; when `cacheTTL` expires the gateway re-reads the remote's lists and replaces what it proxied before (dropped entries disappear, nothing is duplicated).
156
159
 
157
160
  `RemoteAuthConfig` modes:
158
161
 
@@ -73,7 +73,7 @@ The workspace generator creates the directory structure (`apps/`, `libs/`, `serv
73
73
  nx g @frontmcp/nx:app my-app
74
74
  ```
75
75
 
76
- Creates an `@App`-decorated class in `apps/my-app/` with a tools directory, barrel exports, and project configuration. The `--project` flag is not needed for app generation since the app is the project.
76
+ Creates an `@App`-decorated class in `apps/my-app/` with a sample `hello` tool and its starter spec (`src/tools/hello.tool.spec.ts`, so `nx test my-app` has a test to run), and project configuration with `build`, `dev`, `serve`, `test`, `typecheck` and `inspector` targets. The `--project` flag is not needed for app generation since the app is the project.
77
77
 
78
78
  ### Generate a Shared Library
79
79
 
@@ -81,7 +81,12 @@ Creates an `@App`-decorated class in `apps/my-app/` with a tools directory, barr
81
81
  nx g @frontmcp/nx:lib my-lib
82
82
  ```
83
83
 
84
- Creates a shared library in `libs/my-lib/` with TypeScript configuration, Jest setup, and barrel exports. Use libraries for shared providers, utilities, and types that multiple apps consume.
84
+ Creates a shared library in `libs/my-lib/` with TypeScript configuration, Jest setup, barrel exports and a starter spec. Use libraries for shared providers, utilities, and types that multiple apps consume.
85
+
86
+ - `--libType plugin` / `--libType adapter` start from the same class the `plugin` / `adapter` generators write (an adapter declares `options: { name: string } & <Name>AdapterOptions`, which `DynamicAdapter` requires).
87
+ - The library's `test` target runs `@frontmcp/nx:test` (no `@nx/jest` needed), and `typecheck` runs `tsc --noEmit` on `tsconfig.lib.json` and `tsconfig.spec.json`. Libraries need no build target: apps import them through the path alias and `nx build <app>` bundles them.
88
+ - `--publishable` (with `--importPath @my-org/my-lib`) adds what publishing needs: a cached `build` target that runs `tsc -p tsconfig.lib.json` into `libs/my-lib/dist`, and a `package.json` named after the import path with `main`/`types` pointing at `./dist/index.js`/`./dist/index.d.ts`, `files: ["dist"]`, and dependencies on `tslib` and (plugin, adapter, tool-register) `@frontmcp/sdk` at the workspace ranges. `nx build my-lib`, then `npm publish libs/my-lib`.
89
+ - The import path (`@frontmcp/my-lib`, or `--importPath`) is registered in `tsconfig.base.json` as `["./libs/my-lib/src/index.ts"]`. The leading `./` matters: TypeScript rejects a bare `libs/...` target when the base config has no `baseUrl` (TS5090), which is the case in `create-nx-workspace --preset=ts` workspaces.
85
90
 
86
91
  ### Generate a Server (Deployment Shell)
87
92
 
@@ -89,7 +94,16 @@ Creates a shared library in `libs/my-lib/` with TypeScript configuration, Jest s
89
94
  nx g @frontmcp/nx:server my-server --deploymentTarget=node --apps=my-app
90
95
  ```
91
96
 
92
- Creates a `@FrontMcp`-decorated server class in `servers/my-server/` that composes one or more apps. The server is the deployment unit.
97
+ Creates a `@FrontMcp`-decorated server class in `servers/my-server/` that composes one or more apps. The server is the deployment unit; its Nx project is named `server-my-server`, with `build`, `dev`, `typecheck` and `deploy` targets.
98
+
99
+ `nx build server-my-server` runs `frontmcp build --target <deploymentTarget>` into `servers/my-server/dist`, and the generated deployment files point at what that build writes:
100
+
101
+ | Target | Build output | Generated file |
102
+ | ------------ | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
103
+ | `node` | `dist/node/server-my-server.bundle.js` | `Dockerfile` runs it (`CMD ["node", "dist/node/server-my-server.bundle.js"]`, `FRONTMCP_BIND_ADDRESS=all`); build context is the workspace root, ignore rules in `Dockerfile.dockerignore` |
104
+ | `vercel` | `.vercel/output/` (Build Output API) and `dist/vercel/handler.cjs` | `vercel.json` installs and runs `nx build server-my-server` from the workspace root; set the Vercel Root Directory to `servers/my-server` |
105
+ | `lambda` | `dist/lambda/handler.cjs`, exporting `handler` | `template.yaml`: `CodeUri: dist/lambda/`, `Handler: handler.handler`; the generator adds `@codegenie/serverless-express`, which the bundle loads at runtime (provide it with a Lambda layer) |
106
+ | `cloudflare` | `dist/cloudflare/index.js` | `wrangler.toml`: `main = "dist/cloudflare/index.js"` |
93
107
 
94
108
  | Option | Type | Default | Description |
95
109
  | ------------------ | ------------------------------------------------ | ---------------- | ------------------------------------- |
@@ -150,7 +164,7 @@ Creates a `SKILL.md`-based skill directory in `apps/my-app/src/skills/my-skill/`
150
164
  nx g @frontmcp/nx:agent my-agent --project=my-app
151
165
  ```
152
166
 
153
- Creates an `@Agent`-decorated class in `apps/my-app/src/agents/`. Agents are autonomous AI components with their own LLM providers and isolated scopes, automatically exposed as `use-agent:<agent_id>` tools. The generated `llm` block picks `anthropic` (`ANTHROPIC_API_KEY`) for `claude*` models and `openai` (`OPENAI_API_KEY`) otherwise, and `--tools a,b` imports each tool class from `../tools/<name>.tool` (de-duplicated) instead of using string names.
167
+ Creates an `@Agent`-decorated class in `apps/my-app/src/agents/`. Agents are autonomous AI components with their own LLM providers and isolated scopes, automatically exposed as `invoke_<agent_id>` tools. The generated `llm` block picks `anthropic` (`ANTHROPIC_API_KEY`) for `claude*` models and `openai` (`OPENAI_API_KEY`) otherwise, and `--tools a,b` imports each tool class from `../tools/<name>.tool` (de-duplicated) instead of using string names.
154
168
 
155
169
  ### Plugin
156
170
 
@@ -213,7 +227,7 @@ Creates an `@AuthProvider` class in `apps/my-app/src/auth-providers/`. Auth prov
213
227
  ### Build a Single Project
214
228
 
215
229
  ```bash
216
- nx build my-server
230
+ nx build server-my-server
217
231
  ```
218
232
 
219
233
  Builds the server and all its dependencies in the correct order. Nx caches build outputs so subsequent builds of unchanged projects are instant (generated projects set `cache: true`, and `init` covers existing workspaces).
@@ -226,10 +240,20 @@ The `@frontmcp/nx:build` executor runs `frontmcp build` from the project root us
226
240
  nx test my-app
227
241
  ```
228
242
 
229
- Runs `frontmcp test` from the project root. The generated `jest.config.cjs` uses the swc transform, loads `@frontmcp/testing/setup`, and maps the `tsconfig.base.json` path aliases so imports of workspace libraries resolve. Test files must use `.spec.ts` extension (not `.test.ts`).
243
+ Runs `frontmcp test` from the project root (apps and libraries alike; both are generated with a starter spec, so `nx run-many -t test` finds tests in a fresh workspace). The generated `jest.config.cjs` uses the swc transform, loads `@frontmcp/testing/setup`, and maps the `tsconfig.base.json` path aliases so imports of workspace libraries resolve. Test files must use `.spec.ts` extension (not `.test.ts`).
230
244
 
231
245
  The `inspector` executor forwards its `port` option as the `CLIENT_PORT` environment variable (the `frontmcp inspector` command has no port flag).
232
246
 
247
+ ### Type-check a Project
248
+
249
+ ```bash
250
+ nx typecheck my-app
251
+ ```
252
+
253
+ Runs the project's own `typecheck` target: `tsc --noEmit` on `tsconfig.lib.json` and `tsconfig.spec.json`. The generated `tsconfig.json` sets `"nx": { "addTypecheckTarget": false }`, so a workspace that registers `@nx/js/typescript` does not infer a `tsc --build --emitDeclarationOnly` target for it (which fails with TS5069). It also makes the project build JavaScript on any base config: `module: commonjs` with `moduleResolution: node10` on TypeScript 5 and `bundler` on TypeScript 6+, `rootDir` at the workspace root, and `composite` / `declarationMap` / `emitDeclarationOnly` off for TS-solution bases.
254
+
255
+ Workspaces generated by `@frontmcp/nx` do not register `@nx/js/typescript`: its `typescript-sync` generator needs a solution-style root `tsconfig.json` and fails builds with `Missing root "tsconfig.json"` once a library is in the graph. A workspace generated by 1.8.7 or earlier should remove that entry from `nx.json` `plugins`, and give each library it publishes the `build` target and `package.json` that `--publishable` generates (the inferred `build` goes away with the plugin).
256
+
233
257
  ### Build All Projects
234
258
 
235
259
  ```bash
@@ -297,13 +321,16 @@ my-project/
297
321
  my-lib/
298
322
  src/
299
323
  index.ts
324
+ my-lib.spec.ts # starter spec
300
325
  project.json
326
+ jest.config.cjs
301
327
  servers/
302
328
  my-server/
303
329
  src/
304
330
  main.ts # @FrontMcp server (default export)
305
331
  project.json
306
332
  Dockerfile # (node target)
333
+ Dockerfile.dockerignore
307
334
  nx.json
308
335
  tsconfig.base.json
309
336
  package.json
@@ -314,13 +341,13 @@ my-project/
314
341
  ### Serve in Development
315
342
 
316
343
  ```bash
317
- nx serve my-server
344
+ nx dev my-app
318
345
  ```
319
346
 
320
- Or use the FrontMCP dev command:
347
+ A server shell composes its apps; run it the same way through its `dev` target:
321
348
 
322
349
  ```bash
323
- nx dev my-server
350
+ nx dev server-my-server
324
351
  ```
325
352
 
326
353
  ### Generate, Build, and Test a New Feature
@@ -337,7 +364,7 @@ nx g @frontmcp/nx:tool calculate-tax --project=billing-app
337
364
  nx test billing-app
338
365
 
339
366
  # 4. Build the server that includes this app
340
- nx build billing-server
367
+ nx build server-billing
341
368
 
342
369
  # 5. Or test everything affected by your changes
343
370
  nx affected -t test
@@ -383,7 +410,7 @@ Complete list of all `@frontmcp/nx` generators from `generators.json`:
383
410
  | Test file naming | `my-tool.tool.spec.ts` | `my-tool.tool.test.ts` | FrontMCP enforces `.spec.ts` extension; `.test.ts` files are not picked up by Jest config |
384
411
  | Affected-only CI testing | `nx affected -t test` | `nx run-many -t test` | `affected` only runs tests for changed projects, saving CI time and compute |
385
412
  | Server composition | `nx g @frontmcp/nx:server my-server --apps=app-a,app-b` | Manually importing apps in `main.ts` | The server generator wires app composition and deployment config automatically |
386
- | Build before deploy | `nx build my-server` (builds server + all deps) | Building each lib and app individually | Nx resolves the dependency graph and builds in the correct order with caching |
413
+ | Build before deploy | `nx build server-my-server` (builds server + all deps) | Building each lib and app individually | Nx resolves the dependency graph and builds in the correct order with caching |
387
414
 
388
415
  ## Verification Checklist
389
416
 
@@ -401,25 +428,30 @@ Complete list of all `@frontmcp/nx` generators from `generators.json`:
401
428
 
402
429
  ### Build and Test
403
430
 
404
- - [ ] `nx build <server>` completes without TypeScript errors or warnings
431
+ - [ ] `nx build server-<name>` completes without TypeScript errors or warnings
432
+ - [ ] `nx typecheck <project>` passes for apps, libs and servers
405
433
  - [ ] `nx test <app>` passes with 95%+ coverage
406
434
  - [ ] `nx affected -t test` correctly identifies changed projects
407
435
 
408
436
  ### Development Workflow
409
437
 
410
- - [ ] `nx serve <server>` or `nx dev <server>` starts the server successfully
438
+ - [ ] `nx dev <app>` or `nx dev server-<name>` starts the server successfully
411
439
  - [ ] `nx graph` renders the project dependency graph in the browser
412
440
 
413
441
  ## Troubleshooting
414
442
 
415
- | Problem | Cause | Solution |
416
- | ---------------------------------------------- | -------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
417
- | `Cannot find module '@frontmcp/nx'` | Plugin not installed | Run `yarn add -D @frontmcp/nx` and ensure it appears in `devDependencies` |
418
- | Generator creates files in the wrong directory | Missing or incorrect `--project` flag | Always pass `--project=<app-name>` for primitive generators; verify the app exists in `apps/` |
419
- | `nx affected` runs nothing despite changes | Base branch not configured or no dependency link | Check `nx.json` for `defaultBase` setting; verify the changed file belongs to a project in the graph |
420
- | Build fails with circular dependency error | Library A imports from Library B and vice versa | Use `nx graph` to visualize the cycle; extract shared code into a new library |
421
- | Cache not working (full rebuild every time) | Executor targets are not marked cacheable | Run `nx g @frontmcp/nx:init`, or set `cache: true` on the target / in `targetDefaults` |
422
- | `Cannot find module '@scope/lib'` in Jest | Old `jest.config.ts` without the path-alias mapper | Use the generated `jest.config.cjs` (maps `tsconfig.base.json` paths) or add a `moduleNameMapper` |
443
+ | Problem | Cause | Solution |
444
+ | ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
445
+ | `Cannot find module '@frontmcp/nx'` | Plugin not installed | Run `yarn add -D @frontmcp/nx` and ensure it appears in `devDependencies` |
446
+ | Generator creates files in the wrong directory | Missing or incorrect `--project` flag | Always pass `--project=<app-name>` for primitive generators; verify the app exists in `apps/` |
447
+ | `nx affected` runs nothing despite changes | Base branch not configured or no dependency link | Check `nx.json` for `defaultBase` setting; verify the changed file belongs to a project in the graph |
448
+ | `[@nx/js:typescript-sync]: Missing root "tsconfig.json"` on `nx build` | Workspace generated by `@frontmcp/nx` 1.8.7 or earlier registers `@nx/js/typescript` | Remove `@nx/js/typescript` from `plugins` in `nx.json`; FrontMCP projects declare their own `build`, `test` and `typecheck` targets |
449
+ | `nx typecheck` fails with TS5069 | Inferred `tsc --build --emitDeclarationOnly` target on a project without `declaration` | Regenerate the project, or add the `typecheck` target (`tsc --noEmit -p tsconfig.lib.json`) and `"nx": { "addTypecheckTarget": false }` in its `tsconfig.json` |
450
+ | TS5090 on a `tsconfig.base.json` path alias | Alias target written without `./` and no `baseUrl` | Write targets as `./libs/<name>/src/index.ts` (the `lib` and `ui-*` generators do) |
451
+ | `ERESOLVE` on `esbuild` after `nx g @frontmcp/nx:ui-shell` | An `esbuild` range below the `>=0.27` peer of `@frontmcp/uipack` | Re-run the UI generator (it raises an older `esbuild` range to `^0.27.3`) or set `esbuild` to `^0.27.3` |
452
+ | Build fails with circular dependency error | Library A imports from Library B and vice versa | Use `nx graph` to visualize the cycle; extract shared code into a new library |
453
+ | Cache not working (full rebuild every time) | Executor targets are not marked cacheable | Run `nx g @frontmcp/nx:init`, or set `cache: true` on the target / in `targetDefaults` |
454
+ | `Cannot find module '@scope/lib'` in Jest | Old `jest.config.ts` without the path-alias mapper | Use the generated `jest.config.cjs` (maps `tsconfig.base.json` paths) or add a `moduleNameMapper` |
423
455
 
424
456
  ## Examples
425
457