@cyanheads/mcp-ts-core 0.8.1 → 0.8.3
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 +8 -2
- package/README.md +1 -1
- package/changelog/0.8.x/0.8.2.md +18 -0
- package/changelog/0.8.x/0.8.3.md +43 -0
- package/dist/logs/combined.log +12 -4
- package/dist/logs/error.log +12 -4
- package/dist/mcp-server/resources/resource-registration.d.ts.map +1 -1
- package/dist/mcp-server/resources/resource-registration.js +2 -3
- package/dist/mcp-server/resources/resource-registration.js.map +1 -1
- package/dist/mcp-server/resources/utils/resourceDefinition.d.ts +3 -3
- package/dist/mcp-server/tools/tool-registration.d.ts.map +1 -1
- package/dist/mcp-server/tools/tool-registration.js +8 -12
- package/dist/mcp-server/tools/tool-registration.js.map +1 -1
- package/dist/mcp-server/tools/utils/toolDefinition.d.ts +2 -3
- package/dist/mcp-server/tools/utils/toolDefinition.d.ts.map +1 -1
- package/dist/mcp-server/tools/utils/toolDefinition.js.map +1 -1
- package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts +28 -0
- package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts.map +1 -1
- package/dist/mcp-server/tools/utils/toolHandlerFactory.js +64 -23
- package/dist/mcp-server/tools/utils/toolHandlerFactory.js.map +1 -1
- package/dist/mcp-server/transports/http/landing-page/assets/copy-script.d.ts +13 -4
- package/dist/mcp-server/transports/http/landing-page/assets/copy-script.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/landing-page/assets/copy-script.js +99 -25
- package/dist/mcp-server/transports/http/landing-page/assets/copy-script.js.map +1 -1
- package/dist/mcp-server/transports/http/landing-page/assets/styles.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/landing-page/assets/styles.js +318 -8
- package/dist/mcp-server/transports/http/landing-page/assets/styles.js.map +1 -1
- package/dist/mcp-server/transports/http/landing-page/sections/status-strip.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/landing-page/sections/status-strip.js +20 -1
- package/dist/mcp-server/transports/http/landing-page/sections/status-strip.js.map +1 -1
- package/dist/mcp-server/transports/http/landing-page/sections/tools.d.ts +6 -5
- package/dist/mcp-server/transports/http/landing-page/sections/tools.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/landing-page/sections/tools.js +114 -69
- package/dist/mcp-server/transports/http/landing-page/sections/tools.js.map +1 -1
- package/dist/types-global/errors.d.ts +3 -22
- package/dist/types-global/errors.d.ts.map +1 -1
- package/dist/types-global/errors.js +0 -19
- package/dist/types-global/errors.js.map +1 -1
- package/package.json +1 -1
- package/skills/add-app-tool/SKILL.md +24 -8
- package/skills/add-resource/SKILL.md +1 -1
- package/skills/add-tool/SKILL.md +87 -20
- package/skills/api-errors/SKILL.md +21 -7
- package/skills/api-linter/SKILL.md +1 -1
- package/skills/design-mcp-server/SKILL.md +1 -1
- package/skills/field-test/SKILL.md +15 -6
- package/skills/maintenance/SKILL.md +19 -7
- package/templates/AGENTS.md +3 -2
- package/templates/CLAUDE.md +3 -2
- package/templates/src/mcp-server/tools/definitions/echo.tool.ts +18 -1
|
@@ -61,11 +61,10 @@ export const fetchTool = tool('fetch_articles', {
|
|
|
61
61
|
|:--------|:---------|
|
|
62
62
|
| Compile time | `ctx.fail('typo')` is a TS error. Auto-completes declared reasons. |
|
|
63
63
|
| Runtime | `ctx.fail(reason, msg?, data?, options?)` builds an `McpError(contract.code, msg, { ...data, reason }, options)` — `data.reason` is auto-populated from the contract and cannot be overridden by caller-supplied data (spread first, then `reason` written last), so observers see a stable identifier. `options` accepts `{ cause }` for ES2022 error chaining. |
|
|
64
|
-
| `tools/list` | Contract surfaced under `_meta['mcp-ts-core/errors']` so clients/agents can preview failure modes. |
|
|
65
64
|
| Lint (startup) | Each `code` validated against `JsonRpcErrorCode`. Reasons validated as snake_case + unique within contract. |
|
|
66
65
|
| Lint (conformance) | If the handler `throw new McpError(JsonRpcErrorCode.X)` outside `ctx.fail`, conformance check warns when X isn't declared. |
|
|
67
66
|
|
|
68
|
-
**Skip the contract** for one-off internal tools or quick prototypes — `ctx` is plain `Context` (no `fail`) and you throw via [factories](#error-factories-fallback) directly. Behavior is identical at the wire; the contract just adds compile-time safety
|
|
67
|
+
**Skip the contract** for one-off internal tools or quick prototypes — `ctx` is plain `Context` (no `fail`) and you throw via [factories](#error-factories-fallback) directly. Behavior is identical at the wire; the contract just adds compile-time safety.
|
|
69
68
|
|
|
70
69
|
> **Limits of the conformance lint.** The conformance and prefer-fail rules scan the handler's source text for `throw` statements. Errors thrown from called services (e.g. `await myService.fetch()` raising `RateLimited` internally) are invisible — the lint only sees what's lexically in the handler. Treat the contract as the *advertised* failure surface; bubbled-up codes still reach the client correctly via the auto-classifier, just without lint enforcement.
|
|
71
70
|
|
|
@@ -87,7 +86,7 @@ errors: [
|
|
|
87
86
|
]
|
|
88
87
|
```
|
|
89
88
|
|
|
90
|
-
The handler doesn't catch and re-throw — letting service errors bubble unchanged keeps "logic throws, framework catches" intact. The
|
|
89
|
+
The handler doesn't catch and re-throw — letting service errors bubble unchanged keeps "logic throws, framework catches" intact. The wire payload still carries `code` + `data.reason`, and clients can switch on reason without parsing message text. What's lost is lint-time enforcement that every reason is reachable; compensate with one wire-shape test per reason.
|
|
91
90
|
|
|
92
91
|
---
|
|
93
92
|
|
|
@@ -266,9 +265,24 @@ Checked before common patterns. Cover: AWS exception names, HTTP status codes, D
|
|
|
266
265
|
| Layer | Pattern |
|
|
267
266
|
|:------|:--------|
|
|
268
267
|
| Tool/resource handlers | Throw `McpError` — no try/catch |
|
|
269
|
-
| Handler factory | Catches all errors, normalizes to `McpError`, sets `isError: true
|
|
268
|
+
| Handler factory (tools) | Catches all errors, normalizes to `McpError`, sets `isError: true`, mirrors error across both client surfaces (see [Error-path parity](#error-path-parity)) |
|
|
269
|
+
| Handler factory (resources) | Catches and re-throws to the SDK, which routes through the JSON-RPC error envelope |
|
|
270
270
|
| Services/setup code | `ErrorHandler.tryCatch` for graceful recovery |
|
|
271
271
|
|
|
272
|
+
### Error-path parity
|
|
273
|
+
|
|
274
|
+
MCP clients differ in which `CallToolResult` surface they forward to the agent. Tool errors mirror the success-path `format-parity` invariant — both surfaces carry the same payload:
|
|
275
|
+
|
|
276
|
+
| Surface | Content | Read by |
|
|
277
|
+
|:--------|:--------|:--------|
|
|
278
|
+
| `content[]` | Text rendering: `Error: <message>` (plus `Recovery: <hint>` when `data.recovery.hint` is present) | Claude Desktop and other format()-only clients |
|
|
279
|
+
| `structuredContent.error` | JSON `{ code, message, data? }` carrying the error code, message, and any structured data from the thrown `McpError` or `ZodError` | Claude Code and other structuredContent-only clients |
|
|
280
|
+
|
|
281
|
+
Important properties:
|
|
282
|
+
- **`_meta.error` is NOT emitted.** Error code/data live on `structuredContent.error` instead. Don't read `_meta.error` in clients or tests — it doesn't exist.
|
|
283
|
+
- **`data` propagation is restricted** to explicitly-thrown `McpError.data` and `ZodError.issues`. Auto-classified plain errors (`TypeError`, network errors, etc.) emit `code` + `message` only — no `data` — so internal classification context never leaks to clients.
|
|
284
|
+
- **Recovery hint mirroring is automatic.** When the thrown `McpError` carries `data.recovery.hint`, the handler factory appends it to the `content[]` text so the markdown surface matches the JSON surface. Authors don't need to format the hint manually.
|
|
285
|
+
|
|
272
286
|
**Handler — throw freely, no try/catch:**
|
|
273
287
|
|
|
274
288
|
```ts
|
|
@@ -349,7 +363,7 @@ if (!response.ok) {
|
|
|
349
363
|
|
|
350
364
|
Captures the response body (truncated, configurable limit) and `Retry-After` header (stored as `data.retryAfter`) into `error.data`. The codes it produces line up with `withRetry`'s transient-code set, so retryable responses are retried automatically.
|
|
351
365
|
|
|
352
|
-
> **Body reaches the client.** `error.data` is forwarded to the MCP client as `
|
|
366
|
+
> **Body reaches the client.** `error.data` is forwarded to the MCP client as `structuredContent.error.data` (tool errors) or JSON-RPC `error.data` (resource errors). Upstream 401/403/422 responses sometimes echo token claims, internal user IDs, or schema validation hints — that text becomes client-visible. For sensitive endpoints, pass `captureBody: false` (or `bodyLimit: 0`) so the body stays out of `data`. Defaults remain `captureBody: true` because most upstreams return useful diagnostic text and silent dropping helps no one debug.
|
|
353
367
|
|
|
354
368
|
Full status table:
|
|
355
369
|
|
|
@@ -408,7 +422,7 @@ The linter validates the structure of `errors[]` and (when present) cross-checks
|
|
|
408
422
|
|
|
409
423
|
| Rule | Severity | Catches |
|
|
410
424
|
|:-----|:---------|:--------|
|
|
411
|
-
| `error-contract-conformance` | warning | Handler throws a non-baseline code that isn't in the contract. Suggests adding it to `errors[]` so
|
|
425
|
+
| `error-contract-conformance` | warning | Handler throws a non-baseline code that isn't in the contract. Suggests adding it to `errors[]` so the contract is the canonical source of truth for declared failure modes. |
|
|
412
426
|
| `error-contract-prefer-fail` | warning | Handler throws a code that **is** in the contract directly (via factory or `new McpError`) instead of through `ctx.fail(reason, …)`. Encourages routing through the typed helper so observers see consistent `data.reason` values. |
|
|
413
427
|
|
|
414
428
|
### Baseline codes (auto-allowed)
|
|
@@ -421,7 +435,7 @@ These codes bubble up from anywhere — services, framework utilities, the auto-
|
|
|
421
435
|
- `ValidationError` — schema violations, malformed input
|
|
422
436
|
- `SerializationError` — JSON/XML parse failures
|
|
423
437
|
|
|
424
|
-
If you *want* to
|
|
438
|
+
If you *want* to declare one of these as a domain-specific failure (e.g., a tool that intentionally times out under defined conditions), put it in `errors[]` anyway — the contract still binds `ctx.fail(reason)` and the conformance lint will catch undeclared throws. The lint just doesn't *require* you to enumerate baselines.
|
|
425
439
|
|
|
426
440
|
### When to declare vs. let it bubble
|
|
427
441
|
|
|
@@ -502,7 +502,7 @@ throw serviceUnavailable('Upstream failed', { upstreamError: e }, { cause: e });
|
|
|
502
502
|
|
|
503
503
|
Validate the optional `errors[]` declarative contract on tool/resource definitions. Structural rules check the shape of contract entries; conformance rules cross-check the handler body against the declared codes.
|
|
504
504
|
|
|
505
|
-
When a contract is declared,
|
|
505
|
+
When a contract is declared, the handler receives a typed `ctx.fail(reason, …)` keyed by the declared reason union. See `skills/api-errors/SKILL.md` for runtime semantics.
|
|
506
506
|
|
|
507
507
|
### error-contract-type
|
|
508
508
|
|
|
@@ -323,7 +323,7 @@ The pattern: name the shortcut for what it does (`text_search`, `name_search`),
|
|
|
323
323
|
|
|
324
324
|
Errors are part of the tool's interface — design them during the design phase, not as an afterthought. Three aspects: **the contract** (which failures are public), **classification** (what error code), and **messaging** (what the LLM reads).
|
|
325
325
|
|
|
326
|
-
**Declare a typed contract for domain failures.** When a tool has known failure modes the agent should plan around (`no_match`, `queue_full`, `vendor_down`), enumerate them as `errors: [{ reason, code, when, retryable? }]` on the definition. The framework
|
|
326
|
+
**Declare a typed contract for domain failures.** When a tool has known failure modes the agent should plan around (`no_match`, `queue_full`, `vendor_down`), enumerate them as `errors: [{ reason, code, when, retryable? }]` on the definition. The framework types `ctx.fail(reason, …)` against the declared reason union (typos become TS errors) and auto-populates `data.reason` on the thrown error for stable observability. The error reaches clients with parity across both surfaces — `structuredContent.error` (Claude Code) and `content[]` text (Claude Desktop). Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`) bubble from anywhere and don't need to be enumerated. See `api-errors` skill for the full pattern.
|
|
327
327
|
|
|
328
328
|
**Classify errors by origin.** Different error sources need different codes and different recovery guidance. Map the failure modes for each tool during design:
|
|
329
329
|
|
|
@@ -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.2"
|
|
8
8
|
audience: external
|
|
9
9
|
type: debug
|
|
10
10
|
---
|
|
@@ -15,6 +15,15 @@ Unit tests (`add-test` skill) verify handler logic with mocked context. Field te
|
|
|
15
15
|
|
|
16
16
|
**Actively call the tools. Don't read code and guess.**
|
|
17
17
|
|
|
18
|
+
### Transport coverage
|
|
19
|
+
|
|
20
|
+
This skill drives an HTTP server because curl + JSON-RPC is the most reliable harness for shell-based agents. Most servers ship both transports (`bun run dev:http` and `dev:stdio`), so HTTP coverage is sufficient: the same handler runs on both, only the framing differs. If the server is **stdio-only** (no HTTP transport in `package.json` / no `MCP_TRANSPORT_TYPE=http` path), drive it through one of:
|
|
21
|
+
|
|
22
|
+
- **MCP Inspector** (`npx @modelcontextprotocol/inspector bun run dev:stdio`) — interactive UI for catalog browsing and tool calls; best for hands-on exploration
|
|
23
|
+
- **mcp-cli** (`uvx mcp-cli --stdio bun run dev:stdio`) — scriptable JSON-RPC client; best for batch/agentic testing
|
|
24
|
+
|
|
25
|
+
Adapt the test plan below the same way — universal battery on every definition, situational categories only when triggered, same error-contract verification — but call the tools through the inspector / mcp-cli rather than `mcp_call`. Pino startup + handler logs land on stderr in stdio mode (stdout is reserved for JSON-RPC), so tail with `2>/tmp/mcp-server.log` if you start the server yourself.
|
|
26
|
+
|
|
18
27
|
---
|
|
19
28
|
|
|
20
29
|
## Steps
|
|
@@ -190,8 +199,8 @@ Runs `initialize`, captures the session id, sends `notifications/initialized`.
|
|
|
190
199
|
|
|
191
200
|
```bash
|
|
192
201
|
. /tmp/mcp-field-test.sh
|
|
193
|
-
mcp_call tools/list | jq '.result.tools[] | {name, description, inputSchema, outputSchema
|
|
194
|
-
mcp_call resources/list | jq '.result.resources[] | {uri, name, mimeType
|
|
202
|
+
mcp_call tools/list | jq '.result.tools[] | {name, description, inputSchema, outputSchema}'
|
|
203
|
+
mcp_call resources/list | jq '.result.resources[] | {uri, name, mimeType}'
|
|
195
204
|
mcp_call prompts/list | jq '.result.prompts[] | {name, description, arguments}'
|
|
196
205
|
```
|
|
197
206
|
|
|
@@ -229,7 +238,7 @@ Treat any hit as a `ux` finding in the report. The authoring rule lives under *T
|
|
|
229
238
|
| Hits external API / live upstream | One call that exercises upstream; note rate-limit / timeout / transient-failure behavior |
|
|
230
239
|
| Chained with other tools (search → detail → act) | Run one representative chain end-to-end; does each step return the IDs/cursors the next needs? |
|
|
231
240
|
| `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.
|
|
241
|
+
| 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. |
|
|
233
242
|
| 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.) |
|
|
234
243
|
|
|
235
244
|
**Resources.** Happy path, not-found URI, `list` if defined, pagination if used.
|
|
@@ -253,7 +262,7 @@ When a call surprises you — slow, hangs, returns terse output, surfaces an unh
|
|
|
253
262
|
**Interpreting responses**
|
|
254
263
|
|
|
255
264
|
- 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.
|
|
265
|
+
- **Tool error code/reason** rides on `result.structuredContent.error.{code, message, 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. The text in `result.content[0].text` mirrors the message and includes `Recovery: <hint>` when `data.recovery.hint` is present.
|
|
257
266
|
- **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.
|
|
258
267
|
- JSON-RPC `error` only appears for protocol issues (bad session, malformed envelope, unknown method).
|
|
259
268
|
- `mcp_call` already strips SSE framing. Pipe to `jq` for readability.
|
|
@@ -317,7 +326,7 @@ End with:
|
|
|
317
326
|
- [ ] Catalog surfaced and presented; descriptions audited for leaks (implementation details, meta-coaching, consumer-aware phrasing)
|
|
318
327
|
- [ ] Universal battery run on every definition
|
|
319
328
|
- [ ] Situational categories applied only when triggered
|
|
320
|
-
- [ ] **If a tool declared an `errors: [...]` contract:** ≥1 declared failure mode triggered; `result.
|
|
329
|
+
- [ ] **If a tool declared an `errors: [...]` contract:** ≥1 declared failure mode triggered; `result.structuredContent.error.code` and `data.reason` verified against the contract entry
|
|
321
330
|
- [ ] **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
|
|
322
331
|
- [ ] External-state / auth-gated tools handled explicitly (run, skip, or confirm)
|
|
323
332
|
- [ ] Server stopped; state file removed
|
|
@@ -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: "
|
|
7
|
+
version: "2.0"
|
|
8
8
|
audience: external
|
|
9
9
|
type: workflow
|
|
10
10
|
---
|
|
@@ -148,16 +148,28 @@ If the consumer customized a framework script, the overwrite discards those chan
|
|
|
148
148
|
|
|
149
149
|
Apply the findings from Steps 3 and 4. Framework changes and third-party library changes have different adoption defaults — the asymmetry is deliberate.
|
|
150
150
|
|
|
151
|
-
**Framework changes (`@cyanheads/mcp-ts-core`) —
|
|
151
|
+
**Framework changes (`@cyanheads/mcp-ts-core`) — auto-adopt every applicable site, in this pass.**
|
|
152
152
|
|
|
153
|
-
The consumer opted into the framework; its templates, skills, scripts, linter rules, and
|
|
153
|
+
The consumer opted into the framework; its templates, skills, scripts, linter rules, conventions, and new APIs that supersede local code are authoritative. Adopt them now — not as a follow-up.
|
|
154
154
|
|
|
155
155
|
- **Breaking changes** — fix call sites. Not optional.
|
|
156
156
|
- **Deprecations** — migrate now, while context is fresh.
|
|
157
157
|
- **New linter rules** — if the rule now flags existing code, fix the code; don't silence the rule.
|
|
158
|
-
- **New utilities that supersede local code** — swap them in. The point of the framework is to centralize.
|
|
158
|
+
- **New utilities that supersede local code** — swap them in. The point of the framework is to centralize. This applies even when the local helper has richer messages or branch handling — port the domain detail onto the framework path; don't leave the local helper as-is. (E.g., `httpErrorFromResponse` replacing a project-local `throwForStatus`: keep the per-route message map, but route it through the framework utility.)
|
|
159
159
|
- **New conventions** (template changes, new config keys, renamed env vars) — adopt and update `.env.example`, server config schema, `server.json`, and README if user-facing.
|
|
160
|
-
- **New
|
|
160
|
+
- **New patterns that match existing surfaces** — refactor *every* matching site in this pass. Examples: typed error contracts (`errors[]` + `ctx.fail`) on tools that already throw domain-specific failures; factory adoption (`notFound()`, `validationError()`, …) replacing ad-hoc `new McpError(...)`; new logging/observability hooks supplanting bespoke logging. If the framework added a pattern that fits N tools/services, do all N — partial adoption fragments the surface and rots faster.
|
|
161
|
+
- **New framework features that don't match existing use cases** — skip. These are for future features, not retroactive refactors. "Don't match" means *the surface doesn't exist in this server* (e.g., a new Speech API in a non-speech server) — not "I'd have to touch a few files."
|
|
162
|
+
|
|
163
|
+
**Hard rule — invalid framework deferrals.**
|
|
164
|
+
|
|
165
|
+
| ❌ Not a valid reason to defer | ✅ Valid reason to defer |
|
|
166
|
+
|:-------------------------------|:-------------------------|
|
|
167
|
+
| "Larger change than fits this pass" | Code-commented or `CLAUDE.md`-documented local override that intentionally diverges from the framework convention |
|
|
168
|
+
| "Marginal benefit / leaving as-is" | Breaking change with multiple migration paths that need user input |
|
|
169
|
+
| "Per-tool refactor — worth doing as a focused follow-up" | Feature genuinely doesn't apply (the surface doesn't exist in this server) |
|
|
170
|
+
| "Existing helper has rich domain messages we'd lose" | — (port the messages onto the framework path) |
|
|
171
|
+
|
|
172
|
+
If you find yourself writing the left-column phrasing in Step 8's "Open decisions", stop and adopt it instead. Cost/benefit reasoning belongs to third-party changes only.
|
|
161
173
|
|
|
162
174
|
**Third-party library changes — default cost/benefit.**
|
|
163
175
|
|
|
@@ -189,7 +201,7 @@ Present a concise numbered summary to the user:
|
|
|
189
201
|
3. **Features adopted** — new framework APIs now in use
|
|
190
202
|
4. **Skills synced** — added/updated with versions (Phase A) and agent directories refreshed (Phase B)
|
|
191
203
|
5. **New/changed skills available** — skills that appeared in Phase A for the first time or had materially-changed step sequences. Frame as "consider running when the time is right" rather than immediate actions; the user decides when to invoke them.
|
|
192
|
-
6. **Open decisions** — genuinely ambiguous items: breaking changes with multiple migration paths, framework changes that conflict with a documented local override, third-party adoptions where
|
|
204
|
+
6. **Open decisions** — genuinely ambiguous items only. Valid: breaking changes with multiple migration paths needing user input, framework changes that conflict with a code-commented or `CLAUDE.md`-documented local override, third-party adoptions where cost/benefit is close. **Not valid:** framework adoptions deferred for scope, effort, or marginal-benefit reasoning — those were already adopted in Step 6 and belong under "Features adopted." If this section is empty, that's the expected outcome of a clean framework upgrade.
|
|
193
205
|
7. **Status** — rebuild / devcheck / test results
|
|
194
206
|
|
|
195
207
|
## Checklist
|
|
@@ -197,7 +209,7 @@ Present a concise numbered summary to the user:
|
|
|
197
209
|
- [ ] Update applied (`bun update --latest`) — Mode A, or already done by user — Mode B
|
|
198
210
|
- [ ] `changelog` skill invoked for each updated package
|
|
199
211
|
- [ ] Framework CHANGELOG reviewed if `@cyanheads/mcp-ts-core` was updated
|
|
200
|
-
- [ ]
|
|
212
|
+
- [ ] Every applicable framework adoption opportunity applied in this pass — no scope/effort/marginal-benefit deferrals; third-party adoptions evaluated on cost/benefit
|
|
201
213
|
- [ ] Project `skills/` synced from package (Phase A), with a change report
|
|
202
214
|
- [ ] Agent skill directories (`.claude/skills/`, `.agents/skills/`, etc.) refreshed from project `skills/` (Phase B)
|
|
203
215
|
- [ ] Framework `scripts/` resynced from package via content-hash compare (Phase C), with a change report; `scripts/` diff reviewed before committing
|
package/templates/AGENTS.md
CHANGED
|
@@ -90,6 +90,7 @@ export const searchItems = tool('search_items', {
|
|
|
90
90
|
|
|
91
91
|
```ts
|
|
92
92
|
import { resource, z } from '@cyanheads/mcp-ts-core';
|
|
93
|
+
import { notFound } from '@cyanheads/mcp-ts-core/errors';
|
|
93
94
|
|
|
94
95
|
export const itemData = resource('inventory://{itemId}', {
|
|
95
96
|
description: 'Fetch an inventory item by ID.',
|
|
@@ -97,7 +98,7 @@ export const itemData = resource('inventory://{itemId}', {
|
|
|
97
98
|
auth: ['inventory:read'],
|
|
98
99
|
async handler(params, ctx) {
|
|
99
100
|
const item = await ctx.state.get(`item:${params.itemId}`);
|
|
100
|
-
if (!item) throw
|
|
101
|
+
if (!item) throw notFound(`Item ${params.itemId} not found`, { itemId: params.itemId });
|
|
101
102
|
return item;
|
|
102
103
|
},
|
|
103
104
|
});
|
|
@@ -167,7 +168,7 @@ Handlers receive a unified `ctx` object. Key properties:
|
|
|
167
168
|
|
|
168
169
|
Handlers throw — the framework catches, classifies, and formats.
|
|
169
170
|
|
|
170
|
-
**Recommended: typed error contract.** Declare `errors: [{ reason, code, when, retryable? }]` on `tool()` / `resource()` to
|
|
171
|
+
**Recommended: typed error contract.** Declare `errors: [{ reason, code, when, retryable? }]` on `tool()` / `resource()` to 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.
|
|
171
172
|
|
|
172
173
|
```ts
|
|
173
174
|
errors: [
|
package/templates/CLAUDE.md
CHANGED
|
@@ -90,6 +90,7 @@ export const searchItems = tool('search_items', {
|
|
|
90
90
|
|
|
91
91
|
```ts
|
|
92
92
|
import { resource, z } from '@cyanheads/mcp-ts-core';
|
|
93
|
+
import { notFound } from '@cyanheads/mcp-ts-core/errors';
|
|
93
94
|
|
|
94
95
|
export const itemData = resource('inventory://{itemId}', {
|
|
95
96
|
description: 'Fetch an inventory item by ID.',
|
|
@@ -97,7 +98,7 @@ export const itemData = resource('inventory://{itemId}', {
|
|
|
97
98
|
auth: ['inventory:read'],
|
|
98
99
|
async handler(params, ctx) {
|
|
99
100
|
const item = await ctx.state.get(`item:${params.itemId}`);
|
|
100
|
-
if (!item) throw
|
|
101
|
+
if (!item) throw notFound(`Item ${params.itemId} not found`, { itemId: params.itemId });
|
|
101
102
|
return item;
|
|
102
103
|
},
|
|
103
104
|
});
|
|
@@ -167,7 +168,7 @@ Handlers receive a unified `ctx` object. Key properties:
|
|
|
167
168
|
|
|
168
169
|
Handlers throw — the framework catches, classifies, and formats.
|
|
169
170
|
|
|
170
|
-
**Recommended: typed error contract.** Declare `errors: [{ reason, code, when, retryable? }]` on `tool()` / `resource()` to
|
|
171
|
+
**Recommended: typed error contract.** Declare `errors: [{ reason, code, when, retryable? }]` on `tool()` / `resource()` to 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.
|
|
171
172
|
|
|
172
173
|
```ts
|
|
173
174
|
errors: [
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { tool, z } from '@cyanheads/mcp-ts-core';
|
|
7
|
+
import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
7
8
|
|
|
8
9
|
// Tool names are snake_case, prefixed with your server name to avoid collisions across servers.
|
|
9
10
|
// e.g. for a "tasks" server: tasks_fetch_list, tasks_create_item.
|
|
@@ -17,7 +18,23 @@ export const echoTool = tool('template_echo_message', {
|
|
|
17
18
|
message: z.string().describe('The echoed message.'),
|
|
18
19
|
}),
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
// Declare each domain failure mode the agent should plan around. The framework
|
|
22
|
+
// types `ctx.fail(reason, …)` against the declared union. Baseline codes
|
|
23
|
+
// (InternalError, ServiceUnavailable, Timeout, ValidationError,
|
|
24
|
+
// SerializationError) bubble freely — only declare domain-specific reasons.
|
|
25
|
+
// Delete this block if no domain-specific failures apply to your tool.
|
|
26
|
+
errors: [
|
|
27
|
+
{
|
|
28
|
+
reason: 'empty_message',
|
|
29
|
+
code: JsonRpcErrorCode.InvalidParams,
|
|
30
|
+
when: 'Message contained only whitespace.',
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
|
|
34
|
+
handler(input, ctx) {
|
|
35
|
+
if (input.message.trim().length === 0) {
|
|
36
|
+
throw ctx.fail('empty_message', 'Message must contain at least one non-whitespace character.');
|
|
37
|
+
}
|
|
21
38
|
return { message: input.message };
|
|
22
39
|
},
|
|
23
40
|
|