@cyanheads/mcp-ts-core 0.9.0 → 0.9.2

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 (95) hide show
  1. package/CLAUDE.md +2 -1
  2. package/README.md +6 -2
  3. package/changelog/0.9.x/0.9.1.md +41 -0
  4. package/changelog/0.9.x/0.9.2.md +55 -0
  5. package/dist/cli/init.js +1 -1
  6. package/dist/cli/init.js.map +1 -1
  7. package/dist/core/app.d.ts.map +1 -1
  8. package/dist/core/app.js +3 -0
  9. package/dist/core/app.js.map +1 -1
  10. package/dist/core/context.d.ts +6 -0
  11. package/dist/core/context.d.ts.map +1 -1
  12. package/dist/core/context.js +2 -0
  13. package/dist/core/context.js.map +1 -1
  14. package/dist/core/serverManifest.d.ts +8 -2
  15. package/dist/core/serverManifest.d.ts.map +1 -1
  16. package/dist/core/serverManifest.js +16 -2
  17. package/dist/core/serverManifest.js.map +1 -1
  18. package/dist/linter/rules/format-parity-rules.d.ts.map +1 -1
  19. package/dist/linter/rules/format-parity-rules.js +134 -106
  20. package/dist/linter/rules/format-parity-rules.js.map +1 -1
  21. package/dist/logs/combined.log +7 -7
  22. package/dist/logs/error.log +5 -5
  23. package/dist/mcp-server/resources/resource-registration.d.ts.map +1 -1
  24. package/dist/mcp-server/resources/resource-registration.js +2 -0
  25. package/dist/mcp-server/resources/resource-registration.js.map +1 -1
  26. package/dist/mcp-server/resources/utils/resourceHandlerFactory.d.ts +2 -0
  27. package/dist/mcp-server/resources/utils/resourceHandlerFactory.d.ts.map +1 -1
  28. package/dist/mcp-server/resources/utils/resourceHandlerFactory.js +2 -0
  29. package/dist/mcp-server/resources/utils/resourceHandlerFactory.js.map +1 -1
  30. package/dist/mcp-server/server.d.ts +7 -0
  31. package/dist/mcp-server/server.d.ts.map +1 -1
  32. package/dist/mcp-server/server.js +11 -7
  33. package/dist/mcp-server/server.js.map +1 -1
  34. package/dist/mcp-server/tools/tool-registration.d.ts.map +1 -1
  35. package/dist/mcp-server/tools/tool-registration.js +4 -0
  36. package/dist/mcp-server/tools/tool-registration.js.map +1 -1
  37. package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts +2 -0
  38. package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts.map +1 -1
  39. package/dist/mcp-server/tools/utils/toolHandlerFactory.js +2 -0
  40. package/dist/mcp-server/tools/utils/toolHandlerFactory.js.map +1 -1
  41. package/dist/mcp-server/transports/http/httpTransport.d.ts.map +1 -1
  42. package/dist/mcp-server/transports/http/httpTransport.js +9 -0
  43. package/dist/mcp-server/transports/http/httpTransport.js.map +1 -1
  44. package/dist/mcp-server/transports/http/landing-page/assets/styles.d.ts.map +1 -1
  45. package/dist/mcp-server/transports/http/landing-page/assets/styles.js +90 -72
  46. package/dist/mcp-server/transports/http/landing-page/assets/styles.js.map +1 -1
  47. package/dist/mcp-server/transports/http/landing-page/sections/connect.d.ts +6 -4
  48. package/dist/mcp-server/transports/http/landing-page/sections/connect.d.ts.map +1 -1
  49. package/dist/mcp-server/transports/http/landing-page/sections/connect.js +76 -19
  50. package/dist/mcp-server/transports/http/landing-page/sections/connect.js.map +1 -1
  51. package/dist/mcp-server/transports/http/landing-page/sections/head.d.ts +2 -1
  52. package/dist/mcp-server/transports/http/landing-page/sections/head.d.ts.map +1 -1
  53. package/dist/mcp-server/transports/http/landing-page/sections/head.js +20 -2
  54. package/dist/mcp-server/transports/http/landing-page/sections/head.js.map +1 -1
  55. package/dist/mcp-server/transports/http/landing-page/sections/prompts.js +1 -1
  56. package/dist/mcp-server/transports/http/landing-page/sections/prompts.js.map +1 -1
  57. package/dist/mcp-server/transports/http/landing-page/sections/resources.js +1 -1
  58. package/dist/mcp-server/transports/http/landing-page/sections/resources.js.map +1 -1
  59. package/dist/mcp-server/transports/http/landing-page/sections/tools.js +23 -16
  60. package/dist/mcp-server/transports/http/landing-page/sections/tools.js.map +1 -1
  61. package/dist/mcp-server/transports/http/robotsTxt.d.ts +24 -0
  62. package/dist/mcp-server/transports/http/robotsTxt.d.ts.map +1 -0
  63. package/dist/mcp-server/transports/http/robotsTxt.js +39 -0
  64. package/dist/mcp-server/transports/http/robotsTxt.js.map +1 -0
  65. package/dist/testing/fuzz.js +1 -1
  66. package/dist/testing/fuzz.js.map +1 -1
  67. package/dist/testing/index.d.ts +4 -0
  68. package/dist/testing/index.d.ts.map +1 -1
  69. package/dist/testing/index.js +2 -0
  70. package/dist/testing/index.js.map +1 -1
  71. package/dist/utils/telemetry/instrumentation.d.ts.map +1 -1
  72. package/dist/utils/telemetry/instrumentation.js +5 -6
  73. package/dist/utils/telemetry/instrumentation.js.map +1 -1
  74. package/package.json +40 -35
  75. package/scripts/devcheck.ts +35 -4
  76. package/scripts/lint-packaging.ts +116 -0
  77. package/scripts/list-skills.ts +170 -0
  78. package/skills/api-workers/SKILL.md +15 -1
  79. package/skills/field-test/SKILL.md +96 -90
  80. package/skills/maintenance/SKILL.md +3 -1
  81. package/skills/multi-server-orchestration/SKILL.md +123 -0
  82. package/skills/multi-server-orchestration/references/greenfield-buildout.md +215 -0
  83. package/skills/multi-server-orchestration/references/maintenance-pass.md +119 -0
  84. package/skills/multi-server-orchestration/references/release-pass.md +189 -0
  85. package/skills/polish-docs-meta/SKILL.md +1 -1
  86. package/skills/polish-docs-meta/references/package-meta.md +1 -1
  87. package/skills/polish-docs-meta/references/readme.md +10 -7
  88. package/skills/polish-docs-meta/references/server-json.md +2 -2
  89. package/skills/release-and-publish/SKILL.md +38 -7
  90. package/skills/setup/SKILL.md +1 -1
  91. package/templates/AGENTS.md +37 -0
  92. package/templates/CLAUDE.md +37 -0
  93. package/templates/_.mcpbignore +13 -0
  94. package/templates/manifest.json +26 -0
  95. package/templates/package.json +6 -1
@@ -4,7 +4,7 @@ description: >
4
4
  Exercise tools, resources, and prompts against a live HTTP server via MCP JSON-RPC over curl. Starts the server, surfaces the catalog, runs real and adversarial inputs, and produces a tight report with concrete findings and numbered follow-up options. Use after adding or modifying definitions, or when the user asks to test, try out, or verify their MCP surface.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "2.4"
7
+ version: "2.5"
8
8
  audience: external
9
9
  type: debug
10
10
  ---
@@ -27,115 +27,117 @@ This skill drives an HTTP server because curl + JSON-RPC is the most reliable ha
27
27
 
28
28
  ### 1. Start the server
29
29
 
30
- Write the helper to `/tmp/mcp-field-test.sh` once, then source it in every subsequent Bash call. Helper keeps PID / URL / session id in a per-`$PWD` state file (`/tmp/mcp-field-test-<hash>.env`) so state survives across tool invocations and concurrent field-tests in different project trees don't clobber each other.
30
+ Generate a 10-character alphanumeric ID (e.g. `9DJ73-K103L`) and write the helper to `/tmp/<project-name>-field-test-<ID>.sh`. Use that exact path in every subsequent Bash call. **Two agents in the same project tree must pick different IDs** that's what keeps their helper files, server logs, and call scratch from colliding.
31
+
32
+ The helper itself is **stateless** — every function takes the IDs it needs (server `pid`, server `url`, MCP `sid`, server log path) as positional args. `mcp_start` prints them; the agent threads them through every later call. No env vars, no shared state files.
31
33
 
32
34
  ```bash
33
- cat > /tmp/mcp-field-test.sh <<'HELPER_EOF'
35
+ # Pick your ID — example below uses 9DJ73-K103L. Substitute your own.
36
+ # (Helper path also encodes the project name so /tmp/ stays grep-friendly.)
37
+ cat > /tmp/<project-name>-field-test-9DJ73-K103L.sh <<'HELPER_EOF'
34
38
  #!/bin/bash
35
- # Field-test helper: manage an MCP HTTP server + JSON-RPC session across shell calls.
39
+ # Field-test helper: stateless wrappers around an MCP HTTP server + JSON-RPC
40
+ # session. Every function takes the IDs it needs as positional args — the agent
41
+ # threads pid/url/sid/log through each call rather than relying on a state
42
+ # file or env vars (the Bash tool wipes shell state between calls, and a
43
+ # pointer file would race the same way two agents race on shared state).
44
+ # See https://github.com/cyanheads/mcp-ts-core/issues/90, #144.
45
+ #
36
46
  # Surfaces failures aggressively — field test is for finding things that fail,
37
47
  # so the helper auto-tails logs and prints HTTP status/body on errors instead
38
48
  # of swallowing them.
39
- #
40
- # State and log paths are namespaced by an 8-char hash of $PWD so concurrent
41
- # field-tests across different project trees don't clobber each other (see
42
- # https://github.com/cyanheads/mcp-ts-core/issues/90).
43
- PREFIX="/tmp/mcp-field-test-$(printf '%s' "$PWD" | shasum | cut -c1-8)"
44
- STATE_FILE="${PREFIX}.env"
45
- BUILD_LOG="${PREFIX}-build.log"
46
- SERVER_LOG="${PREFIX}-server.log"
47
- [ -f "$STATE_FILE" ] && . "$STATE_FILE"
48
49
 
50
+ # Usage: mcp_start /path/to/server
51
+ # Builds, starts the HTTP server in the background, waits for the listen line,
52
+ # and prints: ready pid=<n> url=<u> port=<n> log=<path>
53
+ # Capture these — every later helper takes them as args.
49
54
  mcp_start() {
50
55
  local dir="${1:-$PWD}"
51
- echo "building $dir ..."
52
- if ! (cd "$dir" && bun run rebuild) >"$BUILD_LOG" 2>&1; then
53
- echo "BUILD FAILED last 30 lines of $BUILD_LOG:"
54
- tail -30 "$BUILD_LOG"
56
+ local build_log; build_log=$(mktemp /tmp/mcp-field-test-build.XXXXXX)
57
+ echo "building $dir ..." >&2
58
+ if ! (cd "$dir" && bun run rebuild) >"$build_log" 2>&1; then
59
+ echo "BUILD FAILED — last 30 lines of $build_log:" >&2
60
+ tail -30 "$build_log" >&2
55
61
  return 1
56
62
  fi
57
- echo "starting server ..."
58
- (cd "$dir" && bun run start:http) >"$SERVER_LOG" 2>&1 &
63
+ rm -f "$build_log"
64
+ local server_log; server_log=$(mktemp /tmp/mcp-field-test-server.XXXXXX)
65
+ echo "starting server ..." >&2
66
+ (cd "$dir" && bun run start:http) >"$server_log" 2>&1 &
59
67
  local pid=$!
60
68
  local line=""
61
69
  for _ in $(seq 1 40); do
62
- line=$(grep -Eo 'listening at http://[^" ]+/mcp' "$SERVER_LOG" | head -1)
70
+ line=$(grep -Eo 'listening at http://[^" ]+/mcp' "$server_log" | head -1)
63
71
  [ -n "$line" ] && break
64
72
  sleep 0.25
65
73
  done
66
74
  if [ -z "$line" ]; then
67
- echo "server failed to start within 10s — last 30 lines of $SERVER_LOG:"
68
- tail -30 "$SERVER_LOG"
75
+ echo "server failed to start within 10s — last 30 lines of $server_log:" >&2
76
+ tail -30 "$server_log" >&2
69
77
  kill "$pid" 2>/dev/null
78
+ rm -f "$server_log"
70
79
  return 1
71
80
  fi
72
81
  local url="${line#listening at }"
73
82
  local port; port=$(echo "$url" | sed -E 's|.*:([0-9]+)/.*|\1|')
74
- cat > "$STATE_FILE" <<EOF
75
- export MCP_PID=$pid
76
- export MCP_URL=$url
77
- export MCP_PORT=$port
78
- EOF
79
- . "$STATE_FILE"
80
- echo "ready pid=$pid url=$url"
83
+ echo "ready pid=$pid url=$url port=$port log=$server_log"
81
84
  }
82
85
 
86
+ # Usage: mcp_init <url>
87
+ # Runs `initialize`, sends `notifications/initialized`, prints: ready sid=<id>
83
88
  mcp_init() {
84
- [ -z "$MCP_URL" ] && { echo "run mcp_start first"; return 1; }
85
- local hdr="${PREFIX}-init-headers.txt"
86
- local body_file="${PREFIX}-init-body.txt"
87
- local status
88
- status=$(curl -sS -D "$hdr" -o "$body_file" -w '%{http_code}' -X POST "$MCP_URL" \
89
+ local url="$1"
90
+ [ -z "$url" ] && { echo "usage: mcp_init <url>" >&2; return 1; }
91
+ local hdr; hdr=$(mktemp)
92
+ local body_file; body_file=$(mktemp)
93
+ local code
94
+ code=$(curl -sS -D "$hdr" -o "$body_file" -w '%{http_code}' -X POST "$url" \
89
95
  -H "Content-Type: application/json" \
90
96
  -H "Accept: application/json, text/event-stream" \
91
- -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"field-test","version":"2.3"}}}')
97
+ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"field-test","version":"2.5"}}}')
92
98
  local sid; sid=$(grep -i '^mcp-session-id:' "$hdr" | awk '{print $2}' | tr -d '\r\n')
93
99
  if [ -z "$sid" ]; then
94
- echo "init failed — HTTP $status, no Mcp-Session-Id header returned"
95
- echo "--- response body ---"
96
- cat "$body_file"
97
- echo "--- response headers ---"
98
- cat "$hdr"
100
+ echo "init failed — HTTP $code, no Mcp-Session-Id header returned" >&2
101
+ echo "--- response body ---" >&2
102
+ cat "$body_file" >&2
103
+ echo "--- response headers ---" >&2
104
+ cat "$hdr" >&2
105
+ rm -f "$hdr" "$body_file"
99
106
  return 1
100
107
  fi
101
- cat > "$STATE_FILE" <<EOF
102
- export MCP_PID=$MCP_PID
103
- export MCP_URL=$MCP_URL
104
- export MCP_PORT=$MCP_PORT
105
- export MCP_SID=$sid
106
- EOF
107
- . "$STATE_FILE"
108
- curl -sS -X POST "$MCP_URL" \
108
+ curl -sS -X POST "$url" \
109
109
  -H "Content-Type: application/json" \
110
110
  -H "Accept: application/json, text/event-stream" \
111
111
  -H "Mcp-Session-Id: $sid" \
112
112
  -d '{"jsonrpc":"2.0","method":"notifications/initialized"}' >/dev/null
113
- echo "session=$sid (HTTP $status)"
113
+ rm -f "$hdr" "$body_file"
114
+ echo "ready sid=$sid (HTTP $code)"
114
115
  }
115
116
 
116
- # Usage: mcp_call METHOD [JSON_PARAMS]
117
+ # Usage: mcp_call <url> <sid> <method> [JSON_PARAMS]
117
118
  # Prints the JSON-RPC response. SSE framing is stripped when present; on
118
119
  # non-SSE responses the raw body is printed instead so plain-JSON error
119
120
  # replies (HTTP 4xx/5xx) still surface. Pipe to `jq`.
120
121
  mcp_call() {
121
- [ -z "$MCP_SID" ] && { echo "run mcp_init first"; return 1; }
122
- local method="$1"; local params="${2:-}"
122
+ local url="$1"; local sid="$2"; local method="$3"; local params="${4:-}"
123
+ [ -z "$url" ] || [ -z "$sid" ] || [ -z "$method" ] && { echo "usage: mcp_call <url> <sid> <method> [params]" >&2; return 1; }
123
124
  local body
124
125
  if [ -z "$params" ]; then
125
126
  body=$(printf '{"jsonrpc":"2.0","id":%d,"method":"%s"}' "$RANDOM" "$method")
126
127
  else
127
128
  body=$(printf '{"jsonrpc":"2.0","id":%d,"method":"%s","params":%s}' "$RANDOM" "$method" "$params")
128
129
  fi
129
- local resp_file="${PREFIX}-call-body.txt"
130
- local status
131
- status=$(curl -sS -o "$resp_file" -w '%{http_code}' -X POST "$MCP_URL" \
130
+ local resp_file; resp_file=$(mktemp)
131
+ local code
132
+ code=$(curl -sS -o "$resp_file" -w '%{http_code}' -X POST "$url" \
132
133
  -H "Content-Type: application/json" \
133
134
  -H "Accept: application/json, text/event-stream" \
134
- -H "Mcp-Session-Id: $MCP_SID" \
135
+ -H "Mcp-Session-Id: $sid" \
135
136
  -d "$body")
136
- if [ "$status" -ge 400 ]; then
137
- echo "HTTP $status from $method — response:" >&2
137
+ if [ "$code" -ge 400 ]; then
138
+ echo "HTTP $code from $method — response:" >&2
138
139
  cat "$resp_file" >&2
140
+ rm -f "$resp_file"
139
141
  return 1
140
142
  fi
141
143
  local sse; sse=$(sed -n 's/^data: //p' "$resp_file")
@@ -144,46 +146,49 @@ mcp_call() {
144
146
  else
145
147
  cat "$resp_file"
146
148
  fi
149
+ rm -f "$resp_file"
147
150
  }
148
151
 
149
- # Tail the server log. Useful when a call surprises you — pino startup banner,
150
- # definition lint diagnostics, request handler errors, upstream calls, and
151
- # rate-limit warnings live in the per-session server log.
152
- # Usage: mcp_log [N] (default: 50 lines)
152
+ # Usage: mcp_log <server-log-path> [N] (default: 50 lines)
153
+ # Tail the per-server log printed by mcp_start. Useful when a call surprises
154
+ # you pino startup banner, definition lint diagnostics, request handler
155
+ # errors, upstream calls, and rate-limit warnings all land here.
153
156
  mcp_log() {
154
- local n="${1:-50}"
155
- tail -n "$n" "$SERVER_LOG"
157
+ local log="$1"; local n="${2:-50}"
158
+ [ -z "$log" ] && { echo "usage: mcp_log <log-path> [n]" >&2; return 1; }
159
+ tail -n "$n" "$log"
156
160
  }
157
161
 
162
+ # Usage: mcp_stop <pid> [server-log-path]
163
+ # Kills the background server. Removes the server log if a path is given.
158
164
  mcp_stop() {
159
- if [ -z "$MCP_PID" ]; then
160
- rm -f "$STATE_FILE"
161
- echo "no PID to stop"
162
- return 0
163
- fi
164
- kill "$MCP_PID" 2>/dev/null
165
+ local pid="$1"; local log="${2:-}"
166
+ [ -z "$pid" ] && { echo "usage: mcp_stop <pid> [log-path]" >&2; return 1; }
167
+ kill "$pid" 2>/dev/null
165
168
  for _ in $(seq 1 12); do
166
- kill -0 "$MCP_PID" 2>/dev/null || break
169
+ kill -0 "$pid" 2>/dev/null || break
167
170
  sleep 0.25
168
171
  done
169
- if kill -0 "$MCP_PID" 2>/dev/null; then
170
- echo "PID $MCP_PID didn't exit on SIGTERM — sending SIGKILL"
171
- kill -9 "$MCP_PID" 2>/dev/null
172
+ if kill -0 "$pid" 2>/dev/null; then
173
+ echo "PID $pid didn't exit on SIGTERM — sending SIGKILL"
174
+ kill -9 "$pid" 2>/dev/null
172
175
  sleep 0.5
173
176
  fi
174
- if kill -0 "$MCP_PID" 2>/dev/null; then
175
- echo "WARNING: PID $MCP_PID still alive after SIGKILL"
177
+ if kill -0 "$pid" 2>/dev/null; then
178
+ echo "WARNING: PID $pid still alive after SIGKILL"
176
179
  else
177
- echo "stopped pid=$MCP_PID"
180
+ echo "stopped pid=$pid"
178
181
  fi
179
- rm -f "$STATE_FILE"
182
+ [ -n "$log" ] && rm -f "$log"
180
183
  }
181
184
  HELPER_EOF
182
185
 
183
- . /tmp/mcp-field-test.sh
186
+ . /tmp/<project-name>-field-test-9DJ73-K103L.sh
184
187
  mcp_start /absolute/path/to/server # replace with the target server
185
188
  ```
186
189
 
190
+ Capture `pid`, `url`, `port`, `log` from the `mcp_start` output — every later call takes them as positional args. Two agents running concurrently in the same project tree each pick their own ID, so their helper paths, server logs, and call scratch never share a name.
191
+
187
192
  **Notes**
188
193
 
189
194
  - `MCP_HTTP_PORT` is a *starting* port — the server auto-increments if taken. Helper parses the real URL from the log (`HTTP transport listening at ...`).
@@ -193,19 +198,19 @@ mcp_start /absolute/path/to/server # replace with the target server
193
198
  ### 2. Initialize the session
194
199
 
195
200
  ```bash
196
- . /tmp/mcp-field-test.sh
197
- mcp_init
201
+ . /tmp/<project-name>-field-test-<ID>.sh
202
+ mcp_init <url-from-mcp_start>
198
203
  ```
199
204
 
200
- Runs `initialize`, captures the session id, sends `notifications/initialized`.
205
+ Runs `initialize`, sends `notifications/initialized`, prints `sid=<id>` to capture for `mcp_call`.
201
206
 
202
207
  ### 3. Surface the catalog
203
208
 
204
209
  ```bash
205
- . /tmp/mcp-field-test.sh
206
- mcp_call tools/list | jq '.result.tools[] | {name, description, inputSchema, outputSchema}'
207
- mcp_call resources/list | jq '.result.resources[] | {uri, name, mimeType}'
208
- mcp_call prompts/list | jq '.result.prompts[] | {name, description, arguments}'
210
+ . /tmp/<project-name>-field-test-<ID>.sh
211
+ mcp_call <url> <sid> tools/list | jq '.result.tools[] | {name, description, inputSchema, outputSchema}'
212
+ mcp_call <url> <sid> resources/list | jq '.result.resources[] | {uri, name, mimeType}'
213
+ mcp_call <url> <sid> prompts/list | jq '.result.prompts[] | {name, description, arguments}'
209
214
  ```
210
215
 
211
216
  Present a compact catalog to the user: each definition's name + 1-line description. Flag vague or missing descriptions as you go — those feed into the report. Use this to build the test plan.
@@ -262,7 +267,7 @@ Use `TaskCreate` — one task per definition. Mark complete as you go. Don't bat
262
267
 
263
268
  For each call, capture: input sent, response (trim huge payloads to files), whether `isError: true` appeared, anything surprising (slow response, parity drift, unhelpful text, crash).
264
269
 
265
- When a call surprises you — slow, hangs, returns terse output, surfaces an unhelpful error — run `. /tmp/mcp-field-test.sh && mcp_log` to tail the server log. The pino startup banner, request handler errors, upstream API call traces, and rate-limit warnings all land in the per-session server log (read via `mcp_log`) rather than coming back through `mcp_call`. Don't guess at runtime behavior from response text alone.
270
+ When a call surprises you — slow, hangs, returns terse output, surfaces an unhelpful error — run `. /tmp/<project-name>-field-test-<ID>.sh && mcp_log <log>` to tail the server log. The pino startup banner, request handler errors, upstream API call traces, and rate-limit warnings all land in the per-server log (read via `mcp_log`) rather than coming back through `mcp_call`. Don't guess at runtime behavior from response text alone.
266
271
 
267
272
  **Interpreting responses**
268
273
 
@@ -275,11 +280,12 @@ When a call surprises you — slow, hangs, returns terse output, surfaces an unh
275
280
  ### 6. Tear down
276
281
 
277
282
  ```bash
278
- . /tmp/mcp-field-test.sh
279
- mcp_stop
283
+ . /tmp/<project-name>-field-test-<ID>.sh
284
+ mcp_stop <pid> <log>
285
+ rm -f /tmp/<project-name>-field-test-<ID>.sh
280
286
  ```
281
287
 
282
- Kills the background server, clears state. Do this *before* writing the report so nothing leaks into the next session.
288
+ Kills the background server, removes the server log, then removes the helper script itself. Do this *before* writing the report so nothing leaks into the next session.
283
289
 
284
290
  ### 7. Report
285
291
 
@@ -4,7 +4,7 @@ description: >
4
4
  Investigate, adopt, and verify dependency updates — with special handling for `@cyanheads/mcp-ts-core`. Captures what changed, understands why, cross-references against the codebase, adopts framework improvements, syncs project skills, and runs final checks. Supports two entry modes: run the full flow end-to-end, or review updates you already applied.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "2.1"
7
+ version: "2.2"
8
8
  audience: external
9
9
  type: workflow
10
10
  ---
@@ -202,6 +202,8 @@ In **Mode B**, the user already ran rebuild + test before invoking this skill, b
202
202
 
203
203
  Fix anything that fails. Re-run until clean.
204
204
 
205
+ **Transitive advisory triage.** If `bun audit` (inside devcheck) reports a vulnerability in a transitive dep, run `bun run audit:refresh` before treating it as real. Bun's `bun update` is sticky on transitive resolutions — it keeps lockfile entries even when a parent's range allows a newer patched version. `audit:refresh` deletes `bun.lock`, reinstalls, and re-audits; if the advisory disappears, it was a stale-lockfile false positive (commit the refreshed lockfile). If it survives, it's real — patch via `package.json` `overrides` or nudge upstream.
206
+
205
207
  ### 8. Summary
206
208
 
207
209
  Present a concise numbered summary to the user:
@@ -0,0 +1,123 @@
1
+ ---
2
+ name: multi-server-orchestration
3
+ description: >
4
+ Orchestrate parallel sub-agent fanouts across one or more MCP server projects — the same workflow run independently per target. Use for greenfield builds across N new servers, maintenance passes across N existing ones, or any repeatable workflow that benefits from fresh-context per-target sub-agents. Encodes the orient template every sub-agent needs (CLAUDE.md chain + list-skills + spec artifacts), the universal hard rules around git tooling and authorization, common gotchas that bite across runs, and a router into per-scenario references for the phase pattern.
5
+ metadata:
6
+ author: cyanheads
7
+ version: "1.0"
8
+ audience: external
9
+ type: workflow
10
+ ---
11
+
12
+ ## When to Use
13
+
14
+ - Same workflow driven across multiple MCP server projects in parallel — greenfield builds, maintenance updates, security audits, design extensions
15
+ - Single server, but the work is large enough to want fresh-context sub-agents per phase rather than one continuous session
16
+ - Any case where the per-target work is independent and benefits from devcheck-gated handoffs between phases
17
+
18
+ Skip this skill for in-session tweaks to a single server — use `add-tool`, `maintenance`, `polish-docs-meta`, etc. directly.
19
+
20
+ ## First Pass
21
+
22
+ Read this SKILL.md end-to-end first — the concepts, orient block, and hard rules below are universal across scenarios. Then:
23
+
24
+ 1. **Capture the target list.** N projects with absolute paths. Per-target metadata as relevant (framework version, gold-standard reference, GH owner/org). If the user named a single target, that's still N = 1.
25
+ 2. **Identify the scenario** from user intent first, then sanity-check against project state. If user intent is ambiguous, use the state heuristics below to surface a recommendation and confirm with the user — don't pick silently.
26
+
27
+ | Likely scenario | State signals (per target) |
28
+ |:----------------|:---------------------------|
29
+ | **Greenfield build-out** | Echo definitions still present in `src/mcp-server/{tools,resources,prompts}/definitions/`, no real services in `src/services/`, no released changelog files under `changelog/<minor>.x/`, `package.json` version is `0.0.0` or unset |
30
+ | **Maintenance pass** | Real tool/resource definitions present, framework deps installed, `bun outdated` reports updates available, at least one prior release tagged |
31
+ | **Release pass** | Working tree has uncommitted/staged work, OR `git log v<latest-tag>..HEAD` shows commits since the last release that warrant a new version, OR the user explicitly asks to "ship", "release", or do a version bump |
32
+ | **Neither** | None of the above match cleanly — surface the state to the user, ask what they want |
33
+
34
+ 3. **Pick the reference** from the References table below. Each reference contains its scenario's phase pattern, per-sub-agent prompt bodies, scenario-specific gotchas, and checklist.
35
+ 4. **Surface the plan to the user** before kicking off the first fanout: scenario, target list, phase summary, gotchas in play. Get explicit authorization before any phase that commits, tags, pushes, or creates remote resources.
36
+
37
+ ## References
38
+
39
+ | Scenario | Reference | When |
40
+ |:---------|:----------|:-----|
41
+ | **Greenfield build-out** | `references/greenfield-buildout.md` | New server(s) from `bunx @cyanheads/mcp-ts-core init` driven through design → build → first release |
42
+ | **Maintenance pass** | `references/maintenance-pass.md` | Existing server(s) need `bun update --latest`, changelog investigation, framework adoption, and verification — optionally followed by a commit/push |
43
+ | **Release pass** | `references/release-pass.md` | Existing server(s) have committed/staged work that needs to ship as a new version — verification → README polish → wrapup (version + changelog + commit + tag) → publish (npm / MCP Registry / GHCR via `release-and-publish`) → optional GH issue closure |
44
+
45
+ Scenarios chain naturally: a maintenance pass often runs into a release pass; a greenfield build-out's final phase is effectively a release pass. Pick the reference for the current scope and chain explicitly if more work follows.
46
+
47
+ For scenarios not listed (security audits, design-only extensions, framework-wide migrations), the concepts/orient/rules/gotchas below are universal. Author a new reference describing the phase pattern when the workflow becomes repeatable.
48
+
49
+ ## Concepts
50
+
51
+ **Parallel fanout.** One phase = one parallel batch of sub-agents, one per target. All N agents run independently in the same orchestrator message. The orchestrator (you) collects their results, normalizes inconsistencies, then triggers the next phase.
52
+
53
+ **Sub-agent isolation.** Sub-agents do **not** inherit the parent session's `CLAUDE.md` chain or skills registry. They start with a blank slate plus the prompt you give them. Every sub-agent prompt must begin with an explicit orient sequence (see below) or it will work from defaults and pattern-matching instead of project conventions.
54
+
55
+ **Narrow scope per fanout.** A single agent doing "implement everything, write tests, run devcheck, polish, commit, tag" will exhaust its context window before finishing — the work lands on disk but the agent can't continue. Split phases narrowly so each agent finishes well under the context limit. Plan for a follow-up "finish" pass after a big work phase.
56
+
57
+ **Normalize after divergent fanouts.** Independent agents will diverge on incidental choices (scoped vs. unscoped names, script invocation form, README hero structure). When the choices should be uniform across targets, plan an explicit normalization pass after the fanout — don't expect alignment for free.
58
+
59
+ ## The Orient Block
60
+
61
+ Every parallel sub-agent prompt opens with this block. The block is **mandatory, not advisory** — the sub-agent's first six tool calls should be the orient sequence, in order. Substitute the bracketed values per target.
62
+
63
+ ```text
64
+ You are working on `[project name]` at `[project absolute path]`.
65
+
66
+ Orient first. These six steps are required before any task work — do them in
67
+ order. If any file does not exist, note it and continue.
68
+
69
+ 1. Read the global agent protocol at `~/.claude/CLAUDE.md` (or your agent's equivalent).
70
+ 2. Read the workspace-level protocol if one exists at `[workspace CLAUDE.md path]`
71
+ — skip this step if no workspace-tier protocol applies.
72
+ 3. Read the project protocol at `[project absolute path]/CLAUDE.md`.
73
+ 4. Run `cd [project absolute path] && bun run list-skills` to see the project's
74
+ available skills with descriptions and locations.
75
+ 5. Read the skill file(s) for this task: `[skill paths]`.
76
+ 6. Read the spec artifact(s) you'll work from: `[doc paths, e.g., docs/design.md]`.
77
+
78
+ Only after that, begin the task below.
79
+
80
+ **Task:** [concrete description with constraints, no-go list, expected outputs]
81
+ ```
82
+
83
+ The orient block compensates for the two pieces of context sub-agents don't inherit: the CLAUDE.md chain (global → workspace → project) and the project skill registry. Both must be reconstructed manually inside the sub-agent prompt or the agent works from defaults.
84
+
85
+ ### Prerequisite: `list-skills.ts` in each project
86
+
87
+ `list-skills.ts` is the script the orient block runs in step 4. It parses YAML frontmatter from each project-local `.claude/skills/*/SKILL.md` (falling back to `skills/`) and prints a summary an agent can read. It ships with `@cyanheads/mcp-ts-core` and is scaffolded by `init` into `scripts/list-skills.ts` with a corresponding `list-skills` package script. Projects scaffolded before the script existed pick it up via `maintenance` Phase C on the next sync.
88
+
89
+ Verify presence in each target before kicking off any fanout:
90
+
91
+ ```bash
92
+ test -f scripts/list-skills.ts && grep -q '"list-skills"' package.json
93
+ ```
94
+
95
+ ## Hard Rules
96
+
97
+ These apply to every scenario. Scenario-specific rules live in their reference.
98
+
99
+ 1. **Bash `git` only in parallel sub-agents.** Do not let parallel sub-agents call `mcp__git-mcp-server__*` tools (or any git MCP tool that holds session state across calls). The MCP server's session state (`set_working_dir`) leaks across parallel agents in the same orchestrator session, causing silent no-ops, wrong-directory operations, and false "tag already exists" errors. Bash `git` in the agent's CWD is reliable. The orchestrator may still use git-mcp-server itself in serial.
100
+ 2. **No git commits, pushes, tags, branch creation, or destructive ops without an explicit user request.** Setup-phase and build-phase work is left unstaged for review. Wrap-up phases run only after the user authorizes a commit. Honor every `~/.claude/CLAUDE.md` rule: no `git stash`, no `reset --hard`, no `restore .`, no `clean -f`, no `--no-verify`, plain `-m` commit messages, no `Co-authored-by` or `Generated with` trailers.
101
+ 3. **`bun run devcheck` is the handoff gate.** Work phases must hand back a green devcheck. If a sub-agent can't reach green, it reports the failing step verbatim and stops rather than carrying broken state forward to the next phase.
102
+ 4. **Verify sub-agent claims with a read-only check.** Agent summaries describe intent, not always reality. After any fanout that touched the filesystem, the orchestrator confirms with `git log`, `git status`, `ls`, or its own `bun run devcheck` before declaring the phase done.
103
+ 5. **Skip marketing adjectives.** In commits, tags, READMEs, and CHANGELOG entries — no "comprehensive", "robust", "enhanced", "seamless", "improved". State the change. Restate this rule in every sub-agent prompt that produces text artifacts; the global protocol's restated rules aren't visible to sub-agents at prompt time.
104
+ 6. **One scenario per orchestration run.** Don't interleave greenfield and maintenance phases against the same target in one session. If a target needs both (e.g., a build run that needs a `bun update` first), sequence them as two scenarios with a clean handoff in between.
105
+
106
+ ## Gotchas
107
+
108
+ These commonly bite across all scenarios. Scenario-specific gotchas live in their reference.
109
+
110
+ | # | Gotcha | Mitigation |
111
+ |:--|:-------|:-----------|
112
+ | 1 | Sub-agents don't inherit the CLAUDE.md chain | Orient block — global, workspace (if applicable), and project CLAUDE.md read before work |
113
+ | 2 | Sub-agents don't inherit the project skill registry | Orient block step 4 — `bun run list-skills` surfaces available skills, agent then reads task-specific ones by path |
114
+ | 3 | `git-mcp-server`'s `set_working_dir` state leaks across parallel sub-agents in the same orchestrator session | Bash `git` only in parallel sub-agents; reserve `git-mcp-server` for serial orchestrator use |
115
+ | 4 | Big work agents exhaust context before finishing — work lands on disk but the agent can't continue | Narrow scope per fanout; plan a finish-pass as the backstop in the scenario reference |
116
+ | 5 | Parallel agent self-reports describe intent, not always reality (claimed "pushed" but no remote ref exists) | Verify with read-only `git log --oneline -5`, `git ls-remote --tags origin`, `gh repo view` after every fanout that touched git |
117
+ | 6 | Independent agents diverge on incidental conventions (scoping, scripts, README hero) | Plan an explicit normalization pass; don't expect alignment for free |
118
+ | 7 | Sub-agent skips the orient block and proceeds with pattern-matched defaults | Put orient as a numbered prerequisite in the prompt with "Only after that, begin the task"; spot-check the first tool calls in the agent's response |
119
+ | 8 | Sub-agent runs `git stash` to "test something safely" | Restate the global rule verbatim in every sub-agent prompt that may touch git: "NEVER `git stash` for any reason." |
120
+
121
+ ## Extending the pattern
122
+
123
+ When a new scenario emerges that's worth codifying (security-pass fanout across N servers, framework-wide migration, etc.), author a new reference at `references/<scenario>.md` and add a row to the table above. The reference should follow the shape of the existing two: scope, pre-flight, phase table, phase details, scenario-specific gotchas, checklist. The concepts/orient/rules/gotchas in this SKILL.md don't need to be restated — the reference assumes the reader started here.