@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.
- package/AGENTS.md +1 -1
- package/CLAUDE.md +1 -1
- package/README.md +2 -2
- package/biome.json +1 -1
- package/changelog/0.10.x/0.10.17.md +41 -0
- package/changelog/0.11.x/0.11.0.md +69 -0
- package/dist/cli/init.js.map +1 -1
- package/dist/config/index.d.ts +111 -108
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +14 -0
- package/dist/config/index.js.map +1 -1
- package/dist/config/parseEnvConfig.js.map +1 -1
- package/dist/core/app.js.map +1 -1
- package/dist/core/gcPressure.d.ts.map +1 -1
- package/dist/core/serverManifest.d.ts +1 -1
- package/dist/core/serverManifest.d.ts.map +1 -1
- package/dist/core/worker.d.ts.map +1 -1
- package/dist/core/worker.js.map +1 -1
- package/dist/linter/rules/format-parity-rules.js.map +1 -1
- package/dist/linter/rules/portability-rules.js.map +1 -1
- package/dist/logs/combined.log +8 -8
- package/dist/logs/error.log +4 -4
- package/dist/mcp-server/prompts/prompt-registration.d.ts.map +1 -1
- package/dist/mcp-server/prompts/prompt-registration.js.map +1 -1
- package/dist/mcp-server/resources/resource-registration.d.ts.map +1 -1
- package/dist/mcp-server/resources/resource-registration.js.map +1 -1
- package/dist/mcp-server/tasks/core/evictingTaskMessageQueue.d.ts +53 -0
- package/dist/mcp-server/tasks/core/evictingTaskMessageQueue.d.ts.map +1 -0
- package/dist/mcp-server/tasks/core/evictingTaskMessageQueue.js +65 -0
- package/dist/mcp-server/tasks/core/evictingTaskMessageQueue.js.map +1 -0
- package/dist/mcp-server/tasks/core/sessionAwareTaskStore.d.ts +30 -2
- package/dist/mcp-server/tasks/core/sessionAwareTaskStore.d.ts.map +1 -1
- package/dist/mcp-server/tasks/core/sessionAwareTaskStore.js +65 -4
- package/dist/mcp-server/tasks/core/sessionAwareTaskStore.js.map +1 -1
- package/dist/mcp-server/tasks/core/storageBackedTaskStore.d.ts.map +1 -1
- package/dist/mcp-server/tasks/core/storageBackedTaskStore.js.map +1 -1
- package/dist/mcp-server/tasks/core/taskManager.d.ts.map +1 -1
- package/dist/mcp-server/tasks/core/taskManager.js +6 -2
- package/dist/mcp-server/tasks/core/taskManager.js.map +1 -1
- package/dist/mcp-server/tasks/core/taskTypes.d.ts +7 -0
- package/dist/mcp-server/tasks/core/taskTypes.d.ts.map +1 -1
- package/dist/mcp-server/tasks/core/taskTypes.js +7 -0
- package/dist/mcp-server/tasks/core/taskTypes.js.map +1 -1
- package/dist/mcp-server/tools/tool-registration.d.ts.map +1 -1
- package/dist/mcp-server/tools/tool-registration.js +1 -2
- package/dist/mcp-server/tools/tool-registration.js.map +1 -1
- package/dist/mcp-server/tools/utils/toolHandlerFactory.js.map +1 -1
- package/dist/mcp-server/transports/auth/strategies/jwtStrategy.d.ts.map +1 -1
- package/dist/mcp-server/transports/auth/strategies/jwtStrategy.js.map +1 -1
- package/dist/mcp-server/transports/auth/strategies/oauthStrategy.d.ts.map +1 -1
- package/dist/mcp-server/transports/auth/strategies/oauthStrategy.js.map +1 -1
- package/dist/mcp-server/transports/heartbeat.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/httpErrorHandler.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/landing-page/handler.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/robotsTxt.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/serverCard.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/sessionStore.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/sessionStore.js.map +1 -1
- package/dist/mcp-server/transports/manager.d.ts.map +1 -1
- package/dist/mcp-server/transports/manager.js.map +1 -1
- package/dist/services/canvas/core/CanvasInstance.d.ts.map +1 -1
- package/dist/services/canvas/core/CanvasInstance.js.map +1 -1
- package/dist/services/canvas/core/CanvasRegistry.d.ts.map +1 -1
- package/dist/services/canvas/core/CanvasRegistry.js.map +1 -1
- package/dist/services/canvas/core/DataCanvas.d.ts.map +1 -1
- package/dist/services/canvas/core/DataCanvas.js.map +1 -1
- package/dist/services/canvas/core/canvasFactory.d.ts.map +1 -1
- package/dist/services/canvas/core/canvasFactory.js +1 -0
- package/dist/services/canvas/core/canvasFactory.js.map +1 -1
- package/dist/services/canvas/core/sqlGate.d.ts +10 -10
- package/dist/services/canvas/core/sqlGate.d.ts.map +1 -1
- package/dist/services/canvas/providers/duckdb/DuckdbProvider.d.ts +16 -0
- package/dist/services/canvas/providers/duckdb/DuckdbProvider.d.ts.map +1 -1
- package/dist/services/canvas/providers/duckdb/DuckdbProvider.js +25 -7
- package/dist/services/canvas/providers/duckdb/DuckdbProvider.js.map +1 -1
- package/dist/services/canvas/providers/duckdb/exportWriter.d.ts +4 -2
- package/dist/services/canvas/providers/duckdb/exportWriter.d.ts.map +1 -1
- package/dist/services/canvas/providers/duckdb/exportWriter.js +4 -2
- package/dist/services/canvas/providers/duckdb/exportWriter.js.map +1 -1
- package/dist/services/graph/core/GraphService.d.ts.map +1 -1
- package/dist/services/graph/core/GraphService.js.map +1 -1
- package/dist/services/llm/providers/openrouter.provider.d.ts.map +1 -1
- package/dist/services/llm/providers/openrouter.provider.js.map +1 -1
- package/dist/services/mirror/sqlite/handle.js.map +1 -1
- package/dist/services/speech/core/SpeechService.d.ts.map +1 -1
- package/dist/services/speech/core/SpeechService.js.map +1 -1
- package/dist/services/speech/providers/elevenlabs.provider.d.ts.map +1 -1
- package/dist/services/speech/providers/whisper.provider.d.ts.map +1 -1
- package/dist/storage/core/StorageService.d.ts.map +1 -1
- package/dist/storage/core/StorageService.js.map +1 -1
- package/dist/storage/core/storageFactory.js.map +1 -1
- package/dist/storage/providers/cloudflare/d1Provider.d.ts.map +1 -1
- package/dist/storage/providers/cloudflare/d1Provider.js.map +1 -1
- package/dist/storage/providers/cloudflare/kvProvider.d.ts.map +1 -1
- package/dist/storage/providers/cloudflare/r2Provider.d.ts.map +1 -1
- package/dist/storage/providers/fileSystem/fileSystemProvider.d.ts.map +1 -1
- package/dist/storage/providers/inMemory/inMemoryProvider.d.ts.map +1 -1
- package/dist/storage/providers/supabase/supabaseProvider.d.ts.map +1 -1
- package/dist/storage/providers/supabase/supabaseProvider.js.map +1 -1
- package/dist/testing/fuzz.js.map +1 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/vitest.d.ts +1 -1
- package/dist/types-global/errors.d.ts.map +1 -1
- package/dist/utils/formatting/diffFormatter.d.ts.map +1 -1
- package/dist/utils/formatting/html.d.ts.map +1 -1
- package/dist/utils/formatting/html.js.map +1 -1
- package/dist/utils/formatting/markdownBuilder.d.ts.map +1 -1
- package/dist/utils/formatting/tableFormatter.d.ts.map +1 -1
- package/dist/utils/formatting/tableFormatter.js.map +1 -1
- package/dist/utils/formatting/treeFormatter.d.ts.map +1 -1
- package/dist/utils/internal/error-handler/errorHandler.d.ts.map +1 -1
- package/dist/utils/internal/logger.d.ts +19 -3
- package/dist/utils/internal/logger.d.ts.map +1 -1
- package/dist/utils/internal/logger.js +51 -21
- package/dist/utils/internal/logger.js.map +1 -1
- package/dist/utils/internal/requestContext.js.map +1 -1
- package/dist/utils/internal/telemetryMessages.d.ts +4 -4
- package/dist/utils/internal/telemetryMessages.d.ts.map +1 -1
- package/dist/utils/network/fetchWithTimeout.d.ts.map +1 -1
- package/dist/utils/network/fetchWithTimeout.js +33 -16
- package/dist/utils/network/fetchWithTimeout.js.map +1 -1
- package/dist/utils/network/httpError.js.map +1 -1
- package/dist/utils/network/retry.js.map +1 -1
- package/dist/utils/pagination/pagination.d.ts.map +1 -1
- package/dist/utils/parsing/csvParser.d.ts.map +1 -1
- package/dist/utils/parsing/dateParser.d.ts.map +1 -1
- package/dist/utils/parsing/frontmatterParser.d.ts.map +1 -1
- package/dist/utils/parsing/htmlExtractor.d.ts.map +1 -1
- package/dist/utils/parsing/jsonParser.d.ts.map +1 -1
- package/dist/utils/parsing/jsonParser.js.map +1 -1
- package/dist/utils/parsing/pdfParser.d.ts.map +1 -1
- package/dist/utils/parsing/pdfParser.js.map +1 -1
- package/dist/utils/parsing/xmlParser.d.ts.map +1 -1
- package/dist/utils/parsing/yamlParser.d.ts.map +1 -1
- package/dist/utils/scheduling/scheduler.d.ts.map +1 -1
- package/dist/utils/security/idGenerator.d.ts.map +1 -1
- package/dist/utils/security/idGenerator.js.map +1 -1
- package/dist/utils/security/rateLimiter.d.ts.map +1 -1
- package/dist/utils/security/rateLimiter.js.map +1 -1
- package/dist/utils/security/sanitization.d.ts.map +1 -1
- package/dist/utils/security/sanitization.js.map +1 -1
- package/package.json +43 -34
- package/scripts/check-dependency-specifiers.ts +13 -7
- package/scripts/clean-mcpb.ts +48 -15
- package/scripts/lint-packaging.ts +36 -13
- package/skills/api-canvas/SKILL.md +4 -3
- package/skills/api-config/SKILL.md +2 -1
- package/skills/field-test/SKILL.md +71 -28
- package/templates/AGENTS.md +1 -1
- package/templates/CLAUDE.md +1 -1
- 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
|
+
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`,
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
119
|
-
#
|
|
120
|
-
#
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
package/templates/AGENTS.md
CHANGED
|
@@ -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 (`
|
|
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
|
|
package/templates/CLAUDE.md
CHANGED
|
@@ -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 (`
|
|
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
|
|
package/templates/package.json
CHANGED
|
@@ -64,13 +64,13 @@
|
|
|
64
64
|
"zod": "{{ZOD_VERSION}}"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
|
-
"@biomejs/biome": "2.5.
|
|
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.
|
|
72
|
-
"tsc-alias": "^1.9.
|
|
73
|
-
"typescript": "^
|
|
74
|
-
"vitest": "^4.1.
|
|
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
|
}
|