@cyanheads/mcp-ts-core 0.7.6 → 0.8.1
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/CLAUDE.md +22 -7
- package/README.md +2 -2
- package/changelog/0.8.x/0.8.0.md +33 -0
- package/changelog/0.8.x/0.8.1.md +17 -0
- package/changelog/template.md +13 -0
- package/dist/core/context.d.ts +67 -0
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/context.js +46 -1
- package/dist/core/context.js.map +1 -1
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -0
- package/dist/core/index.js.map +1 -1
- package/dist/linter/rules/error-contract-rules.d.ts +45 -0
- package/dist/linter/rules/error-contract-rules.d.ts.map +1 -0
- package/dist/linter/rules/error-contract-rules.js +321 -0
- package/dist/linter/rules/error-contract-rules.js.map +1 -0
- package/dist/linter/rules/handler-body-rules.d.ts +18 -0
- package/dist/linter/rules/handler-body-rules.d.ts.map +1 -0
- package/dist/linter/rules/handler-body-rules.js +134 -0
- package/dist/linter/rules/handler-body-rules.js.map +1 -0
- package/dist/linter/rules/index.d.ts +2 -0
- package/dist/linter/rules/index.d.ts.map +1 -1
- package/dist/linter/rules/index.js +2 -0
- package/dist/linter/rules/index.js.map +1 -1
- package/dist/linter/rules/resource-rules.d.ts.map +1 -1
- package/dist/linter/rules/resource-rules.js +9 -0
- package/dist/linter/rules/resource-rules.js.map +1 -1
- package/dist/linter/rules/source-text.d.ts +19 -0
- package/dist/linter/rules/source-text.d.ts.map +1 -0
- package/dist/linter/rules/source-text.js +96 -0
- package/dist/linter/rules/source-text.js.map +1 -0
- package/dist/linter/rules/tool-rules.d.ts.map +1 -1
- package/dist/linter/rules/tool-rules.js +9 -0
- package/dist/linter/rules/tool-rules.js.map +1 -1
- package/dist/logs/combined.log +4 -4
- package/dist/logs/error.log +4 -4
- package/dist/mcp-server/apps/appBuilders.d.ts +9 -4
- package/dist/mcp-server/apps/appBuilders.d.ts.map +1 -1
- package/dist/mcp-server/apps/appBuilders.js +4 -0
- package/dist/mcp-server/apps/appBuilders.js.map +1 -1
- package/dist/mcp-server/resources/resource-registration.d.ts.map +1 -1
- package/dist/mcp-server/resources/resource-registration.js +3 -2
- package/dist/mcp-server/resources/resource-registration.js.map +1 -1
- package/dist/mcp-server/resources/utils/resourceDefinition.d.ts +13 -5
- package/dist/mcp-server/resources/utils/resourceDefinition.d.ts.map +1 -1
- package/dist/mcp-server/resources/utils/resourceDefinition.js.map +1 -1
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.d.ts.map +1 -1
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.js +5 -4
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.js.map +1 -1
- package/dist/mcp-server/tools/tool-registration.d.ts.map +1 -1
- package/dist/mcp-server/tools/tool-registration.js +13 -7
- package/dist/mcp-server/tools/tool-registration.js.map +1 -1
- package/dist/mcp-server/tools/utils/toolDefinition.d.ts +64 -16
- package/dist/mcp-server/tools/utils/toolDefinition.d.ts.map +1 -1
- package/dist/mcp-server/tools/utils/toolDefinition.js +25 -11
- package/dist/mcp-server/tools/utils/toolDefinition.js.map +1 -1
- package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts.map +1 -1
- package/dist/mcp-server/tools/utils/toolHandlerFactory.js +6 -4
- package/dist/mcp-server/tools/utils/toolHandlerFactory.js.map +1 -1
- package/dist/testing/index.d.ts +8 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +5 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/types-global/errors.d.ts +82 -0
- package/dist/types-global/errors.d.ts.map +1 -1
- package/dist/types-global/errors.js +25 -0
- package/dist/types-global/errors.js.map +1 -1
- package/dist/utils/formatting/index.d.ts +1 -0
- package/dist/utils/formatting/index.d.ts.map +1 -1
- package/dist/utils/formatting/index.js +1 -0
- package/dist/utils/formatting/index.js.map +1 -1
- package/dist/utils/formatting/partialResult.d.ts +145 -0
- package/dist/utils/formatting/partialResult.d.ts.map +1 -0
- package/dist/utils/formatting/partialResult.js +145 -0
- package/dist/utils/formatting/partialResult.js.map +1 -0
- package/dist/utils/index.d.ts +2 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/network/httpError.d.ts +112 -0
- package/dist/utils/network/httpError.d.ts.map +1 -0
- package/dist/utils/network/httpError.js +153 -0
- package/dist/utils/network/httpError.js.map +1 -0
- package/dist/utils/network/retry.d.ts.map +1 -1
- package/dist/utils/network/retry.js +0 -1
- package/dist/utils/network/retry.js.map +1 -1
- package/package.json +5 -4
- package/scripts/split-changelog.ts +133 -0
- package/skills/add-app-tool/SKILL.md +12 -0
- package/skills/add-resource/SKILL.md +40 -0
- package/skills/add-service/SKILL.md +54 -1
- package/skills/add-test/SKILL.md +39 -0
- package/skills/add-tool/SKILL.md +42 -5
- package/skills/api-context/SKILL.md +75 -1
- package/skills/api-errors/SKILL.md +183 -5
- package/skills/api-linter/SKILL.md +223 -3
- package/skills/api-testing/SKILL.md +79 -4
- package/skills/api-utils/SKILL.md +4 -2
- package/skills/design-mcp-server/SKILL.md +13 -10
- package/skills/field-test/SKILL.md +81 -15
- package/skills/maintenance/SKILL.md +5 -2
- package/skills/report-issue-framework/SKILL.md +2 -2
- package/skills/security-pass/SKILL.md +6 -5
- package/templates/AGENTS.md +23 -8
- package/templates/CLAUDE.md +23 -8
- package/templates/changelog/template.md +18 -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.1"
|
|
8
8
|
audience: external
|
|
9
9
|
type: debug
|
|
10
10
|
---
|
|
@@ -27,14 +27,20 @@ Write the helper to `/tmp/mcp-field-test.sh` once, then source it in every subse
|
|
|
27
27
|
cat > /tmp/mcp-field-test.sh <<'HELPER_EOF'
|
|
28
28
|
#!/bin/bash
|
|
29
29
|
# Field-test helper: manage an MCP HTTP server + JSON-RPC session across shell calls.
|
|
30
|
+
# Surfaces failures aggressively — field test is for finding things that fail,
|
|
31
|
+
# so the helper auto-tails logs and prints HTTP status/body on errors instead
|
|
32
|
+
# of swallowing them.
|
|
30
33
|
STATE_FILE="/tmp/mcp-field-test.env"
|
|
31
34
|
[ -f "$STATE_FILE" ] && . "$STATE_FILE"
|
|
32
35
|
|
|
33
36
|
mcp_start() {
|
|
34
37
|
local dir="${1:-$PWD}"
|
|
35
38
|
echo "building $dir ..."
|
|
36
|
-
(cd "$dir" && bun run rebuild) >/tmp/mcp-build.log 2>&1
|
|
37
|
-
|
|
39
|
+
if ! (cd "$dir" && bun run rebuild) >/tmp/mcp-build.log 2>&1; then
|
|
40
|
+
echo "BUILD FAILED — last 30 lines of /tmp/mcp-build.log:"
|
|
41
|
+
tail -30 /tmp/mcp-build.log
|
|
42
|
+
return 1
|
|
43
|
+
fi
|
|
38
44
|
echo "starting server ..."
|
|
39
45
|
(cd "$dir" && bun run start:http) >/tmp/mcp-server.log 2>&1 &
|
|
40
46
|
local pid=$!
|
|
@@ -45,7 +51,8 @@ mcp_start() {
|
|
|
45
51
|
sleep 0.25
|
|
46
52
|
done
|
|
47
53
|
if [ -z "$line" ]; then
|
|
48
|
-
echo "server failed to start —
|
|
54
|
+
echo "server failed to start within 10s — last 30 lines of /tmp/mcp-server.log:"
|
|
55
|
+
tail -30 /tmp/mcp-server.log
|
|
49
56
|
kill "$pid" 2>/dev/null
|
|
50
57
|
return 1
|
|
51
58
|
fi
|
|
@@ -63,12 +70,21 @@ EOF
|
|
|
63
70
|
mcp_init() {
|
|
64
71
|
[ -z "$MCP_URL" ] && { echo "run mcp_start first"; return 1; }
|
|
65
72
|
local hdr="/tmp/mcp-init-headers.txt"
|
|
66
|
-
|
|
73
|
+
local body_file="/tmp/mcp-init-body.txt"
|
|
74
|
+
local status
|
|
75
|
+
status=$(curl -sS -D "$hdr" -o "$body_file" -w '%{http_code}' -X POST "$MCP_URL" \
|
|
67
76
|
-H "Content-Type: application/json" \
|
|
68
77
|
-H "Accept: application/json, text/event-stream" \
|
|
69
|
-
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"field-test","version":"2.
|
|
78
|
+
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"field-test","version":"2.1"}}}')
|
|
70
79
|
local sid; sid=$(grep -i '^mcp-session-id:' "$hdr" | awk '{print $2}' | tr -d '\r\n')
|
|
71
|
-
[ -z "$sid" ]
|
|
80
|
+
if [ -z "$sid" ]; then
|
|
81
|
+
echo "init failed — HTTP $status, no Mcp-Session-Id header returned"
|
|
82
|
+
echo "--- response body ---"
|
|
83
|
+
cat "$body_file"
|
|
84
|
+
echo "--- response headers ---"
|
|
85
|
+
cat "$hdr"
|
|
86
|
+
return 1
|
|
87
|
+
fi
|
|
72
88
|
cat > "$STATE_FILE" <<EOF
|
|
73
89
|
export MCP_PID=$MCP_PID
|
|
74
90
|
export MCP_URL=$MCP_URL
|
|
@@ -81,11 +97,13 @@ EOF
|
|
|
81
97
|
-H "Accept: application/json, text/event-stream" \
|
|
82
98
|
-H "Mcp-Session-Id: $sid" \
|
|
83
99
|
-d '{"jsonrpc":"2.0","method":"notifications/initialized"}' >/dev/null
|
|
84
|
-
echo "session=$sid"
|
|
100
|
+
echo "session=$sid (HTTP $status)"
|
|
85
101
|
}
|
|
86
102
|
|
|
87
103
|
# Usage: mcp_call METHOD [JSON_PARAMS]
|
|
88
|
-
# Prints the JSON-RPC response
|
|
104
|
+
# Prints the JSON-RPC response. SSE framing is stripped when present; on
|
|
105
|
+
# non-SSE responses the raw body is printed instead so plain-JSON error
|
|
106
|
+
# replies (HTTP 4xx/5xx) still surface. Pipe to `jq`.
|
|
89
107
|
mcp_call() {
|
|
90
108
|
[ -z "$MCP_SID" ] && { echo "run mcp_init first"; return 1; }
|
|
91
109
|
local method="$1"; local params="${2:-}"
|
|
@@ -95,17 +113,57 @@ mcp_call() {
|
|
|
95
113
|
else
|
|
96
114
|
body=$(printf '{"jsonrpc":"2.0","id":%d,"method":"%s","params":%s}' "$RANDOM" "$method" "$params")
|
|
97
115
|
fi
|
|
98
|
-
|
|
116
|
+
local resp_file="/tmp/mcp-call-body.txt"
|
|
117
|
+
local status
|
|
118
|
+
status=$(curl -sS -o "$resp_file" -w '%{http_code}' -X POST "$MCP_URL" \
|
|
99
119
|
-H "Content-Type: application/json" \
|
|
100
120
|
-H "Accept: application/json, text/event-stream" \
|
|
101
121
|
-H "Mcp-Session-Id: $MCP_SID" \
|
|
102
|
-
-d "$body"
|
|
122
|
+
-d "$body")
|
|
123
|
+
if [ "$status" -ge 400 ]; then
|
|
124
|
+
echo "HTTP $status from $method — response:" >&2
|
|
125
|
+
cat "$resp_file" >&2
|
|
126
|
+
return 1
|
|
127
|
+
fi
|
|
128
|
+
local sse; sse=$(sed -n 's/^data: //p' "$resp_file")
|
|
129
|
+
if [ -n "$sse" ]; then
|
|
130
|
+
printf '%s\n' "$sse"
|
|
131
|
+
else
|
|
132
|
+
cat "$resp_file"
|
|
133
|
+
fi
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
# Tail the server log. Useful when a call surprises you — pino startup banner,
|
|
137
|
+
# definition lint diagnostics, request handler errors, upstream calls, and
|
|
138
|
+
# rate-limit warnings live in /tmp/mcp-server.log.
|
|
139
|
+
# Usage: mcp_log [N] (default: 50 lines)
|
|
140
|
+
mcp_log() {
|
|
141
|
+
local n="${1:-50}"
|
|
142
|
+
tail -n "$n" /tmp/mcp-server.log
|
|
103
143
|
}
|
|
104
144
|
|
|
105
145
|
mcp_stop() {
|
|
106
|
-
[ -
|
|
146
|
+
if [ -z "$MCP_PID" ]; then
|
|
147
|
+
rm -f "$STATE_FILE"
|
|
148
|
+
echo "no PID to stop"
|
|
149
|
+
return 0
|
|
150
|
+
fi
|
|
151
|
+
kill "$MCP_PID" 2>/dev/null
|
|
152
|
+
for _ in $(seq 1 12); do
|
|
153
|
+
kill -0 "$MCP_PID" 2>/dev/null || break
|
|
154
|
+
sleep 0.25
|
|
155
|
+
done
|
|
156
|
+
if kill -0 "$MCP_PID" 2>/dev/null; then
|
|
157
|
+
echo "PID $MCP_PID didn't exit on SIGTERM — sending SIGKILL"
|
|
158
|
+
kill -9 "$MCP_PID" 2>/dev/null
|
|
159
|
+
sleep 0.5
|
|
160
|
+
fi
|
|
161
|
+
if kill -0 "$MCP_PID" 2>/dev/null; then
|
|
162
|
+
echo "WARNING: PID $MCP_PID still alive after SIGKILL"
|
|
163
|
+
else
|
|
164
|
+
echo "stopped pid=$MCP_PID"
|
|
165
|
+
fi
|
|
107
166
|
rm -f "$STATE_FILE"
|
|
108
|
-
echo "stopped"
|
|
109
167
|
}
|
|
110
168
|
HELPER_EOF
|
|
111
169
|
|
|
@@ -132,8 +190,8 @@ Runs `initialize`, captures the session id, sends `notifications/initialized`.
|
|
|
132
190
|
|
|
133
191
|
```bash
|
|
134
192
|
. /tmp/mcp-field-test.sh
|
|
135
|
-
mcp_call tools/list | jq '.result.tools[] | {name, description, inputSchema, outputSchema}'
|
|
136
|
-
mcp_call resources/list | jq '.result.resources[] | {uri, name, mimeType}'
|
|
193
|
+
mcp_call tools/list | jq '.result.tools[] | {name, description, inputSchema, outputSchema, errors: ._meta["mcp-ts-core/errors"]}'
|
|
194
|
+
mcp_call resources/list | jq '.result.resources[] | {uri, name, mimeType, errors: ._meta["mcp-ts-core/errors"]}'
|
|
137
195
|
mcp_call prompts/list | jq '.result.prompts[] | {name, description, arguments}'
|
|
138
196
|
```
|
|
139
197
|
|
|
@@ -171,6 +229,8 @@ Treat any hit as a `ux` finding in the report. The authoring rule lives under *T
|
|
|
171
229
|
| Hits external API / live upstream | One call that exercises upstream; note rate-limit / timeout / transient-failure behavior |
|
|
172
230
|
| Chained with other tools (search → detail → act) | Run one representative chain end-to-end; does each step return the IDs/cursors the next needs? |
|
|
173
231
|
| `cursor` / `offset` / `limit` params | Pagination: second page, end-of-list |
|
|
232
|
+
| Tool declared an `errors: [...]` contract | Error contract (tool): trigger ≥1 declared failure mode. Verify `result._meta.error.code` matches the contract entry, `result._meta.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. |
|
|
233
|
+
| 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.) |
|
|
174
234
|
|
|
175
235
|
**Resources.** Happy path, not-found URI, `list` if defined, pagination if used.
|
|
176
236
|
**Prompts.** Happy path, defaults omitted, skim message quality.
|
|
@@ -188,9 +248,13 @@ Use `TaskCreate` — one task per definition. Mark complete as you go. Don't bat
|
|
|
188
248
|
|
|
189
249
|
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).
|
|
190
250
|
|
|
251
|
+
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 `/tmp/mcp-server.log` rather than coming back through `mcp_call`. Don't guess at runtime behavior from response text alone.
|
|
252
|
+
|
|
191
253
|
**Interpreting responses**
|
|
192
254
|
|
|
193
255
|
- Tool domain errors return `{result: {content: [...], isError: true}}` — they live in `result`, not `error`. Check `isError`, not the JSON-RPC error field.
|
|
256
|
+
- **Tool error code/reason** rides on `result._meta.error.{code, data.reason}` — inspect that, not just the text. `data` is only spread when the handler threw an `McpError` (or `ZodError`); plain `throw new Error(...)` won't populate `data.reason`. Use `ctx.fail`-thrown errors when the contract reason matters.
|
|
257
|
+
- **Resource errors** are JSON-RPC-level — they appear in the top-level `error.{code, data.reason}` field, not inside `result`. Resource handlers re-throw rather than producing an `isError` envelope.
|
|
194
258
|
- JSON-RPC `error` only appears for protocol issues (bad session, malformed envelope, unknown method).
|
|
195
259
|
- `mcp_call` already strips SSE framing. Pipe to `jq` for readability.
|
|
196
260
|
|
|
@@ -253,6 +317,8 @@ End with:
|
|
|
253
317
|
- [ ] Catalog surfaced and presented; descriptions audited for leaks (implementation details, meta-coaching, consumer-aware phrasing)
|
|
254
318
|
- [ ] Universal battery run on every definition
|
|
255
319
|
- [ ] Situational categories applied only when triggered
|
|
320
|
+
- [ ] **If a tool declared an `errors: [...]` contract:** ≥1 declared failure mode triggered; `result._meta.error.code` and `data.reason` verified against the contract entry
|
|
321
|
+
- [ ] **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
|
|
256
322
|
- [ ] External-state / auth-gated tools handled explicitly (run, skip, or confirm)
|
|
257
323
|
- [ ] Server stopped; state file removed
|
|
258
324
|
- [ ] Report: summary paragraph → grouped findings → numbered options
|
|
@@ -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: "1.
|
|
7
|
+
version: "1.9"
|
|
8
8
|
audience: external
|
|
9
9
|
type: workflow
|
|
10
10
|
---
|
|
@@ -50,6 +50,8 @@ Do not redo this investigation inline — the `changelog` skill handles tag-form
|
|
|
50
50
|
|
|
51
51
|
### 4. Framework review (`@cyanheads/mcp-ts-core`)
|
|
52
52
|
|
|
53
|
+
**Skill-version paradox.** If `node_modules/@cyanheads/mcp-ts-core/skills/maintenance/SKILL.md`'s `version` exceeds the one running, run Step 5 Phase A first and re-invoke `maintenance` — otherwise feature-adoption rows added in the new version silently don't surface.
|
|
54
|
+
|
|
53
55
|
If `@cyanheads/mcp-ts-core` was updated, do a deeper pass beyond what the `changelog` skill covers. The framework ships a **directory-based changelog** grouped by minor series (`.x` semver-wildcard convention) — one file per released version at `node_modules/@cyanheads/mcp-ts-core/changelog/<major.minor>.x/<version>.md`. Read only the files between old and new rather than scanning a monolithic file.
|
|
54
56
|
|
|
55
57
|
Example — `0.5.2 → 0.5.4` means reading two new version files:
|
|
@@ -65,7 +67,8 @@ Scan specifically for:
|
|
|
65
67
|
|
|
66
68
|
| Area | Adoption Check |
|
|
67
69
|
|:-----|:---------------|
|
|
68
|
-
| New
|
|
70
|
+
| New `/errors` surface — factories, typed contracts (`errors[]` + `ctx.fail`), `httpErrorFromResponse` | Replace ad-hoc `new McpError(...)` with factories; declare `errors: [...]` on tools that surface domain-specific failure modes; route declared throws through `ctx.fail(reason, …)` so the conformance lint is happy |
|
|
71
|
+
| Existing factory choice — semantic audit | Beyond factory-vs-`new McpError`: audit each `throw factory(...)` against intent. `invalidParams` (-32602) is for malformed JSON-RPC params (wrong-shape post-Zod is rare); semantic post-shape validation should use `validationError` (-32007). `notFound` for missing entities, `conflict` for state collisions, `unauthorized` vs `forbidden` for unauth vs scope-denied. Wrong codes degrade `mcp_error_classified_code` observability and break client retry logic — fix during this pass even if not adopting contracts yet. |
|
|
69
72
|
| New utilities in `/utils` | Identify any that supersede local helper code |
|
|
70
73
|
| New context capabilities | Added `ctx.*` methods worth adopting |
|
|
71
74
|
| Provider/service APIs | Updates to `OpenRouterProvider`, `SpeechService`, `GraphService`, etc. |
|
|
@@ -4,7 +4,7 @@ description: >
|
|
|
4
4
|
File a bug or feature request against @cyanheads/mcp-ts-core when you hit a framework issue. Use when a builder, utility, context method, or config behaves contrary to the documented API — not for server-specific application bugs.
|
|
5
5
|
metadata:
|
|
6
6
|
author: cyanheads
|
|
7
|
-
version: "1.
|
|
7
|
+
version: "1.4"
|
|
8
8
|
audience: external
|
|
9
9
|
type: workflow
|
|
10
10
|
---
|
|
@@ -141,7 +141,7 @@ Format: `bug(<scope>): concise description`
|
|
|
141
141
|
| `prompt` | Prompt builder, generate, args |
|
|
142
142
|
| `context` | Context, logger, state, progress, elicit, sample |
|
|
143
143
|
| `config` | AppConfig, parseConfig, env parsing |
|
|
144
|
-
| `errors` | McpError, error factories, auto-classification |
|
|
144
|
+
| `errors` | McpError, error factories, typed contracts (`errors[]` / `ctx.fail`), conformance lint, `httpErrorFromResponse`, auto-classification |
|
|
145
145
|
| `auth` | Auth modes, scope checking, JWT/OAuth |
|
|
146
146
|
| `storage` | StorageService, providers |
|
|
147
147
|
| `transport` | stdio/http transport, SSE, session handling |
|
|
@@ -4,7 +4,7 @@ description: >
|
|
|
4
4
|
Review an MCP server for common security gaps: LLM-facing surfaces as injection vector (tools, resources, prompts, descriptions), scope blast radius, destructive ops without consent, upstream auth shape, input sinks (URL / path / roots / shell / sampling / schema strictness / ReDoS), tenant isolation, leakage through errors and telemetry, unbounded resources, and HTTP-mode deployment surface. Use before a release, after a batch of handler changes, or when the user asks for a security review, audit, or hardening pass. Produces grouped findings and a numbered options list.
|
|
5
5
|
metadata:
|
|
6
6
|
author: cyanheads
|
|
7
|
-
version: "1.
|
|
7
|
+
version: "1.2"
|
|
8
8
|
audience: external
|
|
9
9
|
type: audit
|
|
10
10
|
---
|
|
@@ -203,10 +203,10 @@ grep -rn "^let " src/services/
|
|
|
203
203
|
|
|
204
204
|
What accidentally reaches the LLM, user, or observability sinks.
|
|
205
205
|
|
|
206
|
-
**Look in:** `throw new McpError(...)` sites, `McpError.data` fields, output schemas, and every logging / telemetry surface — not just `ctx.log`.
|
|
206
|
+
**Look in:** `throw new McpError(...)` and `ctx.fail(reason, msg, data)` sites, error factory calls (`notFound`, `httpErrorFromResponse`, …), `McpError.data` fields (the `data` arg flows through both paths), output schemas, and every logging / telemetry surface — not just `ctx.log`.
|
|
207
207
|
|
|
208
208
|
```bash
|
|
209
|
-
grep -
|
|
209
|
+
grep -rnE "new McpError|ctx\.fail\(|httpErrorFromResponse\(" src/
|
|
210
210
|
grep -rnE "\b(ctx\.log|console\.(log|info|warn|error|debug)|logger\.)" src/
|
|
211
211
|
grep -rnE "(Sentry\.|captureException|setTag|setContext|addBreadcrumb)" src/
|
|
212
212
|
grep -rnE "(setAttribute|setAttributes|span\.)" src/ # OpenTelemetry
|
|
@@ -214,7 +214,8 @@ grep -rnE "(setAttribute|setAttributes|span\.)" src/ # OpenTelemetry
|
|
|
214
214
|
|
|
215
215
|
**Check:**
|
|
216
216
|
|
|
217
|
-
- Error `data` fields carry upstream response bodies, auth headers, stack traces?
|
|
217
|
+
- Error `data` fields (whether passed via `ctx.fail(reason, msg, data)`, `new McpError(code, msg, data)`, or factory calls) carry upstream response bodies, auth headers, stack traces?
|
|
218
|
+
- `httpErrorFromResponse` body capture sweeping in too much (default 500-byte cap is fine for most APIs but consider `captureBody: false` when the upstream returns auth-bearing payloads)?
|
|
218
219
|
- Output schemas include token prefixes, internal IDs, session identifiers?
|
|
219
220
|
- `format()` renders fields that shouldn't leave the server?
|
|
220
221
|
- `ctx.log.info(msg, body)` where `body` is the raw request (may contain secrets)?
|
|
@@ -222,7 +223,7 @@ grep -rnE "(setAttribute|setAttributes|span\.)" src/ # OpenTelemetry
|
|
|
222
223
|
- OpenTelemetry span attributes / Sentry breadcrumbs carry tokens, PII, or full request bodies?
|
|
223
224
|
- Secret / token / HMAC comparisons use `===` or `==` instead of constant-time (`timingSafeEqual` / `crypto.timingSafeEqual`) — leaks length and prefix via timing?
|
|
224
225
|
|
|
225
|
-
**Smell:** `throw new McpError(code, upstream.message, { raw: upstream.body })`. Or: `if (apiKey === expected)` on a request-auth path.
|
|
226
|
+
**Smell:** `throw new McpError(code, upstream.message, { raw: upstream.body })` or `throw ctx.fail('upstream_failed', e.message, { raw: e.response.body })`. Or: `if (apiKey === expected)` on a request-auth path.
|
|
226
227
|
|
|
227
228
|
#### Axis 8 — Resource bounds
|
|
228
229
|
|
package/templates/AGENTS.md
CHANGED
|
@@ -165,24 +165,39 @@ Handlers receive a unified `ctx` object. Key properties:
|
|
|
165
165
|
|
|
166
166
|
## Errors
|
|
167
167
|
|
|
168
|
-
Handlers throw — the framework catches, classifies, and formats.
|
|
168
|
+
Handlers throw — the framework catches, classifies, and formats.
|
|
169
|
+
|
|
170
|
+
**Recommended: typed error contract.** Declare `errors: [{ reason, code, when, retryable? }]` on `tool()` / `resource()` to advertise the failure surface in `tools/list` (under `_meta['mcp-ts-core/errors']`) and receive a typed `ctx.fail(reason, …)` keyed by the declared reason union. TypeScript catches `ctx.fail('typo')` at compile time, `data.reason` is auto-populated for observability, and the linter enforces conformance against the handler body. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`) bubble freely and don't need declaring.
|
|
169
171
|
|
|
170
172
|
```ts
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
173
|
+
errors: [
|
|
174
|
+
{ reason: 'no_match', code: JsonRpcErrorCode.NotFound, when: 'No item matched the query' },
|
|
175
|
+
],
|
|
176
|
+
async handler(input, ctx) {
|
|
177
|
+
const item = await db.find(input.id);
|
|
178
|
+
if (!item) throw ctx.fail('no_match', `No item ${input.id}`);
|
|
179
|
+
return item;
|
|
180
|
+
}
|
|
181
|
+
```
|
|
174
182
|
|
|
175
|
-
|
|
176
|
-
|
|
183
|
+
**Fallback (no contract entry fits):** throw via factories or plain `Error`.
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
// Error factories — explicit code
|
|
187
|
+
import { notFound, serviceUnavailable } from '@cyanheads/mcp-ts-core/errors';
|
|
177
188
|
throw notFound('Item not found', { itemId });
|
|
178
189
|
throw serviceUnavailable('API unavailable', { url }, { cause: err });
|
|
179
190
|
|
|
180
|
-
//
|
|
191
|
+
// Plain Error — framework auto-classifies from message patterns
|
|
192
|
+
throw new Error('Item not found'); // → NotFound
|
|
193
|
+
throw new Error('Invalid query format'); // → ValidationError
|
|
194
|
+
|
|
195
|
+
// McpError — when no factory exists for the code
|
|
181
196
|
import { McpError, JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
182
197
|
throw new McpError(JsonRpcErrorCode.DatabaseError, 'Connection failed', { pool: 'primary' });
|
|
183
198
|
```
|
|
184
199
|
|
|
185
|
-
|
|
200
|
+
See framework CLAUDE.md and the `api-errors` skill for the full auto-classification table, all available factories, and the contract reference.
|
|
186
201
|
|
|
187
202
|
---
|
|
188
203
|
|
package/templates/CLAUDE.md
CHANGED
|
@@ -165,24 +165,39 @@ Handlers receive a unified `ctx` object. Key properties:
|
|
|
165
165
|
|
|
166
166
|
## Errors
|
|
167
167
|
|
|
168
|
-
Handlers throw — the framework catches, classifies, and formats.
|
|
168
|
+
Handlers throw — the framework catches, classifies, and formats.
|
|
169
|
+
|
|
170
|
+
**Recommended: typed error contract.** Declare `errors: [{ reason, code, when, retryable? }]` on `tool()` / `resource()` to advertise the failure surface in `tools/list` (under `_meta['mcp-ts-core/errors']`) and receive a typed `ctx.fail(reason, …)` keyed by the declared reason union. TypeScript catches `ctx.fail('typo')` at compile time, `data.reason` is auto-populated for observability, and the linter enforces conformance against the handler body. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`) bubble freely and don't need declaring.
|
|
169
171
|
|
|
170
172
|
```ts
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
173
|
+
errors: [
|
|
174
|
+
{ reason: 'no_match', code: JsonRpcErrorCode.NotFound, when: 'No item matched the query' },
|
|
175
|
+
],
|
|
176
|
+
async handler(input, ctx) {
|
|
177
|
+
const item = await db.find(input.id);
|
|
178
|
+
if (!item) throw ctx.fail('no_match', `No item ${input.id}`);
|
|
179
|
+
return item;
|
|
180
|
+
}
|
|
181
|
+
```
|
|
174
182
|
|
|
175
|
-
|
|
176
|
-
|
|
183
|
+
**Fallback (no contract entry fits):** throw via factories or plain `Error`.
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
// Error factories — explicit code
|
|
187
|
+
import { notFound, serviceUnavailable } from '@cyanheads/mcp-ts-core/errors';
|
|
177
188
|
throw notFound('Item not found', { itemId });
|
|
178
189
|
throw serviceUnavailable('API unavailable', { url }, { cause: err });
|
|
179
190
|
|
|
180
|
-
//
|
|
191
|
+
// Plain Error — framework auto-classifies from message patterns
|
|
192
|
+
throw new Error('Item not found'); // → NotFound
|
|
193
|
+
throw new Error('Invalid query format'); // → ValidationError
|
|
194
|
+
|
|
195
|
+
// McpError — when no factory exists for the code
|
|
181
196
|
import { McpError, JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
182
197
|
throw new McpError(JsonRpcErrorCode.DatabaseError, 'Connection failed', { pool: 'primary' });
|
|
183
198
|
```
|
|
184
199
|
|
|
185
|
-
|
|
200
|
+
See framework CLAUDE.md and the `api-errors` skill for the full auto-classification table, all available factories, and the contract reference.
|
|
186
201
|
|
|
187
202
|
---
|
|
188
203
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# FORMAT REFERENCE — this file is never edited, never moved, never renamed.
|
|
3
3
|
#
|
|
4
4
|
# At release time, author a new per-version file at:
|
|
5
|
-
# changelog/<major.minor>.x/<version>.md (e.g. changelog/0.
|
|
5
|
+
# changelog/<major.minor>.x/<version>.md (e.g. changelog/0.6.x/0.6.6.md)
|
|
6
6
|
# using this file's frontmatter and section layout as the starting point.
|
|
7
7
|
# Set that new file's H1 to `# <version> — YYYY-MM-DD` with a concrete date.
|
|
8
8
|
|
|
@@ -27,16 +27,29 @@ breaking: false
|
|
|
27
27
|
|
|
28
28
|
Optional narrative intro — 1-3 sentences framing the release theme. Delete if not needed.
|
|
29
29
|
|
|
30
|
+
TONE: terse and fact-dense. 1-2 sentence(s) per bullet where possible —
|
|
31
|
+
name the symbol, state what changed, stop. Drop "explains how it works" prose;
|
|
32
|
+
that belongs in JSDoc, AGENTS.md, or the relevant skill. Drop ceremonial
|
|
33
|
+
framings ("This release introduces…", "fully backwards compatible:" with a
|
|
34
|
+
paragraph of justification). Prefer code/symbol names over English
|
|
35
|
+
re-explanations. If a bullet runs more than ~2 lines, split it or cut it.
|
|
36
|
+
|
|
37
|
+
WHAT TO INCLUDE: every distinct fact a reader needs to adopt or audit the
|
|
38
|
+
release — new exports, signatures, lint rule IDs, env vars, breaking
|
|
39
|
+
changes, version bumps on shipped skills. WHAT TO CUT: mechanism walkthroughs,
|
|
40
|
+
duplicate prose between Added and Changed, file-by-file test enumerations,
|
|
41
|
+
internal implementation notes. Trust the reader to read the code or the docs.
|
|
42
|
+
|
|
30
43
|
Linking issues/PRs: use full URLs so the link works everywhere (GitHub web UI,
|
|
31
44
|
npm/node_modules reads, local editors). GitHub's bare `#NN` auto-link only
|
|
32
45
|
resolves inside its own UI.
|
|
33
46
|
|
|
34
|
-
[#
|
|
35
|
-
[#
|
|
47
|
+
[#38](https://github.com/cyanheads/mcp-ts-core/issues/38) ← issue
|
|
48
|
+
[#42](https://github.com/cyanheads/mcp-ts-core/pull/42) ← PR
|
|
36
49
|
|
|
37
50
|
Only link numbers you've verified exist (via `gh issue view NN` or
|
|
38
|
-
`gh pr view NN`). Never speculate on a future number — `#
|
|
39
|
-
upcoming PR" will quietly resolve to whatever real item already owns
|
|
51
|
+
`gh pr view NN`). Never speculate on a future number — `#42` for "my
|
|
52
|
+
upcoming PR" will quietly resolve to whatever real item already owns 42,
|
|
40
53
|
and GitHub timeline previews will pull in that unrelated item's title.
|
|
41
54
|
-->
|
|
42
55
|
|