@cyanheads/mcp-ts-core 0.10.16 → 0.11.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 (151) hide show
  1. package/AGENTS.md +1 -1
  2. package/CLAUDE.md +1 -1
  3. package/README.md +2 -2
  4. package/biome.json +1 -1
  5. package/changelog/0.10.x/0.10.17.md +41 -0
  6. package/changelog/0.11.x/0.11.0.md +69 -0
  7. package/dist/cli/init.js.map +1 -1
  8. package/dist/config/index.d.ts +111 -108
  9. package/dist/config/index.d.ts.map +1 -1
  10. package/dist/config/index.js +14 -0
  11. package/dist/config/index.js.map +1 -1
  12. package/dist/config/parseEnvConfig.js.map +1 -1
  13. package/dist/core/app.js.map +1 -1
  14. package/dist/core/gcPressure.d.ts.map +1 -1
  15. package/dist/core/serverManifest.d.ts +1 -1
  16. package/dist/core/serverManifest.d.ts.map +1 -1
  17. package/dist/core/worker.d.ts.map +1 -1
  18. package/dist/core/worker.js.map +1 -1
  19. package/dist/linter/rules/format-parity-rules.js.map +1 -1
  20. package/dist/linter/rules/portability-rules.js.map +1 -1
  21. package/dist/logs/combined.log +8 -8
  22. package/dist/logs/error.log +4 -4
  23. package/dist/mcp-server/prompts/prompt-registration.d.ts.map +1 -1
  24. package/dist/mcp-server/prompts/prompt-registration.js.map +1 -1
  25. package/dist/mcp-server/resources/resource-registration.d.ts.map +1 -1
  26. package/dist/mcp-server/resources/resource-registration.js.map +1 -1
  27. package/dist/mcp-server/tasks/core/evictingTaskMessageQueue.d.ts +53 -0
  28. package/dist/mcp-server/tasks/core/evictingTaskMessageQueue.d.ts.map +1 -0
  29. package/dist/mcp-server/tasks/core/evictingTaskMessageQueue.js +65 -0
  30. package/dist/mcp-server/tasks/core/evictingTaskMessageQueue.js.map +1 -0
  31. package/dist/mcp-server/tasks/core/sessionAwareTaskStore.d.ts +30 -2
  32. package/dist/mcp-server/tasks/core/sessionAwareTaskStore.d.ts.map +1 -1
  33. package/dist/mcp-server/tasks/core/sessionAwareTaskStore.js +65 -4
  34. package/dist/mcp-server/tasks/core/sessionAwareTaskStore.js.map +1 -1
  35. package/dist/mcp-server/tasks/core/storageBackedTaskStore.d.ts.map +1 -1
  36. package/dist/mcp-server/tasks/core/storageBackedTaskStore.js.map +1 -1
  37. package/dist/mcp-server/tasks/core/taskManager.d.ts.map +1 -1
  38. package/dist/mcp-server/tasks/core/taskManager.js +6 -2
  39. package/dist/mcp-server/tasks/core/taskManager.js.map +1 -1
  40. package/dist/mcp-server/tasks/core/taskTypes.d.ts +7 -0
  41. package/dist/mcp-server/tasks/core/taskTypes.d.ts.map +1 -1
  42. package/dist/mcp-server/tasks/core/taskTypes.js +7 -0
  43. package/dist/mcp-server/tasks/core/taskTypes.js.map +1 -1
  44. package/dist/mcp-server/tools/tool-registration.d.ts.map +1 -1
  45. package/dist/mcp-server/tools/tool-registration.js +1 -2
  46. package/dist/mcp-server/tools/tool-registration.js.map +1 -1
  47. package/dist/mcp-server/tools/utils/toolHandlerFactory.js.map +1 -1
  48. package/dist/mcp-server/transports/auth/strategies/jwtStrategy.d.ts.map +1 -1
  49. package/dist/mcp-server/transports/auth/strategies/jwtStrategy.js.map +1 -1
  50. package/dist/mcp-server/transports/auth/strategies/oauthStrategy.d.ts.map +1 -1
  51. package/dist/mcp-server/transports/auth/strategies/oauthStrategy.js.map +1 -1
  52. package/dist/mcp-server/transports/heartbeat.d.ts.map +1 -1
  53. package/dist/mcp-server/transports/http/httpErrorHandler.d.ts.map +1 -1
  54. package/dist/mcp-server/transports/http/landing-page/handler.d.ts.map +1 -1
  55. package/dist/mcp-server/transports/http/robotsTxt.d.ts.map +1 -1
  56. package/dist/mcp-server/transports/http/serverCard.d.ts.map +1 -1
  57. package/dist/mcp-server/transports/http/sessionStore.d.ts.map +1 -1
  58. package/dist/mcp-server/transports/http/sessionStore.js.map +1 -1
  59. package/dist/mcp-server/transports/manager.d.ts.map +1 -1
  60. package/dist/mcp-server/transports/manager.js.map +1 -1
  61. package/dist/services/canvas/core/CanvasInstance.d.ts.map +1 -1
  62. package/dist/services/canvas/core/CanvasInstance.js.map +1 -1
  63. package/dist/services/canvas/core/CanvasRegistry.d.ts.map +1 -1
  64. package/dist/services/canvas/core/CanvasRegistry.js.map +1 -1
  65. package/dist/services/canvas/core/DataCanvas.d.ts.map +1 -1
  66. package/dist/services/canvas/core/DataCanvas.js.map +1 -1
  67. package/dist/services/canvas/core/canvasFactory.d.ts.map +1 -1
  68. package/dist/services/canvas/core/canvasFactory.js +1 -0
  69. package/dist/services/canvas/core/canvasFactory.js.map +1 -1
  70. package/dist/services/canvas/core/sqlGate.d.ts +10 -10
  71. package/dist/services/canvas/core/sqlGate.d.ts.map +1 -1
  72. package/dist/services/canvas/providers/duckdb/DuckdbProvider.d.ts +16 -0
  73. package/dist/services/canvas/providers/duckdb/DuckdbProvider.d.ts.map +1 -1
  74. package/dist/services/canvas/providers/duckdb/DuckdbProvider.js +25 -7
  75. package/dist/services/canvas/providers/duckdb/DuckdbProvider.js.map +1 -1
  76. package/dist/services/canvas/providers/duckdb/exportWriter.d.ts +4 -2
  77. package/dist/services/canvas/providers/duckdb/exportWriter.d.ts.map +1 -1
  78. package/dist/services/canvas/providers/duckdb/exportWriter.js +4 -2
  79. package/dist/services/canvas/providers/duckdb/exportWriter.js.map +1 -1
  80. package/dist/services/graph/core/GraphService.d.ts.map +1 -1
  81. package/dist/services/graph/core/GraphService.js.map +1 -1
  82. package/dist/services/llm/providers/openrouter.provider.d.ts.map +1 -1
  83. package/dist/services/llm/providers/openrouter.provider.js.map +1 -1
  84. package/dist/services/mirror/sqlite/handle.js.map +1 -1
  85. package/dist/services/speech/core/SpeechService.d.ts.map +1 -1
  86. package/dist/services/speech/core/SpeechService.js.map +1 -1
  87. package/dist/services/speech/providers/elevenlabs.provider.d.ts.map +1 -1
  88. package/dist/services/speech/providers/whisper.provider.d.ts.map +1 -1
  89. package/dist/storage/core/StorageService.d.ts.map +1 -1
  90. package/dist/storage/core/StorageService.js.map +1 -1
  91. package/dist/storage/core/storageFactory.js.map +1 -1
  92. package/dist/storage/providers/cloudflare/d1Provider.d.ts.map +1 -1
  93. package/dist/storage/providers/cloudflare/d1Provider.js.map +1 -1
  94. package/dist/storage/providers/cloudflare/kvProvider.d.ts.map +1 -1
  95. package/dist/storage/providers/cloudflare/r2Provider.d.ts.map +1 -1
  96. package/dist/storage/providers/fileSystem/fileSystemProvider.d.ts.map +1 -1
  97. package/dist/storage/providers/inMemory/inMemoryProvider.d.ts.map +1 -1
  98. package/dist/storage/providers/supabase/supabaseProvider.d.ts.map +1 -1
  99. package/dist/storage/providers/supabase/supabaseProvider.js.map +1 -1
  100. package/dist/testing/fuzz.js.map +1 -1
  101. package/dist/testing/index.js.map +1 -1
  102. package/dist/testing/vitest.d.ts +1 -1
  103. package/dist/types-global/errors.d.ts.map +1 -1
  104. package/dist/utils/formatting/diffFormatter.d.ts.map +1 -1
  105. package/dist/utils/formatting/html.d.ts.map +1 -1
  106. package/dist/utils/formatting/html.js.map +1 -1
  107. package/dist/utils/formatting/markdownBuilder.d.ts.map +1 -1
  108. package/dist/utils/formatting/tableFormatter.d.ts.map +1 -1
  109. package/dist/utils/formatting/tableFormatter.js.map +1 -1
  110. package/dist/utils/formatting/treeFormatter.d.ts.map +1 -1
  111. package/dist/utils/internal/error-handler/errorHandler.d.ts.map +1 -1
  112. package/dist/utils/internal/logger.d.ts +19 -3
  113. package/dist/utils/internal/logger.d.ts.map +1 -1
  114. package/dist/utils/internal/logger.js +51 -21
  115. package/dist/utils/internal/logger.js.map +1 -1
  116. package/dist/utils/internal/requestContext.js.map +1 -1
  117. package/dist/utils/internal/telemetryMessages.d.ts +4 -4
  118. package/dist/utils/internal/telemetryMessages.d.ts.map +1 -1
  119. package/dist/utils/network/fetchWithTimeout.d.ts.map +1 -1
  120. package/dist/utils/network/fetchWithTimeout.js +33 -16
  121. package/dist/utils/network/fetchWithTimeout.js.map +1 -1
  122. package/dist/utils/network/httpError.js.map +1 -1
  123. package/dist/utils/network/retry.js.map +1 -1
  124. package/dist/utils/pagination/pagination.d.ts.map +1 -1
  125. package/dist/utils/parsing/csvParser.d.ts.map +1 -1
  126. package/dist/utils/parsing/dateParser.d.ts.map +1 -1
  127. package/dist/utils/parsing/frontmatterParser.d.ts.map +1 -1
  128. package/dist/utils/parsing/htmlExtractor.d.ts.map +1 -1
  129. package/dist/utils/parsing/jsonParser.d.ts.map +1 -1
  130. package/dist/utils/parsing/jsonParser.js.map +1 -1
  131. package/dist/utils/parsing/pdfParser.d.ts.map +1 -1
  132. package/dist/utils/parsing/pdfParser.js.map +1 -1
  133. package/dist/utils/parsing/xmlParser.d.ts.map +1 -1
  134. package/dist/utils/parsing/yamlParser.d.ts.map +1 -1
  135. package/dist/utils/scheduling/scheduler.d.ts.map +1 -1
  136. package/dist/utils/security/idGenerator.d.ts.map +1 -1
  137. package/dist/utils/security/idGenerator.js.map +1 -1
  138. package/dist/utils/security/rateLimiter.d.ts.map +1 -1
  139. package/dist/utils/security/rateLimiter.js.map +1 -1
  140. package/dist/utils/security/sanitization.d.ts.map +1 -1
  141. package/dist/utils/security/sanitization.js.map +1 -1
  142. package/package.json +43 -34
  143. package/scripts/check-dependency-specifiers.ts +13 -7
  144. package/scripts/clean-mcpb.ts +48 -15
  145. package/scripts/lint-packaging.ts +36 -13
  146. package/skills/api-canvas/SKILL.md +4 -3
  147. package/skills/api-config/SKILL.md +2 -1
  148. package/skills/field-test/SKILL.md +71 -28
  149. package/templates/AGENTS.md +1 -1
  150. package/templates/CLAUDE.md +1 -1
  151. package/templates/package.json +5 -5
@@ -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.7"
7
+ version: "2.8"
8
8
  audience: external
9
9
  type: debug
10
10
  ---
@@ -29,7 +29,7 @@ This skill drives an HTTP server because curl + JSON-RPC is the most reliable ha
29
29
 
30
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
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.
32
+ The helper itself is **stateless** — every function takes the IDs it needs (server `pid`, `url`, `port`, 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.
33
33
 
34
34
  ```bash
35
35
  # Pick your ID — example below uses 9DJ73-K103L. Substitute your own.
@@ -38,7 +38,7 @@ cat > /tmp/<project-name>-field-test-9DJ73-K103L.sh <<'HELPER_EOF'
38
38
  #!/bin/bash
39
39
  # Field-test helper: stateless wrappers around an MCP HTTP server + JSON-RPC
40
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
41
+ # threads pid/url/port/sid/log through each call rather than relying on a state
42
42
  # file or env vars (the Bash tool wipes shell state between calls, and a
43
43
  # pointer file would race the same way two agents race on shared state).
44
44
  # See https://github.com/cyanheads/mcp-ts-core/issues/90, #144.
@@ -47,12 +47,14 @@ cat > /tmp/<project-name>-field-test-9DJ73-K103L.sh <<'HELPER_EOF'
47
47
  # so the helper auto-tails logs and prints HTTP status/body on errors instead
48
48
  # of swallowing them.
49
49
 
50
- # Usage: mcp_start /path/to/server
50
+ # Usage: mcp_start /path/to/server [startup-timeout-seconds] (default: 30)
51
51
  # Builds, starts the HTTP server in the background, waits for the listen line,
52
52
  # and prints: ready pid=<n> url=<u> port=<n> log=<path>
53
- # Capture these — every later helper takes them as args.
53
+ # Capture these — every later helper takes them as args. Raise the timeout for
54
+ # servers that build a local index at boot.
54
55
  mcp_start() {
55
56
  local dir="${1:-$PWD}"
57
+ local timeout="${2:-30}"
56
58
  local build_log; build_log=$(mktemp /tmp/mcp-field-test-build.XXXXXX)
57
59
  echo "building $dir ..." >&2
58
60
  if ! (cd "$dir" && bun run rebuild) >"$build_log" 2>&1; then
@@ -66,13 +68,21 @@ mcp_start() {
66
68
  (cd "$dir" && bun run start:http) >"$server_log" 2>&1 &
67
69
  local pid=$!
68
70
  local line=""
69
- for _ in $(seq 1 40); do
71
+ local waited=0
72
+ while [ "$waited" -lt "$((timeout * 4))" ]; do
70
73
  line=$(grep -Eo 'listening at http://[^" ]+/mcp' "$server_log" | head -1)
71
74
  [ -n "$line" ] && break
75
+ if ! kill -0 "$pid" 2>/dev/null; then
76
+ echo "server exited during startup — last 30 lines of $server_log:" >&2
77
+ tail -30 "$server_log" >&2
78
+ rm -f "$server_log"
79
+ return 1
80
+ fi
72
81
  sleep 0.25
82
+ waited=$((waited + 1))
73
83
  done
74
84
  if [ -z "$line" ]; then
75
- echo "server failed to start within 10s — last 30 lines of $server_log:" >&2
85
+ echo "server failed to start within ${timeout}s — last 30 lines of $server_log:" >&2
76
86
  tail -30 "$server_log" >&2
77
87
  kill "$pid" 2>/dev/null
78
88
  rm -f "$server_log"
@@ -84,17 +94,22 @@ mcp_start() {
84
94
  }
85
95
 
86
96
  # Usage: mcp_init <url>
87
- # Runs `initialize`, sends `notifications/initialized`, prints: ready sid=<id>
97
+ # Runs `initialize`, sends `notifications/initialized`, prints:
98
+ # ready sid=<id> protocol=<negotiated-version>
99
+ # A negotiated version older than the requested one means the server capped it
100
+ # — note that in the report; you are then testing an older protocol than a
101
+ # current client would use.
88
102
  mcp_init() {
89
103
  local url="$1"
90
104
  [ -z "$url" ] && { echo "usage: mcp_init <url>" >&2; return 1; }
105
+ local want="${MCP_FIELD_TEST_PROTOCOL:-2025-11-25}"
91
106
  local hdr; hdr=$(mktemp)
92
107
  local body_file; body_file=$(mktemp)
93
108
  local code
94
109
  code=$(curl -sS -D "$hdr" -o "$body_file" -w '%{http_code}' -X POST "$url" \
95
110
  -H "Content-Type: application/json" \
96
111
  -H "Accept: application/json, text/event-stream" \
97
- -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"field-test","version":"2.5"}}}')
112
+ -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"$want\",\"capabilities\":{},\"clientInfo\":{\"name\":\"field-test\",\"version\":\"1.0.0\"}}}")
98
113
  local sid; sid=$(grep -i '^mcp-session-id:' "$hdr" | awk '{print $2}' | tr -d '\r\n')
99
114
  if [ -z "$sid" ]; then
100
115
  echo "init failed — HTTP $code, no Mcp-Session-Id header returned" >&2
@@ -105,19 +120,23 @@ mcp_init() {
105
120
  rm -f "$hdr" "$body_file"
106
121
  return 1
107
122
  fi
123
+ local got; got=$(sed -n 's/^data: //p' "$body_file" | grep -o '"protocolVersion":"[^"]*"' | head -1 | cut -d'"' -f4)
124
+ [ -z "$got" ] && got=$(grep -o '"protocolVersion":"[^"]*"' "$body_file" | head -1 | cut -d'"' -f4)
108
125
  curl -sS -X POST "$url" \
109
126
  -H "Content-Type: application/json" \
110
127
  -H "Accept: application/json, text/event-stream" \
111
128
  -H "Mcp-Session-Id: $sid" \
112
129
  -d '{"jsonrpc":"2.0","method":"notifications/initialized"}' >/dev/null
113
130
  rm -f "$hdr" "$body_file"
114
- echo "ready sid=$sid (HTTP $code)"
131
+ echo "ready sid=$sid protocol=${got:-unknown} requested=$want (HTTP $code)"
115
132
  }
116
133
 
117
134
  # Usage: mcp_call <url> <sid> <method> [JSON_PARAMS]
118
- # Prints the JSON-RPC response. SSE framing is stripped when present; on
119
- # non-SSE responses the raw body is printed instead so plain-JSON error
120
- # replies (HTTP 4xx/5xx) still surface. Pipe to `jq`.
135
+ # Prints the JSON-RPC response. SSE framing is stripped when present, and only
136
+ # the reply is emitted (a single POST can also carry progress notifications, so
137
+ # emitting every event would break `| jq .result`). A transport failure or an
138
+ # HTTP >= 400 prints the details and returns non-zero — it never returns 0 with
139
+ # empty output. Pipe to `jq`.
121
140
  mcp_call() {
122
141
  local url="$1"; local sid="$2"; local method="$3"; local params="${4:-}"
123
142
  [ -z "$url" ] || [ -z "$sid" ] || [ -z "$method" ] && { echo "usage: mcp_call <url> <sid> <method> [params]" >&2; return 1; }
@@ -128,12 +147,19 @@ mcp_call() {
128
147
  body=$(printf '{"jsonrpc":"2.0","id":%d,"method":"%s","params":%s}' "$RANDOM" "$method" "$params")
129
148
  fi
130
149
  local resp_file; resp_file=$(mktemp)
131
- local code
150
+ local code curl_rc
132
151
  code=$(curl -sS -o "$resp_file" -w '%{http_code}' -X POST "$url" \
133
152
  -H "Content-Type: application/json" \
134
153
  -H "Accept: application/json, text/event-stream" \
135
154
  -H "Mcp-Session-Id: $sid" \
136
155
  -d "$body")
156
+ curl_rc=$?
157
+ if [ "$curl_rc" -ne 0 ] || [ -z "$code" ] || [ "$code" = "000" ]; then
158
+ echo "TRANSPORT FAILURE calling $method — curl exit $curl_rc, http_code '${code:-none}'." >&2
159
+ echo "Server not reachable at $url (check it is still running: mcp_log <log>)." >&2
160
+ rm -f "$resp_file"
161
+ return 1
162
+ fi
137
163
  if [ "$code" -ge 400 ]; then
138
164
  echo "HTTP $code from $method — response:" >&2
139
165
  cat "$resp_file" >&2
@@ -142,7 +168,8 @@ mcp_call() {
142
168
  fi
143
169
  local sse; sse=$(sed -n 's/^data: //p' "$resp_file")
144
170
  if [ -n "$sse" ]; then
145
- printf '%s\n' "$sse"
171
+ local reply; reply=$(printf '%s\n' "$sse" | grep -E '"(result|error)"')
172
+ printf '%s\n' "${reply:-$sse}"
146
173
  else
147
174
  cat "$resp_file"
148
175
  fi
@@ -159,27 +186,37 @@ mcp_log() {
159
186
  tail -n "$n" "$log"
160
187
  }
161
188
 
162
- # Usage: mcp_stop <pid> [server-log-path]
163
- # Kills the background server. Removes the server log if a path is given.
189
+ # Usage: mcp_stop <pid> [server-log-path] [port]
190
+ # Kills the background server and the `bun run` child that actually holds the
191
+ # port (SIGKILL is not forwarded, so the child must be signalled directly or it
192
+ # survives as an orphaned listener). Pass the port from mcp_start to have the
193
+ # stop confirmed against the socket rather than against the wrapper PID.
194
+ # Removes the server log if a path is given.
164
195
  mcp_stop() {
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
196
+ local pid="$1"; local log="${2:-}"; local port="${3:-}"
197
+ [ -z "$pid" ] && { echo "usage: mcp_stop <pid> [log-path] [port]" >&2; return 1; }
198
+ local kids; kids=$(pgrep -P "$pid" 2>/dev/null)
199
+ kill "$pid" $kids 2>/dev/null
168
200
  for _ in $(seq 1 12); do
169
201
  kill -0 "$pid" 2>/dev/null || break
170
202
  sleep 0.25
171
203
  done
172
204
  if kill -0 "$pid" 2>/dev/null; then
173
205
  echo "PID $pid didn't exit on SIGTERM — sending SIGKILL"
174
- kill -9 "$pid" 2>/dev/null
206
+ kill -9 "$pid" $kids 2>/dev/null
175
207
  sleep 0.5
176
208
  fi
177
- if kill -0 "$pid" 2>/dev/null; then
209
+ local held=""
210
+ [ -n "$port" ] && held=$(lsof -ti tcp:"$port" 2>/dev/null | tr '\n' ' ')
211
+ if [ -n "$held" ]; then
212
+ echo "WARNING: port $port still held by PID(s) $held after stopping $pid — kill those before re-running"
213
+ elif kill -0 "$pid" 2>/dev/null; then
178
214
  echo "WARNING: PID $pid still alive after SIGKILL"
179
215
  else
180
- echo "stopped pid=$pid"
216
+ echo "stopped pid=$pid${port:+ (port $port free)}"
181
217
  fi
182
218
  [ -n "$log" ] && rm -f "$log"
219
+ return 0
183
220
  }
184
221
  HELPER_EOF
185
222
 
@@ -193,6 +230,8 @@ Capture `pid`, `url`, `port`, `log` from the `mcp_start` output — every later
193
230
 
194
231
  - `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 ...`).
195
232
  - If `bun run rebuild` fails, stop. Don't field-test broken code — fix the build first.
233
+ - Startup wait defaults to 30s. A server that builds or loads a local index at boot can need more — pass a second arg (`mcp_start /path 90`) rather than reading the timeout as a real startup failure.
234
+ - `pid` is the `bun run` wrapper; the process that actually holds the port is its child. `mcp_stop` signals both — that's why it takes the port.
196
235
  - If a server is already listening on the project's port (`lsof -i :<port>`), confirm with the user before killing it; it may be their own session. If the user isn't available to confirm, abort the field test and surface the port conflict in your response.
197
236
 
198
237
  ### 2. Initialize the session
@@ -202,7 +241,9 @@ Capture `pid`, `url`, `port`, `log` from the `mcp_start` output — every later
202
241
  mcp_init <url-from-mcp_start>
203
242
  ```
204
243
 
205
- Runs `initialize`, sends `notifications/initialized`, prints `sid=<id>` to capture for `mcp_call`.
244
+ Runs `initialize`, sends `notifications/initialized`, prints `sid=<id>` to capture for `mcp_call`, plus the protocol version the server negotiated.
245
+
246
+ The helper requests the SDK's current protocol version. **If `protocol=` comes back older than `requested=`, the server capped it** — every call after that exercises an older protocol than a current client would negotiate. Note it as a `bug` finding and check the pinned `@modelcontextprotocol/sdk` version; don't quietly test the downgraded surface. To deliberately test an older version, set `MCP_FIELD_TEST_PROTOCOL`.
206
247
 
207
248
  ### 3. Surface the catalog
208
249
 
@@ -247,6 +288,7 @@ Treat any hit as a `ux` finding in the report. The authoring rule lives under *T
247
288
  | Hits external API / live upstream | One call that exercises upstream; note rate-limit / timeout / transient-failure behavior |
248
289
  | Chained with other tools (search → detail → act) | Run one representative chain end-to-end; does each step return the IDs/cursors the next needs? |
249
290
  | `cursor` / `offset` / `limit` params | Pagination: second page, end-of-list |
291
+ | Output can be truncated, capped, or spilled (`maxLength`-style caps, outline-on-overflow, canvas/dataframe spill, "showing N of M") | Truncation retrievability: force a response that truncates, then confirm the response both *discloses* the truncation and hands back the means to reach the rest — a cursor, an offset, a document/section selector, a canvas handle. Truncated data with no retrieval path is a `bug`, not a `nit`. |
250
292
  | Tool declared an `errors: [...]` contract | Error contract (tool): trigger ≥1 declared failure mode. Verify `result.structuredContent.error.code` matches the contract entry, `result.structuredContent.error.data.reason` is the declared reason (only present when the handler threw an `McpError` — `ctx.fail` always does, plain `throw new Error(...)` does not), and `content[0].text` is actionable. Reasons declared but unreachable from any input are dead contract entries. |
251
293
  | Resource declared an `errors: [...]` contract | Error contract (resource): trigger ≥1 declared failure mode by reading a URI that exercises it. Resources re-throw errors at the JSON-RPC level — verify `error.code` matches the contract entry and `error.data.reason` is the declared reason. (Resources don't use the `result.isError` envelope — they fail the request itself.) |
252
294
  | Mutator (write/update/delete/append/patch verbs, or `destructiveHint: true`) | Mutator response observability: run an intentionally-ambiguous input (typo path, wrong ID, already-deleted target). Confirm the response carries enough state (pre/post values, state-change discriminator) for the agent to detect intent-effect divergence without re-fetching. |
@@ -282,11 +324,11 @@ When a call surprises you — slow, hangs, returns terse output, surfaces an unh
282
324
 
283
325
  ```bash
284
326
  . /tmp/<project-name>-field-test-<ID>.sh
285
- mcp_stop <pid> <log>
327
+ mcp_stop <pid> <log> <port>
286
328
  rm -f /tmp/<project-name>-field-test-<ID>.sh
287
329
  ```
288
330
 
289
- 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. If `mcp_stop` warns the PID is still alive after SIGKILL, note it in the report and proceed — don't block on a zombie process.
331
+ Kills the background server and its port-holding child, removes the server log, then removes the helper script itself. Do this *before* writing the report so nothing leaks into the next session. Pass the `port` — it's what turns "the wrapper PID is gone" into "the socket is actually free." If `mcp_stop` warns the port is still held or the PID survived SIGKILL, note it in the report and proceed — don't block on a zombie process, but do say which PID to kill.
290
332
 
291
333
  ### 7. Report
292
334
 
@@ -335,13 +377,14 @@ End with:
335
377
 
336
378
  - [ ] Stdio boot check completed — `bun run rebuild && bun run start:stdio` shows clean startup (banner, expected counts, no errors)
337
379
  - [ ] HTTP server built and started; real port parsed from log
338
- - [ ] Session initialized; `notifications/initialized` sent
380
+ - [ ] Session initialized; `notifications/initialized` sent; negotiated protocol version matches the requested one (a downgrade is a finding)
339
381
  - [ ] Catalog surfaced and presented; descriptions audited for leaks (implementation details, meta-coaching, consumer-aware phrasing)
340
382
  - [ ] Universal battery run on every definition (happy path, parity against the full `content[]` array, input error)
341
383
  - [ ] Situational categories applied only when triggered
342
384
  - [ ] **If >15 tools:** sampled 30–40% for situational testing; skipped definitions listed in report
343
385
  - [ ] **If a tool declared an `errors: [...]` contract:** ≥1 declared failure mode triggered; `result.structuredContent.error.code` and `data.reason` verified against the contract entry
344
386
  - [ ] **If a resource declared an `errors: [...]` contract:** ≥1 declared failure mode triggered; top-level JSON-RPC `error.code` and `error.data.reason` verified against the contract entry
387
+ - [ ] **If any tool truncates, caps, or spills its output:** truncation forced; disclosure + a retrieval path (cursor, offset, selector, canvas handle) verified
345
388
  - [ ] External-state / auth-gated tools handled explicitly (run, skip, or confirm)
346
- - [ ] Server stopped; server log and helper script removed
389
+ - [ ] Server stopped (port confirmed free); server log and helper script removed
347
390
  - [ ] Report: summary paragraph → grouped findings → numbered options
@@ -347,7 +347,7 @@ When you complete a skill's checklist, check the boxes and add a completion time
347
347
 
348
348
  ## Bundling
349
349
 
350
- `npm run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. The pack step is followed by `scripts/clean-mcpb.ts`, which prunes dev dependencies (`mcpb clean`) and strips dependency-shipped agent docs (`node_modules/**` `skills/`, `.claude/`, `.agents/`, `SKILL.md`) that root-anchored `.mcpbignore` patterns cannot reach. MCPB is stdio-only — HTTP and Cloudflare Workers deployments are unaffected. Consumers who don't need it can delete `manifest.json` and `.mcpbignore`; `lint:packaging` skips cleanly.
350
+ `npm run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. The pack step is followed by `scripts/clean-mcpb.ts`, which prunes dev dependencies (`mcpb clean`) and strips two classes of `node_modules/**` content that root-anchored `.mcpbignore` patterns cannot reach: dependency-shipped agent docs (`skills/`, `.claude/`, `.agents/`, `SKILL.md`) and platform-specific native bindings, which would otherwise lock the bundle to the platform it was packed on. A server using DataCanvas therefore ships a portable bundle without the DuckDB native — `@duckdb/node-api` is an optional peer loaded lazily, so canvas tools report an actionable install hint and every other tool works normally. MCPB is stdio-only — HTTP and Cloudflare Workers deployments are unaffected. Consumers who don't need it can delete `manifest.json` and `.mcpbignore`; `lint:packaging` skips cleanly.
351
351
 
352
352
  **Adding an env var requires both files:** `server.json` (registry discovery, `environmentVariables[]`) and `manifest.json` (bundle install UX, `mcp_config.env` + `user_config`). `lint:packaging` (run by `devcheck`) verifies the env var names match.
353
353
 
@@ -347,7 +347,7 @@ When you complete a skill's checklist, check the boxes and add a completion time
347
347
 
348
348
  ## Bundling
349
349
 
350
- `npm run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. The pack step is followed by `scripts/clean-mcpb.ts`, which prunes dev dependencies (`mcpb clean`) and strips dependency-shipped agent docs (`node_modules/**` `skills/`, `.claude/`, `.agents/`, `SKILL.md`) that root-anchored `.mcpbignore` patterns cannot reach. MCPB is stdio-only — HTTP and Cloudflare Workers deployments are unaffected. Consumers who don't need it can delete `manifest.json` and `.mcpbignore`; `lint:packaging` skips cleanly.
350
+ `npm run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. The pack step is followed by `scripts/clean-mcpb.ts`, which prunes dev dependencies (`mcpb clean`) and strips two classes of `node_modules/**` content that root-anchored `.mcpbignore` patterns cannot reach: dependency-shipped agent docs (`skills/`, `.claude/`, `.agents/`, `SKILL.md`) and platform-specific native bindings, which would otherwise lock the bundle to the platform it was packed on. A server using DataCanvas therefore ships a portable bundle without the DuckDB native — `@duckdb/node-api` is an optional peer loaded lazily, so canvas tools report an actionable install hint and every other tool works normally. MCPB is stdio-only — HTTP and Cloudflare Workers deployments are unaffected. Consumers who don't need it can delete `manifest.json` and `.mcpbignore`; `lint:packaging` skips cleanly.
351
351
 
352
352
  **Adding an env var requires both files:** `server.json` (registry discovery, `environmentVariables[]`) and `manifest.json` (bundle install UX, `mcp_config.env` + `user_config`). `lint:packaging` (run by `devcheck`) verifies the env var names match.
353
353
 
@@ -64,13 +64,13 @@
64
64
  "zod": "{{ZOD_VERSION}}"
65
65
  },
66
66
  "devDependencies": {
67
- "@biomejs/biome": "2.5.4",
67
+ "@biomejs/biome": "2.5.5",
68
68
  "@socketsecurity/bun-security-scanner": "^1.1.2",
69
69
  "@types/node": "26.1.1",
70
70
  "depcheck": "^1.4.7",
71
- "ignore": "^7.0.5",
72
- "tsc-alias": "^1.9.0",
73
- "typescript": "^6.0.3",
74
- "vitest": "^4.1.9"
71
+ "ignore": "^7.0.6",
72
+ "tsc-alias": "^1.9.1",
73
+ "typescript": "^7.0.2",
74
+ "vitest": "^4.1.10"
75
75
  }
76
76
  }