@cyanheads/mcp-ts-core 0.13.4 → 0.13.6
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 +5 -5
- package/CLAUDE.md +5 -5
- package/README.md +1 -1
- package/biome.json +1 -1
- package/changelog/0.13.x/0.13.5.md +41 -0
- package/changelog/0.13.x/0.13.6.md +49 -0
- package/dist/linter/rules/enrichment-rules.d.ts +5 -4
- package/dist/linter/rules/enrichment-rules.d.ts.map +1 -1
- package/dist/linter/rules/enrichment-rules.js +99 -22
- package/dist/linter/rules/enrichment-rules.js.map +1 -1
- package/dist/linter/rules/error-contract-rules.d.ts +101 -3
- package/dist/linter/rules/error-contract-rules.d.ts.map +1 -1
- package/dist/linter/rules/error-contract-rules.js +291 -3
- package/dist/linter/rules/error-contract-rules.js.map +1 -1
- package/dist/linter/rules/format-parity-rules.js +1 -1
- package/dist/linter/rules/format-parity-rules.js.map +1 -1
- package/dist/linter/rules/index.d.ts +1 -1
- package/dist/linter/rules/index.d.ts.map +1 -1
- package/dist/linter/rules/index.js +1 -1
- 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 +5 -2
- package/dist/linter/rules/resource-rules.js.map +1 -1
- package/dist/linter/rules/tool-rules.d.ts.map +1 -1
- package/dist/linter/rules/tool-rules.js +5 -2
- package/dist/linter/rules/tool-rules.js.map +1 -1
- package/dist/mcp-server/handlerContext.d.ts +5 -2
- package/dist/mcp-server/handlerContext.d.ts.map +1 -1
- package/dist/mcp-server/handlerContext.js +6 -4
- package/dist/mcp-server/handlerContext.js.map +1 -1
- package/dist/mcp-server/inputRequired.d.ts +35 -2
- package/dist/mcp-server/inputRequired.d.ts.map +1 -1
- package/dist/mcp-server/inputRequired.js +116 -2
- package/dist/mcp-server/inputRequired.js.map +1 -1
- package/dist/mcp-server/resources/resource-registration.d.ts +2 -1
- package/dist/mcp-server/resources/resource-registration.d.ts.map +1 -1
- package/dist/mcp-server/resources/resource-registration.js +4 -4
- package/dist/mcp-server/resources/resource-registration.js.map +1 -1
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.d.ts +2 -1
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.d.ts.map +1 -1
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.js +5 -2
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.js.map +1 -1
- package/dist/mcp-server/server.d.ts.map +1 -1
- package/dist/mcp-server/server.js +11 -2
- package/dist/mcp-server/server.js.map +1 -1
- package/dist/mcp-server/tools/tool-registration.d.ts +2 -1
- package/dist/mcp-server/tools/tool-registration.d.ts.map +1 -1
- package/dist/mcp-server/tools/tool-registration.js +4 -4
- package/dist/mcp-server/tools/tool-registration.js.map +1 -1
- package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts +14 -4
- package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts.map +1 -1
- package/dist/mcp-server/tools/utils/toolHandlerFactory.js +83 -9
- package/dist/mcp-server/tools/utils/toolHandlerFactory.js.map +1 -1
- package/dist/services/canvas/core/CanvasInstance.d.ts +4 -1
- package/dist/services/canvas/core/CanvasInstance.d.ts.map +1 -1
- package/dist/services/canvas/core/CanvasInstance.js +5 -0
- package/dist/services/canvas/core/CanvasInstance.js.map +1 -1
- package/dist/services/canvas/core/CanvasRegistry.d.ts +52 -1
- package/dist/services/canvas/core/CanvasRegistry.d.ts.map +1 -1
- package/dist/services/canvas/core/CanvasRegistry.js +79 -3
- package/dist/services/canvas/core/CanvasRegistry.js.map +1 -1
- package/dist/services/canvas/index.d.ts +1 -1
- package/dist/services/canvas/index.d.ts.map +1 -1
- package/dist/services/canvas/index.js +1 -1
- package/dist/services/canvas/index.js.map +1 -1
- package/dist/types-global/errors.d.ts +48 -0
- package/dist/types-global/errors.d.ts.map +1 -1
- package/dist/utils/internal/error-handler/errorHandler.d.ts.map +1 -1
- package/dist/utils/internal/error-handler/errorHandler.js +13 -3
- package/dist/utils/internal/error-handler/errorHandler.js.map +1 -1
- package/dist/utils/internal/error-handler/mappings.d.ts +14 -1
- package/dist/utils/internal/error-handler/mappings.d.ts.map +1 -1
- package/dist/utils/internal/error-handler/mappings.js +19 -1
- package/dist/utils/internal/error-handler/mappings.js.map +1 -1
- package/dist/utils/internal/error-handler/types.d.ts +12 -1
- package/dist/utils/internal/error-handler/types.d.ts.map +1 -1
- package/dist/utils/internal/performance.d.ts.map +1 -1
- package/dist/utils/internal/performance.js +4 -1
- package/dist/utils/internal/performance.js.map +1 -1
- package/dist/utils/network/fetchWithTimeout.d.ts +24 -4
- package/dist/utils/network/fetchWithTimeout.d.ts.map +1 -1
- package/dist/utils/network/fetchWithTimeout.js +10 -6
- package/dist/utils/network/fetchWithTimeout.js.map +1 -1
- package/dist/utils/network/httpError.d.ts +43 -4
- package/dist/utils/network/httpError.d.ts.map +1 -1
- package/dist/utils/network/httpError.js +53 -6
- package/dist/utils/network/httpError.js.map +1 -1
- package/dist/utils/telemetry/attributes.d.ts +8 -0
- package/dist/utils/telemetry/attributes.d.ts.map +1 -1
- package/dist/utils/telemetry/attributes.js +8 -0
- package/dist/utils/telemetry/attributes.js.map +1 -1
- package/framework-skills/add-service/SKILL.md +5 -2
- package/framework-skills/add-tool/SKILL.md +10 -5
- package/framework-skills/api-canvas/SKILL.md +31 -14
- package/framework-skills/api-context/SKILL.md +4 -2
- package/framework-skills/api-errors/SKILL.md +51 -7
- package/framework-skills/api-linter/SKILL.md +128 -13
- package/framework-skills/api-telemetry/SKILL.md +18 -3
- package/framework-skills/api-utils/SKILL.md +4 -4
- package/framework-skills/design-mcp-server/SKILL.md +4 -2
- package/framework-skills/field-test/SKILL.md +2 -2
- package/package.json +3 -3
- package/scripts/lint-mcp.ts +43 -4
- package/templates/AGENTS.md +1 -1
- package/templates/CLAUDE.md +1 -1
- package/templates/devcheck.config.json +1 -0
- package/templates/package.json +1 -1
- package/templates/src/mcp-server/tools/definitions/echo.tool.ts +7 -1
package/AGENTS.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Developer Protocol
|
|
2
2
|
|
|
3
3
|
**Package:** `@cyanheads/mcp-ts-core`
|
|
4
|
-
**Version:** 0.13.
|
|
4
|
+
**Version:** 0.13.6
|
|
5
5
|
**Engines:** Bun ≥1.4.0, Node ≥24.0.0
|
|
6
6
|
**MCP SDK:** `@modelcontextprotocol/server` ^2.0.0 (protocol revisions 2026-07-28 and 2025-*)
|
|
7
7
|
**Zod:** ^4.6.5
|
|
@@ -247,7 +247,7 @@ export const myTool = tool('my_tool', {
|
|
|
247
247
|
**`format`**: Maps output to MCP `content[]`. Different clients forward different surfaces to the agent — some (Claude Code) read `structuredContent` from `output`, others (Claude Desktop) read `content[]` from `format()`. `format()` is the markdown twin of `structuredContent`, not a reduced summary.
|
|
248
248
|
|
|
249
249
|
- **Parity is lint-enforced.** Every terminal field in `output` must appear in `format()`'s rendered text (via sentinel injection), or the `format-parity` rule fails `bun run lint:mcp` / `devcheck`.
|
|
250
|
-
- **Primary fix:** render the missing field in `format()`.
|
|
250
|
+
- **Primary fix:** render the missing field in `format()`. For list/detail variants, declare one flat `z.object` with a `kind` discriminator and presence-based optional arms, rendered by independent `if` blocks — `tool()` rejects a `z.discriminatedUnion` output root.
|
|
251
251
|
- **Escape hatch:** if the schema was over-typed for a genuinely dynamic upstream API, relax it (`z.object({}).passthrough()`) — passthrough still flows data to `structuredContent`.
|
|
252
252
|
- **Fallback:** omit `format` for JSON stringify. Additional formatters in `/utils`: `markdown()` (builder), `diffFormatter` (async), `tableFormatter`, `treeFormatter`.
|
|
253
253
|
|
|
@@ -378,7 +378,7 @@ See `api-context` skill for full details.
|
|
|
378
378
|
|
|
379
379
|
## Error Handling
|
|
380
380
|
|
|
381
|
-
**Recommended path: declare a typed error contract.** Add `errors: [{ reason, code, when, recovery, retryable? }]` to `tool()` / `resource()`. Handler gets `ctx.fail(reason, msg?, data?)` typed against the reason union — typos fail at compile time. Runtime auto-populates `data.reason` for observability; linter enforces conformance against the handler body. `recovery` is required (≥5 words, lint-validated) — the single source of truth for the wire hint. Spread `ctx.recoveryFor('reason')` into `data` to opt the contract recovery onto the wire (framework mirrors `data.recovery.hint` into `content[]` text); override with explicit `{ recovery: { hint: '...' } }` when runtime context matters.
|
|
381
|
+
**Recommended path: declare a typed error contract.** Add `errors: [{ reason, code, when, recovery, retryable?, severity?, thrownBy? }]` to `tool()` / `resource()`. Handler gets `ctx.fail(reason, msg?, data?)` typed against the reason union — typos fail at compile time. Runtime auto-populates `data.reason` for observability; linter enforces conformance against the handler body. `recovery` is required (≥5 words, lint-validated) — the single source of truth for the wire hint. Spread `ctx.recoveryFor('reason')` into `data` to opt the contract recovery onto the wire (framework mirrors `data.recovery.hint` into `content[]` text); override with explicit `{ recovery: { hint: '...' } }` when runtime context matters. Optional `severity` (`debug`/`info`/`notice`/`warning`) logs a modeled outcome below `error` — tools only, logging only: the wire envelope, the span status, and the `mcp.tool.*` metrics are untouched. Optional `thrownBy: 'service'` marks an entry the service layer produces, so `error-contract-unthrown` skips it while still checking the handler's own reasons — lint-only metadata, nothing at runtime reads it.
|
|
382
382
|
|
|
383
383
|
```ts
|
|
384
384
|
errors: [
|
|
@@ -418,9 +418,9 @@ For HTTP responses from upstream APIs, use `httpErrorFromResponse(response, { se
|
|
|
418
418
|
|
|
419
419
|
**Auto-classification.** Plain `Error`, `ZodError`, and any other thrown value are caught and classified automatically. Resolution order: request signal already aborted (→ `RequestCancelled`, outranking the thrown value's own code, `McpError` included) → `McpError` code (preserved as-is) → SDK `ConnectionClosed` (→ `RequestCancelled`) → JS constructor name (`TypeError` → `ValidationError`) → provider patterns (HTTP status codes, AWS errors, DB errors) → common message patterns → `AbortError` name (→ `Timeout`) → `InternalError` fallback.
|
|
420
420
|
|
|
421
|
-
**Error-path parity.** Tool errors: `content[]` carries
|
|
421
|
+
**Error-path parity.** Tool errors: `content[]` carries `Error: <message>`, then `Recovery: <hint>` when the hint says something the message does not already contain, then a closing `(reason … · not retryable)` for whichever of `data.reason` / `data.retryable` is present; the numeric code and `data.issues` stay JSON-only. `structuredContent.error` carries `{ code, message, data? }`. No `_meta.error`. Resources re-throw via JSON-RPC error envelope. An argument rejection is one of them: `-32602` with `data.issues`, plus `data.reason: 'invalid_arguments'` and a hint synthesized from the issues and the root schema — never a tool-declared `reason`, since the handler never ran. `client_capability_missing` is the second framework-owned reason: a `ctx.requestInput` return a 2025-era connection cannot serve is refused before any wire traffic as `-32600` carrying that reason and a hint naming the capability.
|
|
422
422
|
|
|
423
|
-
**Lint rules** (all warnings, surfaced in `devcheck`): `prefer-mcp-error-in-handler`, `prefer-error-factory`, `preserve-cause-on-rethrow`, `no-stringify-upstream-error`, `error-contract-conformance`, `error-contract-prefer-fail
|
|
423
|
+
**Lint rules** (all warnings, surfaced in `devcheck`): `prefer-mcp-error-in-handler`, `prefer-error-factory`, `preserve-cause-on-rethrow`, `no-stringify-upstream-error`, `error-contract-conformance`, `error-contract-prefer-fail`, `error-contract-unthrown` (a declared reason no literal `ctx.fail`/`ctx.recoveryFor` in the handler names, unless marked `thrownBy: 'service'`), `error-contract-recovery-unforwarded` (a `ctx.fail` site carrying neither `ctx.recoveryFor('<reason>')` nor its own `recovery` key, so the declared hint reaches neither client surface). See `api-linter` skill.
|
|
424
424
|
|
|
425
425
|
See `api-errors` skill for the full pattern-matching table, error code reference, and detailed examples.
|
|
426
426
|
|
package/CLAUDE.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Developer Protocol
|
|
2
2
|
|
|
3
3
|
**Package:** `@cyanheads/mcp-ts-core`
|
|
4
|
-
**Version:** 0.13.
|
|
4
|
+
**Version:** 0.13.6
|
|
5
5
|
**Engines:** Bun ≥1.4.0, Node ≥24.0.0
|
|
6
6
|
**MCP SDK:** `@modelcontextprotocol/server` ^2.0.0 (protocol revisions 2026-07-28 and 2025-*)
|
|
7
7
|
**Zod:** ^4.6.5
|
|
@@ -247,7 +247,7 @@ export const myTool = tool('my_tool', {
|
|
|
247
247
|
**`format`**: Maps output to MCP `content[]`. Different clients forward different surfaces to the agent — some (Claude Code) read `structuredContent` from `output`, others (Claude Desktop) read `content[]` from `format()`. `format()` is the markdown twin of `structuredContent`, not a reduced summary.
|
|
248
248
|
|
|
249
249
|
- **Parity is lint-enforced.** Every terminal field in `output` must appear in `format()`'s rendered text (via sentinel injection), or the `format-parity` rule fails `bun run lint:mcp` / `devcheck`.
|
|
250
|
-
- **Primary fix:** render the missing field in `format()`.
|
|
250
|
+
- **Primary fix:** render the missing field in `format()`. For list/detail variants, declare one flat `z.object` with a `kind` discriminator and presence-based optional arms, rendered by independent `if` blocks — `tool()` rejects a `z.discriminatedUnion` output root.
|
|
251
251
|
- **Escape hatch:** if the schema was over-typed for a genuinely dynamic upstream API, relax it (`z.object({}).passthrough()`) — passthrough still flows data to `structuredContent`.
|
|
252
252
|
- **Fallback:** omit `format` for JSON stringify. Additional formatters in `/utils`: `markdown()` (builder), `diffFormatter` (async), `tableFormatter`, `treeFormatter`.
|
|
253
253
|
|
|
@@ -378,7 +378,7 @@ See `api-context` skill for full details.
|
|
|
378
378
|
|
|
379
379
|
## Error Handling
|
|
380
380
|
|
|
381
|
-
**Recommended path: declare a typed error contract.** Add `errors: [{ reason, code, when, recovery, retryable? }]` to `tool()` / `resource()`. Handler gets `ctx.fail(reason, msg?, data?)` typed against the reason union — typos fail at compile time. Runtime auto-populates `data.reason` for observability; linter enforces conformance against the handler body. `recovery` is required (≥5 words, lint-validated) — the single source of truth for the wire hint. Spread `ctx.recoveryFor('reason')` into `data` to opt the contract recovery onto the wire (framework mirrors `data.recovery.hint` into `content[]` text); override with explicit `{ recovery: { hint: '...' } }` when runtime context matters.
|
|
381
|
+
**Recommended path: declare a typed error contract.** Add `errors: [{ reason, code, when, recovery, retryable?, severity?, thrownBy? }]` to `tool()` / `resource()`. Handler gets `ctx.fail(reason, msg?, data?)` typed against the reason union — typos fail at compile time. Runtime auto-populates `data.reason` for observability; linter enforces conformance against the handler body. `recovery` is required (≥5 words, lint-validated) — the single source of truth for the wire hint. Spread `ctx.recoveryFor('reason')` into `data` to opt the contract recovery onto the wire (framework mirrors `data.recovery.hint` into `content[]` text); override with explicit `{ recovery: { hint: '...' } }` when runtime context matters. Optional `severity` (`debug`/`info`/`notice`/`warning`) logs a modeled outcome below `error` — tools only, logging only: the wire envelope, the span status, and the `mcp.tool.*` metrics are untouched. Optional `thrownBy: 'service'` marks an entry the service layer produces, so `error-contract-unthrown` skips it while still checking the handler's own reasons — lint-only metadata, nothing at runtime reads it.
|
|
382
382
|
|
|
383
383
|
```ts
|
|
384
384
|
errors: [
|
|
@@ -418,9 +418,9 @@ For HTTP responses from upstream APIs, use `httpErrorFromResponse(response, { se
|
|
|
418
418
|
|
|
419
419
|
**Auto-classification.** Plain `Error`, `ZodError`, and any other thrown value are caught and classified automatically. Resolution order: request signal already aborted (→ `RequestCancelled`, outranking the thrown value's own code, `McpError` included) → `McpError` code (preserved as-is) → SDK `ConnectionClosed` (→ `RequestCancelled`) → JS constructor name (`TypeError` → `ValidationError`) → provider patterns (HTTP status codes, AWS errors, DB errors) → common message patterns → `AbortError` name (→ `Timeout`) → `InternalError` fallback.
|
|
420
420
|
|
|
421
|
-
**Error-path parity.** Tool errors: `content[]` carries
|
|
421
|
+
**Error-path parity.** Tool errors: `content[]` carries `Error: <message>`, then `Recovery: <hint>` when the hint says something the message does not already contain, then a closing `(reason … · not retryable)` for whichever of `data.reason` / `data.retryable` is present; the numeric code and `data.issues` stay JSON-only. `structuredContent.error` carries `{ code, message, data? }`. No `_meta.error`. Resources re-throw via JSON-RPC error envelope. An argument rejection is one of them: `-32602` with `data.issues`, plus `data.reason: 'invalid_arguments'` and a hint synthesized from the issues and the root schema — never a tool-declared `reason`, since the handler never ran. `client_capability_missing` is the second framework-owned reason: a `ctx.requestInput` return a 2025-era connection cannot serve is refused before any wire traffic as `-32600` carrying that reason and a hint naming the capability.
|
|
422
422
|
|
|
423
|
-
**Lint rules** (all warnings, surfaced in `devcheck`): `prefer-mcp-error-in-handler`, `prefer-error-factory`, `preserve-cause-on-rethrow`, `no-stringify-upstream-error`, `error-contract-conformance`, `error-contract-prefer-fail
|
|
423
|
+
**Lint rules** (all warnings, surfaced in `devcheck`): `prefer-mcp-error-in-handler`, `prefer-error-factory`, `preserve-cause-on-rethrow`, `no-stringify-upstream-error`, `error-contract-conformance`, `error-contract-prefer-fail`, `error-contract-unthrown` (a declared reason no literal `ctx.fail`/`ctx.recoveryFor` in the handler names, unless marked `thrownBy: 'service'`), `error-contract-recovery-unforwarded` (a `ctx.fail` site carrying neither `ctx.recoveryFor('<reason>')` nor its own `recovery` key, so the declared hint reaches neither client surface). See `api-linter` skill.
|
|
424
424
|
|
|
425
425
|
See `api-errors` skill for the full pattern-matching table, error code reference, and detailed examples.
|
|
426
426
|
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
<div align="center">
|
|
8
8
|
|
|
9
|
-
[](./CHANGELOG.md) [](./LICENSE) [](https://modelcontextprotocol.io/specification/2026-07-28)
|
|
10
10
|
|
|
11
11
|
[](https://modelcontextprotocol.io/) [](https://www.typescriptlang.org/) [](https://bun.sh/)
|
|
12
12
|
|
package/biome.json
CHANGED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Tool error text closes with the reason and retryable terms a caller branches on, a ctx.requestInput no 2025-era client capability can serve is refused with a real error envelope, an errors[] entry can declare a log severity below error, and a malformed canvas_id is an input error rather than an expired canvas."
|
|
3
|
+
breaking: false
|
|
4
|
+
security: false
|
|
5
|
+
agent-notes: |
|
|
6
|
+
Three adoption steps for a consumer upgrading from 0.13.4.
|
|
7
|
+
|
|
8
|
+
1. A test pinning `content[0].text` exactly moves for any tool error whose
|
|
9
|
+
`data` carries a `reason` or a boolean `retryable` — the text now closes
|
|
10
|
+
with `(reason <reason> · not retryable)`. Assert containment instead.
|
|
11
|
+
`toolContractSuite`'s `^Error:` assertion is unaffected.
|
|
12
|
+
2. Declare a tool's `canvas_id` *input* field with `CanvasIdSchema` from
|
|
13
|
+
`@cyanheads/mcp-ts-core/canvas` (add your own `.describe()` naming the
|
|
14
|
+
producing tool). The *output* `canvas_id` stays a plain `z.string()`.
|
|
15
|
+
3. `error-contract-unthrown` is new and fires on the next `devcheck`: a
|
|
16
|
+
declared `errors[]` reason no literal `ctx.fail('<reason>'` in the handler
|
|
17
|
+
names. Wire the throw, or drop the entry.
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# 0.13.5 — 2026-09-18
|
|
21
|
+
|
|
22
|
+
## Added
|
|
23
|
+
|
|
24
|
+
- **`severity` on an `errors[]` entry** ([#380](https://github.com/cyanheads/mcp-ts-core/issues/380)) — `debug`, `info`, `notice`, or `warning`, resolved against the thrown error's `data.reason`. It moves that one log record's level and adds `mcp.error.severity` to `mcp.errors.classified`; the wire envelope, the span status, and the `mcp.tool.*` counters are untouched. Tools only.
|
|
25
|
+
- **`CanvasIdSchema`** from `/canvas` ([#327](https://github.com/cyanheads/mcp-ts-core/issues/327)) — the minted `^[A-Za-z0-9_-]{10}$` shape, for a tool's `canvas_id` *input* field so the constraint reaches `inputSchema` and an impossible value is rejected at argument validation.
|
|
26
|
+
- **`errorHeaders` on `fetchWithTimeout` and `httpErrorFromResponse`** ([#302](https://github.com/cyanheads/mcp-ts-core/issues/302)) — an allowlist of response headers copied onto `error.data.headers` under lowercase keys on a non-2xx. Every selected value reaches the client, so never name a credential-bearing header; `set-cookie` is never captured.
|
|
27
|
+
- **Lint rules `error-contract-unthrown` (warning) and `error-contract-severity-unknown` (error)** ([#290](https://github.com/cyanheads/mcp-ts-core/issues/290), [#380](https://github.com/cyanheads/mcp-ts-core/issues/380)) — a declared reason no literal `ctx.fail` / `ctx.recoveryFor` in the handler names, and a `severity` outside the four levels.
|
|
28
|
+
|
|
29
|
+
## Changed
|
|
30
|
+
|
|
31
|
+
- **Tool error `content[]` closes with `(reason … · not retryable)`** ([#458](https://github.com/cyanheads/mcp-ts-core/issues/458)) whenever `data.reason` is a non-empty string or `data.retryable` a boolean. The numeric code and `data.issues` stay JSON-only, and `structuredContent` is byte-identical to before.
|
|
32
|
+
- **The `Recovery:` line is dropped when the trimmed message already contains the hint verbatim** ([#459](https://github.com/cyanheads/mcp-ts-core/issues/459)) — the case a constraint or refinement rejection produces, whose synthesized hint is the issue's own message. `structuredContent.error.data.recovery.hint` stays populated.
|
|
33
|
+
- **The canvas tenant-cap refusal keeps `RateLimited` and names itself** ([#275](https://github.com/cyanheads/mcp-ts-core/issues/275)) — `data.reason: 'canvas_capacity_exhausted'`, `retryable: true`, and a hint leading with reusing a held `canvas_id`. `getErrorCategory` reads the error data alongside the code, so `mcp.tool.error_category` files it as `server` rather than `upstream`.
|
|
34
|
+
- Skill versions: `add-tool` 2.27 → 2.28, `api-canvas` 2.2 → 2.3, `api-context` 2.4 → 2.5, `api-errors` 1.13 → 1.14, `api-linter` 1.15 → 1.16, `api-telemetry` 1.11 → 1.12, `api-utils` 2.10 → 2.11, `design-mcp-server` 2.27 → 2.28, `field-test` 2.15 → 2.16.
|
|
35
|
+
|
|
36
|
+
## Fixed
|
|
37
|
+
|
|
38
|
+
- **A `ctx.requestInput` the connection cannot serve fails with an error envelope** ([#379](https://github.com/cyanheads/mcp-ts-core/issues/379)) — on a 2025-era connection declaring no matching capability, `ctx.requestInput` throws `InvalidRequest` (`-32600`) with `data.reason: 'client_capability_missing'` and a hint naming `elicitation.form`, `elicitation.url`, `sampling.tools`, or `roots`, in place of the bare `isError` text block. A resource read gets the same code, reason, and hint instead of `-32603`. Still refused before any wire traffic; the 2026-07-28 leg is unchanged.
|
|
39
|
+
- **The advertised `error.data.reason` description terminates each contract entry's `when`** ([#389](https://github.com/cyanheads/mcp-ts-core/issues/389)), so entries no longer run into each other or into the trailing sentence. Authored text is untouched — a `when` already ending in `.`, `?`, or `!` takes no second terminator.
|
|
40
|
+
- **A malformed `canvas_id` is a `ValidationError`, not an expired canvas** ([#327](https://github.com/cyanheads/mcp-ts-core/issues/327)) — `data.reason: 'canvas_id_malformed'` before any registry lookup, on `acquire`, `drop` (which previously reported a silent `false`), and `importFrom`'s source id. A well-formed id that is missing, expired, or another tenant's keeps `canvas_not_found`.
|
|
41
|
+
- **A 3xx maps to `InvalidRequest` rather than `InternalError`** ([#460](https://github.com/cyanheads/mcp-ts-core/issues/460)) — reachable under `redirect: 'manual'`, where `httpStatusToErrorCode` previously fell through to a code meaning *this* server failed. Outside `withRetry`'s transient set, since re-issuing returns the same redirect.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "A ctx.fail site that never forwards its declared recovery now warns, an errors[] entry can mark itself thrownBy: 'service' so the unthrown rule skips it, capped-list-no-truncation stops firing on value bounds like max_depth_km, and devcheck.config.json can carry the truncation allowlist."
|
|
3
|
+
breaking: false
|
|
4
|
+
security: false
|
|
5
|
+
agent-notes: |
|
|
6
|
+
Four adoption steps for a consumer upgrading from 0.13.5.
|
|
7
|
+
|
|
8
|
+
1. `error-contract-recovery-unforwarded` is new and fires on the next
|
|
9
|
+
`devcheck` / `lint:mcp`: a literal `ctx.fail('<reason>', …)` site carrying
|
|
10
|
+
neither `ctx.recoveryFor('<reason>')` nor its own `recovery` key. Clear it
|
|
11
|
+
by spreading `...ctx.recoveryFor('<reason>')` into the data argument, by
|
|
12
|
+
passing the resolver as that argument, or with an explicit
|
|
13
|
+
`recovery: { hint }` when the hint needs runtime context.
|
|
14
|
+
2. An `errors[]` reason the service layer produces takes `thrownBy: 'service'`,
|
|
15
|
+
which is what `error-contract-unthrown` reads to skip it. Prefer it over
|
|
16
|
+
moving the handler's literal `ctx.fail` out of scan range — that silences
|
|
17
|
+
the rule for the whole definition, handler-local reasons included.
|
|
18
|
+
3. Declare `lint.truncationAllowlist` in `devcheck.config.json` in place of
|
|
19
|
+
`MCP_LINT_TRUNCATION_ALLOWLIST` on each script entry, and drop the env var
|
|
20
|
+
from the `devcheck` and `lint:mcp` scripts once the key is in place. A
|
|
21
|
+
scaffolded config predating this release has no `lint` key; add it.
|
|
22
|
+
4. Drop any `truncationAllowlist` entry added only to silence
|
|
23
|
+
`capped-list-no-truncation` on a value-bound `max_*` input — those no
|
|
24
|
+
longer warn.
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
# 0.13.6 — 2026-09-19
|
|
28
|
+
|
|
29
|
+
## Added
|
|
30
|
+
|
|
31
|
+
- **Lint rule `error-contract-recovery-unforwarded`** (warning, [#255](https://github.com/cyanheads/mcp-ts-core/issues/255)) — one diagnostic per literal `ctx.fail('<reason>', …)` site carrying neither `ctx.recoveryFor('<reason>')` nor its own `recovery` key, so the declared hint reaches neither client surface. A site whose only resolver names a different reason warns too, naming both.
|
|
32
|
+
- **`thrownBy: 'service'` on an `errors[]` entry** ([#462](https://github.com/cyanheads/mcp-ts-core/issues/462)) — lint-only metadata marking a reason produced below the handler, so `error-contract-unthrown` skips it while the handler's own reasons keep being checked. The entry stays typed, advertised, and thrown exactly as an unmarked one.
|
|
33
|
+
- **`lint.truncationAllowlist` in `devcheck.config.json`** ([#388](https://github.com/cyanheads/mcp-ts-core/issues/388)) — read by `scripts/lint-mcp.ts` and forwarded as `LintInput.truncationAllowlist`, so one project-owned declaration covers every entrypoint that shells out to the linter. Takes an array of tool names or `false`; precedence is explicit `LintInput`, then the config file, then `MCP_LINT_TRUNCATION_ALLOWLIST`.
|
|
34
|
+
|
|
35
|
+
## Changed
|
|
36
|
+
|
|
37
|
+
- **The scaffold's `echo` tool forwards its declared recovery** ([#255](https://github.com/cyanheads/mcp-ts-core/issues/255)) — `templates/src/mcp-server/tools/definitions/echo.tool.ts` spreads `ctx.recoveryFor('empty_message')`, so a new server starts from the shape the rule asks for.
|
|
38
|
+
- Skill versions: `add-service` 1.10 → 1.11, `add-tool` 2.28 → 2.29, `api-errors` 1.14 → 1.15, `api-linter` 1.16 → 1.17.
|
|
39
|
+
|
|
40
|
+
## Fixed
|
|
41
|
+
|
|
42
|
+
- **`capped-list-no-truncation`'s `max_` arm requires the counted noun to name a returned list** ([#345](https://github.com/cyanheads/mcp-ts-core/issues/345)) — a depth-0 array in `output` (plural-insensitive, a trailing `_count` stripped first) or a generic result container (`results`, `records`, `items`, `rows`, `hits`, `entries`, `matches`, `count`, `page`, `docs`). Value bounds (`max_depth_km`, `maxLat`, `max_date`) and work budgets (`max_court_lookups`, `maxCharacters`) stop warning; `limit`, `<noun>_limit`, and the page-size idioms are unchanged.
|
|
43
|
+
- **`error-contract-unthrown` skips a definition whose `ctx.recoveryFor(` takes a non-literal first argument** ([#462](https://github.com/cyanheads/mcp-ts-core/issues/462)), as it already did for `ctx.fail(` — unknown reasons are in play either way. Its message now names all three fixes.
|
|
44
|
+
- **The `format-parity` diagnostic no longer points authors at a `z.discriminatedUnion` output** ([#268](https://github.com/cyanheads/mcp-ts-core/issues/268)), which `tool()` rejects before any lint rule runs. The "Primary fix" line names a flat `z.object` with a `kind` discriminator and presence-based optional arms, rendered by independent `if` blocks.
|
|
45
|
+
- **The `sanitizeHtml` event-handler property matches only inside a tag** ([#463](https://github.com/cyanheads/mcp-ts-core/issues/463)) — the whole-string `/\bon\w+\s*=/` also matched inert text such as `on0=`, failing roughly once in 300 runs. `on0=` and `<p>onclick=1</p>` are pinned as `examples`; the sanitizer itself is unchanged.
|
|
46
|
+
|
|
47
|
+
## Dependencies
|
|
48
|
+
|
|
49
|
+
- `@biomejs/biome` 2.5.13 → 2.5.14, `fast-check` ^4.10.0 → ^4.10.1.
|
|
@@ -45,10 +45,11 @@ export interface TruncationOptions {
|
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
47
47
|
* Warns when a tool takes a cap-like input field (`limit`, `per_page`,
|
|
48
|
-
* `maxRecords`, … — see `isCapFieldName`)
|
|
49
|
-
*
|
|
50
|
-
* `truncated` key in its declared `enrichment` shape, nor
|
|
51
|
-
* nor `truncated` or `totalCount` as top-level
|
|
48
|
+
* `maxRecords`, … — see `isCapFieldName`) that plausibly caps the returned list
|
|
49
|
+
* (see `capsTheList`) and returns an array output, but declares no truncation
|
|
50
|
+
* disclosure — neither a `truncated` key in its declared `enrichment` shape, nor
|
|
51
|
+
* `totalCount` in enrichment, nor `truncated` or `totalCount` as top-level
|
|
52
|
+
* `output` keys.
|
|
52
53
|
*
|
|
53
54
|
* The established `enrich.total()` convention (`totalCount`) and bespoke output fields
|
|
54
55
|
* count as honest disclosure — the rule fires only on a genuinely silent cap.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"enrichment-rules.d.ts","sourceRoot":"","sources":["../../../src/linter/rules/enrichment-rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"enrichment-rules.d.ts","sourceRoot":"","sources":["../../../src/linter/rules/enrichment-rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAqJtE;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,EAAE;IAAE,UAAU,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,EAC/F,cAAc,EAAE,kBAAkB,EAClC,cAAc,EAAE,MAAM,GACrB,cAAc,EAAE,CA0IlB;AAED,sDAAsD;AACtD,MAAM,WAAW,iBAAiB;IAChC,2EAA2E;IAC3E,mBAAmB,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC;CACrD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CACtC,GAAG,EACC;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,GAC3E,IAAI,GACJ,SAAS,EACb,iBAAiB,CAAC,EAAE,iBAAiB,GACpC,cAAc,EAAE,CAgDlB"}
|
|
@@ -19,13 +19,60 @@ import { getCoreDefType, objectShape, objectShapeKeys, unwrapWrappers } from './
|
|
|
19
19
|
/** Cap-like names that carry no `max`/`limit` morpheme and must be listed outright. */
|
|
20
20
|
const CAP_FIELD_EXACT = new Set(['limit', 'per_page', 'page_size']);
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
22
|
+
* Nouns that name a generic result container whatever the array is called, so a
|
|
23
|
+
* `max_<noun>` built on one of them caps the list even when nothing in `output`
|
|
24
|
+
* carries the same name — `maxRecords` against an `articles` array. Stored
|
|
25
|
+
* singular; the counted noun is singularized before the lookup, so the plural
|
|
26
|
+
* spellings (`results`, `records`, `items`, `rows`, `hits`, `entries`,
|
|
27
|
+
* `matches`, `docs`) resolve here too.
|
|
28
|
+
*/
|
|
29
|
+
const RESULT_CONTAINER_NOUNS = new Set([
|
|
30
|
+
'count',
|
|
31
|
+
'doc',
|
|
32
|
+
'entry',
|
|
33
|
+
'hit',
|
|
34
|
+
'item',
|
|
35
|
+
'match',
|
|
36
|
+
'page',
|
|
37
|
+
'record',
|
|
38
|
+
'result',
|
|
39
|
+
'row',
|
|
40
|
+
]);
|
|
41
|
+
/** camelCase → snake_case, lowercased, so `maxRecords` and `max_records` are one case. */
|
|
42
|
+
function snakeCase(key) {
|
|
43
|
+
return key.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Singularizes on a bounded suffix set — `articles` → `article`, `studies` →
|
|
47
|
+
* `study`, `matches` → `match`. Not a general English pluralizer: it only has to
|
|
48
|
+
* make an input's counted noun and an output array's name meet in the middle.
|
|
49
|
+
*/
|
|
50
|
+
function singularize(noun) {
|
|
51
|
+
if (noun.endsWith('ies'))
|
|
52
|
+
return `${noun.slice(0, -3)}y`;
|
|
53
|
+
if (/(?:s|x|ch|sh)es$/.test(noun))
|
|
54
|
+
return noun.slice(0, -2);
|
|
55
|
+
if (noun.endsWith('s') && !noun.endsWith('ss'))
|
|
56
|
+
return noun.slice(0, -1);
|
|
57
|
+
return noun;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The noun a `max_*` field counts: the name minus the `max_` prefix, minus a
|
|
61
|
+
* trailing `_count` (`max_result_count` → `result`), reduced to its last
|
|
62
|
+
* underscore segment and singularized.
|
|
63
|
+
*/
|
|
64
|
+
function countedNoun(key) {
|
|
65
|
+
const stem = snakeCase(key)
|
|
66
|
+
.replace(/^max_/, '')
|
|
67
|
+
.replace(/_count$/, '');
|
|
68
|
+
return singularize(stem.split('_').pop() ?? stem);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* True for a depth-0 input field name that is cap-*shaped*.
|
|
23
72
|
*
|
|
24
73
|
* Matched by shape, not by an enumeration: an allowlist turns every new cap noun
|
|
25
74
|
* (`maxRecords`, `maxRows`, `resultLimit`) into a silent gap where the rule never
|
|
26
|
-
* runs at all, which is the same defect class the rule exists to catch.
|
|
27
|
-
* naming conventions normalize to the same snake form first, so `maxRecords` and
|
|
28
|
-
* `max_records` are one case.
|
|
75
|
+
* runs at all, which is the same defect class the rule exists to catch.
|
|
29
76
|
*
|
|
30
77
|
* - `limit`, and any `<noun>_limit` / `<noun>Limit`
|
|
31
78
|
* - any `max_<noun>` / `max<Noun>`
|
|
@@ -34,17 +81,36 @@ const CAP_FIELD_EXACT = new Set(['limit', 'per_page', 'page_size']);
|
|
|
34
81
|
* Deliberately NOT matched: bare `count`, `size`, `n`, `rows`, `records`, and
|
|
35
82
|
* words that merely start with the letters (`maximum`).
|
|
36
83
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* says anything, and `truncationAllowlist` exempts a tool that trips it anyway.
|
|
84
|
+
* Shape alone cannot separate a cap on *how many* from an upper bound on a
|
|
85
|
+
* *value* or a budget on work done, so the `max_` arm is narrowed further by
|
|
86
|
+
* `capsTheList`.
|
|
41
87
|
*/
|
|
42
88
|
function isCapFieldName(key) {
|
|
43
|
-
const normalized = key
|
|
89
|
+
const normalized = snakeCase(key);
|
|
44
90
|
return (CAP_FIELD_EXACT.has(normalized) ||
|
|
45
91
|
normalized.startsWith('max_') ||
|
|
46
92
|
normalized.endsWith('_limit'));
|
|
47
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* True when a cap-shaped input field plausibly caps the returned list.
|
|
96
|
+
*
|
|
97
|
+
* `limit` / `<noun>_limit` / the page-size idioms say what they bound in the
|
|
98
|
+
* name itself and always qualify. `max_<noun>` does not: the same spelling
|
|
99
|
+
* carries value bounds (`max_depth_km`, `maxLat`, `max_date`) and budgets on
|
|
100
|
+
* secondary work (`max_court_lookups`, `maxCharacters`), none of which slice the
|
|
101
|
+
* array. So the counted noun has to name something the tool returns — a depth-0
|
|
102
|
+
* array in `output`, or a generic result container.
|
|
103
|
+
*
|
|
104
|
+
* Accepted false negative: a domain cap naming neither, `max_studies` against a
|
|
105
|
+
* `documents` array, goes silent. Nothing in the declaration separates it from a
|
|
106
|
+
* value bound, and `truncationAllowlist` only suppresses — it cannot re-enable.
|
|
107
|
+
*/
|
|
108
|
+
function capsTheList(key, arrayNouns) {
|
|
109
|
+
if (!snakeCase(key).startsWith('max_'))
|
|
110
|
+
return true;
|
|
111
|
+
const noun = countedNoun(key);
|
|
112
|
+
return RESULT_CONTAINER_NOUNS.has(noun) || arrayNouns.has(noun);
|
|
113
|
+
}
|
|
48
114
|
/**
|
|
49
115
|
* Output field names that almost always indicate agent-facing context rather
|
|
50
116
|
* than domain payload. Kept deliberately tiny to avoid false positives —
|
|
@@ -228,10 +294,11 @@ export function lintEnrichmentContract(def, definitionType, definitionName) {
|
|
|
228
294
|
}
|
|
229
295
|
/**
|
|
230
296
|
* Warns when a tool takes a cap-like input field (`limit`, `per_page`,
|
|
231
|
-
* `maxRecords`, … — see `isCapFieldName`)
|
|
232
|
-
*
|
|
233
|
-
* `truncated` key in its declared `enrichment` shape, nor
|
|
234
|
-
* nor `truncated` or `totalCount` as top-level
|
|
297
|
+
* `maxRecords`, … — see `isCapFieldName`) that plausibly caps the returned list
|
|
298
|
+
* (see `capsTheList`) and returns an array output, but declares no truncation
|
|
299
|
+
* disclosure — neither a `truncated` key in its declared `enrichment` shape, nor
|
|
300
|
+
* `totalCount` in enrichment, nor `truncated` or `totalCount` as top-level
|
|
301
|
+
* `output` keys.
|
|
235
302
|
*
|
|
236
303
|
* The established `enrich.total()` convention (`totalCount`) and bespoke output fields
|
|
237
304
|
* count as honest disclosure — the rule fires only on a genuinely silent cap.
|
|
@@ -251,12 +318,16 @@ export function lintCappedListTruncation(def, truncationOptions) {
|
|
|
251
318
|
// per variant, so a cap in any branch counts — the rule asks whether the tool
|
|
252
319
|
// can be capped at all, not whether every branch caps.
|
|
253
320
|
const inputKeys = inputVariants(def.input).flatMap((variant) => objectShapeKeys(variant));
|
|
254
|
-
const
|
|
255
|
-
if (
|
|
321
|
+
const capShapedKeys = inputKeys.filter(isCapFieldName);
|
|
322
|
+
if (capShapedKeys.length === 0)
|
|
256
323
|
return [];
|
|
257
|
-
// Check output for at least one array-typed field
|
|
258
|
-
|
|
259
|
-
|
|
324
|
+
// Check output for at least one array-typed field. The array names are also
|
|
325
|
+
// what a `max_<noun>` cap has to correlate with to count as a list cap.
|
|
326
|
+
const arrayNouns = topLevelArrayNouns(def.output);
|
|
327
|
+
if (arrayNouns.size === 0)
|
|
328
|
+
return [];
|
|
329
|
+
const capKeys = capShapedKeys.filter((key) => capsTheList(key, arrayNouns));
|
|
330
|
+
if (capKeys.length === 0)
|
|
260
331
|
return [];
|
|
261
332
|
// Check for truncation disclosure:
|
|
262
333
|
// 1. Declared enrichment has `truncated` or `totalCount`
|
|
@@ -279,12 +350,18 @@ export function lintCappedListTruncation(def, truncationOptions) {
|
|
|
279
350
|
},
|
|
280
351
|
];
|
|
281
352
|
}
|
|
282
|
-
/**
|
|
283
|
-
|
|
353
|
+
/**
|
|
354
|
+
* The singularized names of every depth-0 field whose core Zod type is `array`.
|
|
355
|
+
* Empty means the tool returns no list at all, which is the rule's array
|
|
356
|
+
* precondition; non-empty is also what a `max_<noun>` cap correlates against.
|
|
357
|
+
*/
|
|
358
|
+
function topLevelArrayNouns(schema) {
|
|
284
359
|
const shape = objectShape(schema);
|
|
285
360
|
if (!shape)
|
|
286
|
-
return
|
|
287
|
-
return Object.
|
|
361
|
+
return new Set();
|
|
362
|
+
return new Set(Object.entries(shape)
|
|
363
|
+
.filter(([, field]) => getCoreDefType(unwrapWrappers(field)) === 'array')
|
|
364
|
+
.map(([key]) => singularize(snakeCase(key).split('_').pop() ?? key)));
|
|
288
365
|
}
|
|
289
366
|
/**
|
|
290
367
|
* True when truncation is already disclosed — enrichment block has `truncated` or
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"enrichment-rules.js","sourceRoot":"","sources":["../../../src/linter/rules/enrichment-rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,yCAAyC,CAAC;AAExE,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEjG,uFAAuF;AACvF,MAAM,eAAe,GAAwB,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC,CAAC;AAEzF
|
|
1
|
+
{"version":3,"file":"enrichment-rules.js","sourceRoot":"","sources":["../../../src/linter/rules/enrichment-rules.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Lint rules for the declarative `errors[]` contract on tool and
|
|
3
|
-
* resource definitions. Validates structure (codes, reasons
|
|
4
|
-
* and
|
|
5
|
-
* codes
|
|
3
|
+
* resource definitions. Validates structure (codes, reasons, severity),
|
|
4
|
+
* uniqueness, and — when a contract is present — cross-checks the handler body:
|
|
5
|
+
* codes thrown but not declared, reasons declared but never thrown, and throw
|
|
6
|
+
* sites that never put the declared `recovery` on the wire.
|
|
6
7
|
* @module src/linter/rules/error-contract-rules
|
|
7
8
|
*/
|
|
8
9
|
import type { LintDefinitionType, LintDiagnostic } from '../types.js';
|
|
@@ -16,6 +17,7 @@ import type { LintDefinitionType, LintDiagnostic } from '../types.js';
|
|
|
16
17
|
* - `recovery` is non-empty and ≥ 5 words (forcing function for thoughtful
|
|
17
18
|
* agent guidance — placeholders like "Try again." get flagged)
|
|
18
19
|
* - `retryable` (when present) is a boolean
|
|
20
|
+
* - `severity` (when present) is one of the four levels below `error`
|
|
19
21
|
*/
|
|
20
22
|
export declare function lintErrorContract(errors: unknown, definitionType: LintDefinitionType, definitionName: string): LintDiagnostic[];
|
|
21
23
|
/**
|
|
@@ -46,4 +48,100 @@ export declare function lintErrorContractConformance(def: {
|
|
|
46
48
|
handler?: unknown;
|
|
47
49
|
errors?: unknown;
|
|
48
50
|
}, definitionType: LintDefinitionType, definitionName: string): LintDiagnostic[];
|
|
51
|
+
/** One literal `ctx.fail('<reason>', …)` (or `ctx.recoveryFor`) call site. */
|
|
52
|
+
export interface ReasonCallSite {
|
|
53
|
+
/** Source offset just past the site's closing `)`. */
|
|
54
|
+
end: number;
|
|
55
|
+
/** The reason named by the site's first argument, read from the raw source. */
|
|
56
|
+
reason: string;
|
|
57
|
+
/** Source offset of the site's opening `(`. */
|
|
58
|
+
start: number;
|
|
59
|
+
}
|
|
60
|
+
/** What {@link scanReasonCalls} found for one callee in a handler's source. */
|
|
61
|
+
export interface ReasonCallScan {
|
|
62
|
+
/**
|
|
63
|
+
* True when some call site took a non-literal first argument — a variable, a
|
|
64
|
+
* template literal, a map lookup. The set of reasons in play is then unknown.
|
|
65
|
+
*/
|
|
66
|
+
indeterminate: boolean;
|
|
67
|
+
/** Every literal call site found, in source order. */
|
|
68
|
+
sites: ReasonCallSite[];
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Finds every `<callee>('<literal>', …)` site in a handler's source, one record
|
|
72
|
+
* per site.
|
|
73
|
+
*
|
|
74
|
+
* Matching runs over the comment- and string-stripped text, which already
|
|
75
|
+
* excludes a call written inside a comment or nested in another literal. That
|
|
76
|
+
* transform blanks literal *contents* and keeps the quotes, so the reason
|
|
77
|
+
* survives only in the raw source — and it is length-preserving, so the same
|
|
78
|
+
* offsets address both. `tests/unit/linter/source-text.test.ts` asserts that
|
|
79
|
+
* alignment directly, since this rule depends on it.
|
|
80
|
+
*
|
|
81
|
+
* The span is carried so a rule that needs a site's arguments — not just which
|
|
82
|
+
* reason it names — can read them without rescanning.
|
|
83
|
+
*/
|
|
84
|
+
export declare function scanReasonCalls(source: string, callee: string): ReasonCallScan;
|
|
85
|
+
/**
|
|
86
|
+
* Flags a declared `errors[]` reason that no code path in the handler can
|
|
87
|
+
* produce — the inverse of {@link lintErrorContractConformance}, which only
|
|
88
|
+
* catches codes thrown but not declared.
|
|
89
|
+
*
|
|
90
|
+
* A dead entry compiles, lints clean, and stays in the contract indefinitely;
|
|
91
|
+
* the typed `ctx.fail` union accepts the reason, so nothing downstream objects.
|
|
92
|
+
* The cost lands on the client, which plans for a failure mode the tool cannot
|
|
93
|
+
* produce while the mode it does produce goes undocumented.
|
|
94
|
+
*
|
|
95
|
+
* **Trigger.** Only when the handler holds at least one literal `ctx.fail(`. A
|
|
96
|
+
* handler with none produces its reasons somewhere the scan cannot reach, and
|
|
97
|
+
* firing there would warn on every service-layer definition. A `ctx.fail(` or
|
|
98
|
+
* `ctx.recoveryFor(` whose first argument is not a string literal makes the
|
|
99
|
+
* named set unknowable, so the whole definition is skipped rather than guessed
|
|
100
|
+
* at.
|
|
101
|
+
*
|
|
102
|
+
* **Warning, never error.** A reason produced outside the handler closure is
|
|
103
|
+
* invisible to any `toString()` scan, so the rule can never prove absence. An
|
|
104
|
+
* entry the service layer produces says so with `thrownBy: 'service'` and is
|
|
105
|
+
* skipped while the handler's own reasons keep being checked. Still silent
|
|
106
|
+
* without a marker: a `createFail(errors)` resolver built outside the handler,
|
|
107
|
+
* and an aliased `const fail = ctx.fail`.
|
|
108
|
+
*/
|
|
109
|
+
export declare function lintErrorContractUnthrown(def: {
|
|
110
|
+
handler?: unknown;
|
|
111
|
+
errors?: unknown;
|
|
112
|
+
}, definitionType: LintDefinitionType, definitionName: string): LintDiagnostic[];
|
|
113
|
+
/**
|
|
114
|
+
* Flags a literal `ctx.fail('<reason>', …)` site that does not put the
|
|
115
|
+
* contract's `recovery` on the wire.
|
|
116
|
+
*
|
|
117
|
+
* An `errors[]` entry must declare `recovery`, but reaching the client with it
|
|
118
|
+
* is opt-in: the throw site forwards `ctx.recoveryFor('<reason>')`, or passes
|
|
119
|
+
* its own `recovery` key. A site that does neither ships `reason` and
|
|
120
|
+
* `retryable` with no hint — and because the framework mirrors
|
|
121
|
+
* `data.recovery.hint` into the error `content[]`, both client surfaces lose it
|
|
122
|
+
* together. The declared guidance is right there in the contract and reaches
|
|
123
|
+
* nobody; an error-path test asserting `code` and `reason` passes either way.
|
|
124
|
+
*
|
|
125
|
+
* **Per site, not per reason.** A handler wiring one of six throws is covered
|
|
126
|
+
* at one of them, so each site is judged on its own argument list. Two sites
|
|
127
|
+
* naming one reason, one forwarding and one bare, produce exactly one
|
|
128
|
+
* diagnostic.
|
|
129
|
+
*
|
|
130
|
+
* **Accepted forms.** `{ ...ctx.recoveryFor('<reason>') }` spread into the data
|
|
131
|
+
* object, `ctx.recoveryFor('<reason>')` passed as the data argument, and an
|
|
132
|
+
* explicit `recovery` key carrying a runtime-interpolated hint.
|
|
133
|
+
*
|
|
134
|
+
* **Bails.** A non-literal first argument on either callee skips the whole
|
|
135
|
+
* definition — the reasons in play are unknown. A resolver sitting outside
|
|
136
|
+
* every fail span (a hoisted `const hint = ctx.recoveryFor('x')`) skips that
|
|
137
|
+
* reason, since the binding is assembled where the scan cannot follow it. A
|
|
138
|
+
* data argument the scan cannot read skips that one site.
|
|
139
|
+
*
|
|
140
|
+
* **Warning, never error.** A failure thrown below the handler is invisible to
|
|
141
|
+
* a `handler.toString()` scan, so the rule speaks only for the sites it sees.
|
|
142
|
+
*/
|
|
143
|
+
export declare function lintErrorContractRecoveryUnforwarded(def: {
|
|
144
|
+
handler?: unknown;
|
|
145
|
+
errors?: unknown;
|
|
146
|
+
}, definitionType: LintDefinitionType, definitionName: string): LintDiagnostic[];
|
|
49
147
|
//# sourceMappingURL=error-contract-rules.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-contract-rules.d.ts","sourceRoot":"","sources":["../../../src/linter/rules/error-contract-rules.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"error-contract-rules.d.ts","sourceRoot":"","sources":["../../../src/linter/rules/error-contract-rules.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAuBtE;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,OAAO,EACf,cAAc,EAAE,kBAAkB,EAClC,cAAc,EAAE,MAAM,GACrB,cAAc,EAAE,CAkMlB;AAyDD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,4BAA4B,CAC1C,GAAG,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,EAC5C,cAAc,EAAE,kBAAkB,EAClC,cAAc,EAAE,MAAM,GACrB,cAAc,EAAE,CAiGlB;AAMD,8EAA8E;AAC9E,MAAM,WAAW,cAAc;IAC7B,sDAAsD;IACtD,GAAG,EAAE,MAAM,CAAC;IACZ,+EAA+E;IAC/E,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC;CACf;AAED,+EAA+E;AAC/E,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,aAAa,EAAE,OAAO,CAAC;IACvB,sDAAsD;IACtD,KAAK,EAAE,cAAc,EAAE,CAAC;CACzB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,cAAc,CA0B9E;AA6DD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,yBAAyB,CACvC,GAAG,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,EAC5C,cAAc,EAAE,kBAAkB,EAClC,cAAc,EAAE,MAAM,GACrB,cAAc,EAAE,CA4BlB;AA4DD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,oCAAoC,CAClD,GAAG,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,EAC5C,cAAc,EAAE,kBAAkB,EAClC,cAAc,EAAE,MAAM,GACrB,cAAc,EAAE,CA4ClB"}
|