@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
|
@@ -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
|
|
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,26 +116,42 @@ 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.
|
|
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.
|
|
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.
|
|
138
|
+
|
|
139
|
+
## Redis Connection, TTL and Recovery
|
|
140
|
+
|
|
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.
|
|
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.
|
|
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.
|
|
145
|
+
- `.frontmcp/machine-id` is only read/written in standalone development (never in `distributed` or `serverless`); in Kubernetes the machine ID is `HOSTNAME`.
|
|
124
146
|
|
|
125
147
|
## Load Balancer Affinity
|
|
126
148
|
|
|
127
149
|
FrontMCP sets:
|
|
128
150
|
|
|
129
|
-
- **Cookie**: `__frontmcp_node` on Streamable HTTP initialize
|
|
130
|
-
- **Header**: `X-FrontMCP-Machine-Id` on every distributed response
|
|
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
|
|
131
153
|
|
|
132
|
-
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:
|
|
133
155
|
|
|
134
156
|
```nginx
|
|
135
157
|
upstream mcp_backend {
|
|
@@ -150,16 +172,18 @@ upstream mcp_backend {
|
|
|
150
172
|
|
|
151
173
|
## Errors
|
|
152
174
|
|
|
153
|
-
| Error
|
|
154
|
-
|
|
|
155
|
-
| `
|
|
156
|
-
| `
|
|
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 |
|
|
157
180
|
|
|
158
181
|
## Verification Checklist
|
|
159
182
|
|
|
160
183
|
### Configuration
|
|
161
184
|
|
|
162
185
|
- [ ] `FRONTMCP_DEPLOYMENT_MODE=distributed` set in deployment
|
|
186
|
+
- [ ] Same `MCP_SESSION_SECRET` on every pod
|
|
163
187
|
- [ ] Redis accessible from all pods
|
|
164
188
|
- [ ] `heartbeatTtlMs` >= 2x `heartbeatIntervalMs`
|
|
165
189
|
- [ ] Transport persistence configured with Redis
|
|
@@ -167,18 +191,24 @@ upstream mcp_backend {
|
|
|
167
191
|
### Runtime
|
|
168
192
|
|
|
169
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)
|
|
170
196
|
- [ ] Killing a pod results in its heartbeat expiring within TTL
|
|
171
|
-
- [ ] 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)
|
|
172
198
|
- [ ] `/healthz` and `/readyz` return healthy on all pods
|
|
173
199
|
|
|
174
200
|
## Troubleshooting
|
|
175
201
|
|
|
176
|
-
| Problem
|
|
177
|
-
|
|
|
178
|
-
|
|
|
179
|
-
| `
|
|
180
|
-
|
|
|
181
|
-
|
|
|
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` |
|
|
182
212
|
|
|
183
213
|
## Examples
|
|
184
214
|
|
|
@@ -81,7 +81,9 @@ Deep check: probes all registered dependencies, returns catalog hash and registr
|
|
|
81
81
|
The health service automatically registers probes for:
|
|
82
82
|
|
|
83
83
|
- **Session store** (Redis/Vercel KV) via `TransportService.pingSessionStore()`
|
|
84
|
-
- **Remote MCP apps** via the existing `HealthCheckManager` background checks
|
|
84
|
+
- **Remote MCP apps** via the existing `HealthCheckManager` background checks. Until the first check completes the probe is `degraded` (`state: unknown`), so `/readyz` stays 200; a remote that fails its checks is `unhealthy` and gives 503.
|
|
85
|
+
|
|
86
|
+
The fetch handler (Workers, Vercel Edge, Deno) honours the same `health` settings (`healthzPath`, `readyzPath`, `readyz.enabled`, `enabled: false` gives 404).
|
|
85
87
|
|
|
86
88
|
## Custom Probes
|
|
87
89
|
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
@@ -52,13 +52,13 @@ apps/billing/
|
|
|
52
52
|
index.ts # barrel exports updated automatically
|
|
53
53
|
project.json
|
|
54
54
|
tsconfig.json
|
|
55
|
-
jest.config.
|
|
55
|
+
jest.config.cjs
|
|
56
56
|
```
|
|
57
57
|
|
|
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
|
|
150
|
-
|
|
|
151
|
-
| `timeout`
|
|
152
|
-
| `retryAttempts`
|
|
153
|
-
| `retryDelayMs`
|
|
154
|
-
| `fallbackToSSE`
|
|
155
|
-
| `headers`
|
|
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
|
|
|
@@ -41,12 +41,14 @@ This creates a full Nx workspace with `@frontmcp/nx` pre-installed, sample app,
|
|
|
41
41
|
|
|
42
42
|
### Option B: Add FrontMCP to an existing Nx workspace
|
|
43
43
|
|
|
44
|
-
Install the plugin:
|
|
44
|
+
Install the plugin with `nx add`. It runs the plugin's `init` generator, which adds `@frontmcp/sdk`, `frontmcp`, `@frontmcp/testing` and the Jest toolchain to `package.json` (existing versions are kept) and makes the `@frontmcp/nx:build`, `build-exec` and `test` executors cacheable in `nx.json` `targetDefaults`:
|
|
45
45
|
|
|
46
46
|
```bash
|
|
47
|
-
|
|
47
|
+
nx add @frontmcp/nx
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
+
If you install the packages yourself (`yarn add -D @frontmcp/nx @frontmcp/sdk frontmcp @frontmcp/testing`), run `nx g @frontmcp/nx:init` once to get the same setup.
|
|
51
|
+
|
|
50
52
|
Then initialize the workspace structure:
|
|
51
53
|
|
|
52
54
|
```bash
|
|
@@ -71,7 +73,7 @@ The workspace generator creates the directory structure (`apps/`, `libs/`, `serv
|
|
|
71
73
|
nx g @frontmcp/nx:app my-app
|
|
72
74
|
```
|
|
73
75
|
|
|
74
|
-
Creates an `@App`-decorated class in `apps/my-app/` with a tools
|
|
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.
|
|
75
77
|
|
|
76
78
|
### Generate a Shared Library
|
|
77
79
|
|
|
@@ -79,7 +81,12 @@ Creates an `@App`-decorated class in `apps/my-app/` with a tools directory, barr
|
|
|
79
81
|
nx g @frontmcp/nx:lib my-lib
|
|
80
82
|
```
|
|
81
83
|
|
|
82
|
-
Creates a shared library in `libs/my-lib/` with TypeScript configuration, Jest setup, and
|
|
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.
|
|
83
90
|
|
|
84
91
|
### Generate a Server (Deployment Shell)
|
|
85
92
|
|
|
@@ -87,7 +94,16 @@ Creates a shared library in `libs/my-lib/` with TypeScript configuration, Jest s
|
|
|
87
94
|
nx g @frontmcp/nx:server my-server --deploymentTarget=node --apps=my-app
|
|
88
95
|
```
|
|
89
96
|
|
|
90
|
-
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"` |
|
|
91
107
|
|
|
92
108
|
| Option | Type | Default | Description |
|
|
93
109
|
| ------------------ | ------------------------------------------------ | ---------------- | ------------------------------------- |
|
|
@@ -148,7 +164,7 @@ Creates a `SKILL.md`-based skill directory in `apps/my-app/src/skills/my-skill/`
|
|
|
148
164
|
nx g @frontmcp/nx:agent my-agent --project=my-app
|
|
149
165
|
```
|
|
150
166
|
|
|
151
|
-
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 `
|
|
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.
|
|
152
168
|
|
|
153
169
|
### Plugin
|
|
154
170
|
|
|
@@ -156,7 +172,7 @@ Creates an `@Agent`-decorated class in `apps/my-app/src/agents/`. Agents are aut
|
|
|
156
172
|
nx g @frontmcp/nx:plugin my-plugin --project=my-app
|
|
157
173
|
```
|
|
158
174
|
|
|
159
|
-
Creates a `@Plugin` class extending `DynamicPlugin` in `apps/my-app/src/plugins/`.
|
|
175
|
+
Creates a `@Plugin` class extending `DynamicPlugin` in `apps/my-app/src/plugins/`. The plugin takes its options in the constructor and contributes providers through a **static** `dynamicProviders(options)` method; there is no `onRegister` hook to implement.
|
|
160
176
|
|
|
161
177
|
### Adapter
|
|
162
178
|
|
|
@@ -164,7 +180,7 @@ Creates a `@Plugin` class extending `DynamicPlugin` in `apps/my-app/src/plugins/
|
|
|
164
180
|
nx g @frontmcp/nx:adapter my-adapter --project=my-app
|
|
165
181
|
```
|
|
166
182
|
|
|
167
|
-
Creates an `@Adapter` class extending `DynamicAdapter` in `apps/my-app/src/adapters/`. Adapters convert external definitions (OpenAPI, Lambda, etc.) into generated tools, resources, and prompts.
|
|
183
|
+
Creates an `@Adapter` class extending `DynamicAdapter` in `apps/my-app/src/adapters/`. Adapters convert external definitions (OpenAPI, Lambda, etc.) into generated tools, resources, and prompts. The generated class stores its `{ name } & Options` constructor argument and `fetch()` returns a `FrontMcpAdapterResponse`.
|
|
168
184
|
|
|
169
185
|
### Provider
|
|
170
186
|
|
|
@@ -172,7 +188,7 @@ Creates an `@Adapter` class extending `DynamicAdapter` in `apps/my-app/src/adapt
|
|
|
172
188
|
nx g @frontmcp/nx:provider my-provider --project=my-app
|
|
173
189
|
```
|
|
174
190
|
|
|
175
|
-
Creates a `@Provider` class in `apps/my-app/src/providers/`. Providers are named singletons resolved via DI (e.g., database pools, API clients, config).
|
|
191
|
+
Creates a `@Provider` class in `apps/my-app/src/providers/`. Providers are named singletons resolved via DI (e.g., database pools, API clients, config). The class is its own token: register `providers: [MyProvider]` and resolve it with `this.get(MyProvider)`; `--scope singleton` maps to `ProviderScope.GLOBAL`, `request`/`context` to `ProviderScope.CONTEXT`.
|
|
176
192
|
|
|
177
193
|
### Flow
|
|
178
194
|
|
|
@@ -180,7 +196,7 @@ Creates a `@Provider` class in `apps/my-app/src/providers/`. Providers are named
|
|
|
180
196
|
nx g @frontmcp/nx:flow my-flow --project=my-app
|
|
181
197
|
```
|
|
182
198
|
|
|
183
|
-
Creates a `@Flow` class extending `FlowBase` in `apps/my-app/src/flows/`. Flows define execution pipelines with hooks and stages.
|
|
199
|
+
Creates a `@Flow` class extending `FlowBase` in `apps/my-app/src/flows/`. Flows define execution pipelines with hooks and stages. The generated flow declares its schemas, registers itself through `declare global { interface ExtendFlows }` so `runFlow` is typed, and implements each plan step with a `@Stage` method from `FlowHooksOf(name)`.
|
|
184
200
|
|
|
185
201
|
### Job
|
|
186
202
|
|
|
@@ -211,10 +227,12 @@ Creates an `@AuthProvider` class in `apps/my-app/src/auth-providers/`. Auth prov
|
|
|
211
227
|
### Build a Single Project
|
|
212
228
|
|
|
213
229
|
```bash
|
|
214
|
-
nx build my-server
|
|
230
|
+
nx build server-my-server
|
|
215
231
|
```
|
|
216
232
|
|
|
217
|
-
Builds the server and all its dependencies in the correct order. Nx caches build outputs so subsequent builds of unchanged projects are instant.
|
|
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).
|
|
234
|
+
|
|
235
|
+
The `@frontmcp/nx:build` executor runs `frontmcp build` from the project root using the `frontmcp` CLI installed in the workspace (never `npx`, which would download the newest CLI). Choose the platform with the `target` option (`node`, `vercel`, `lambda`, `cloudflare`); `adapter` is a deprecated alias. Code imported from workspace libraries through `tsconfig.base.json` path aliases is resolved and bundled for every target.
|
|
218
236
|
|
|
219
237
|
### Test a Single Project
|
|
220
238
|
|
|
@@ -222,7 +240,19 @@ Builds the server and all its dependencies in the correct order. Nx caches build
|
|
|
222
240
|
nx test my-app
|
|
223
241
|
```
|
|
224
242
|
|
|
225
|
-
Runs
|
|
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`).
|
|
244
|
+
|
|
245
|
+
The `inspector` executor forwards its `port` option as the `CLIENT_PORT` environment variable (the `frontmcp inspector` command has no port flag).
|
|
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).
|
|
226
256
|
|
|
227
257
|
### Build All Projects
|
|
228
258
|
|
|
@@ -284,19 +314,23 @@ my-project/
|
|
|
284
314
|
my-app.app.ts # @App class
|
|
285
315
|
index.ts # barrel exports
|
|
286
316
|
project.json
|
|
317
|
+
package.json # minimal manifest so `frontmcp build` runs in the project root
|
|
287
318
|
tsconfig.json
|
|
288
|
-
jest.config.
|
|
319
|
+
jest.config.cjs
|
|
289
320
|
libs/
|
|
290
321
|
my-lib/
|
|
291
322
|
src/
|
|
292
323
|
index.ts
|
|
324
|
+
my-lib.spec.ts # starter spec
|
|
293
325
|
project.json
|
|
326
|
+
jest.config.cjs
|
|
294
327
|
servers/
|
|
295
328
|
my-server/
|
|
296
329
|
src/
|
|
297
330
|
main.ts # @FrontMcp server (default export)
|
|
298
331
|
project.json
|
|
299
332
|
Dockerfile # (node target)
|
|
333
|
+
Dockerfile.dockerignore
|
|
300
334
|
nx.json
|
|
301
335
|
tsconfig.base.json
|
|
302
336
|
package.json
|
|
@@ -307,13 +341,13 @@ my-project/
|
|
|
307
341
|
### Serve in Development
|
|
308
342
|
|
|
309
343
|
```bash
|
|
310
|
-
nx
|
|
344
|
+
nx dev my-app
|
|
311
345
|
```
|
|
312
346
|
|
|
313
|
-
|
|
347
|
+
A server shell composes its apps; run it the same way through its `dev` target:
|
|
314
348
|
|
|
315
349
|
```bash
|
|
316
|
-
nx dev my-server
|
|
350
|
+
nx dev server-my-server
|
|
317
351
|
```
|
|
318
352
|
|
|
319
353
|
### Generate, Build, and Test a New Feature
|
|
@@ -330,7 +364,7 @@ nx g @frontmcp/nx:tool calculate-tax --project=billing-app
|
|
|
330
364
|
nx test billing-app
|
|
331
365
|
|
|
332
366
|
# 4. Build the server that includes this app
|
|
333
|
-
nx build billing
|
|
367
|
+
nx build server-billing
|
|
334
368
|
|
|
335
369
|
# 5. Or test everything affected by your changes
|
|
336
370
|
nx affected -t test
|
|
@@ -376,7 +410,7 @@ Complete list of all `@frontmcp/nx` generators from `generators.json`:
|
|
|
376
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 |
|
|
377
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 |
|
|
378
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 |
|
|
379
|
-
| Build before deploy | `nx build my-server` (builds server + all deps)
|
|
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 |
|
|
380
414
|
|
|
381
415
|
## Verification Checklist
|
|
382
416
|
|
|
@@ -394,24 +428,30 @@ Complete list of all `@frontmcp/nx` generators from `generators.json`:
|
|
|
394
428
|
|
|
395
429
|
### Build and Test
|
|
396
430
|
|
|
397
|
-
- [ ] `nx build
|
|
431
|
+
- [ ] `nx build server-<name>` completes without TypeScript errors or warnings
|
|
432
|
+
- [ ] `nx typecheck <project>` passes for apps, libs and servers
|
|
398
433
|
- [ ] `nx test <app>` passes with 95%+ coverage
|
|
399
434
|
- [ ] `nx affected -t test` correctly identifies changed projects
|
|
400
435
|
|
|
401
436
|
### Development Workflow
|
|
402
437
|
|
|
403
|
-
- [ ] `nx
|
|
438
|
+
- [ ] `nx dev <app>` or `nx dev server-<name>` starts the server successfully
|
|
404
439
|
- [ ] `nx graph` renders the project dependency graph in the browser
|
|
405
440
|
|
|
406
441
|
## Troubleshooting
|
|
407
442
|
|
|
408
|
-
| Problem
|
|
409
|
-
|
|
|
410
|
-
| `Cannot find module '@frontmcp/nx'`
|
|
411
|
-
| Generator creates files in the wrong directory
|
|
412
|
-
| `nx affected` runs nothing despite changes
|
|
413
|
-
|
|
|
414
|
-
|
|
|
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` |
|
|
415
455
|
|
|
416
456
|
## Examples
|
|
417
457
|
|