@cyanheads/mcp-ts-core 0.8.0 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # Agent Protocol
2
2
 
3
- **Package:** `@cyanheads/mcp-ts-core` · **Version:** 0.8.0
3
+ **Package:** `@cyanheads/mcp-ts-core` · **Version:** 0.8.1
4
4
  **npm:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) · **Docker:** [ghcr.io/cyanheads/mcp-ts-core](https://ghcr.io/cyanheads/mcp-ts-core)
5
5
 
6
6
  > **Developer note:** Never assume. Read related files and docs before making changes. Read full file content for context. Never edit a file before reading it.
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  <div align="center">
7
7
 
8
- [![Version](https://img.shields.io/badge/Version-0.8.0-blue.svg?style=flat-square)](./CHANGELOG.md) [![MCP Spec](https://img.shields.io/badge/MCP%20Spec-2025--11--25-8A2BE2.svg?style=flat-square)](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-11-25/changelog.mdx) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^1.29.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE)
8
+ [![Version](https://img.shields.io/badge/Version-0.8.1-blue.svg?style=flat-square)](./CHANGELOG.md) [![MCP Spec](https://img.shields.io/badge/MCP%20Spec-2025--11--25-8A2BE2.svg?style=flat-square)](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-11-25/changelog.mdx) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^1.29.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE)
9
9
 
10
10
  [![TypeScript](https://img.shields.io/badge/TypeScript-^6.0.3-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.3.2-blueviolet.svg?style=flat-square)](https://bun.sh/)
11
11
 
@@ -5,27 +5,29 @@ breaking: false
5
5
 
6
6
  # 0.8.0 — 2026-04-28
7
7
 
8
- Minor release introducing **typed error contracts** for tools and resources. Definitions can now declare `errors: [{ reason, code, when, retryable? }]` to advertise their failure surface in `tools/list` (under `_meta['mcp-ts-core/errors']`) and receive a typed `ctx.fail(reason, …)` keyed against the declared reason union TypeScript catches `ctx.fail('typo')` at compile time, the runtime auto-populates `data.reason` for observability, and a startup linter cross-checks the handler body against the contract. Fully backwards compatible: definitions without `errors[]` keep their existing `ctx: Context` signature and direct-throw behavior. Ships alongside two new utility modules (`httpErrorFromResponse`, `partialResult`) and three more error factories.
8
+ Typed error contracts for tools and resources. Backwards compatible — definitions without `errors[]` keep the existing `Context` signature.
9
9
 
10
10
  ## Added
11
11
 
12
- - **Typed error contracts on `tool()` / `resource()` / `appTool()` / `appResource()`** — new optional `errors: [{ code, reason, when, retryable? }]` field declares the public failure surface. The const-tuple shape preserves literal reasons through to the handler signature: `ctx.fail` is typed as `TypedFail<R>` where `R` is the union of declared `reason` strings, and `HandlerContext<ReasonOf<TErrors>>` collapses to plain `Context` when no contract is declared. Runtime `ctx.fail` constructs an `McpError` with the contract's code, the caller's message (or the `when` text as a fallback), and `data.reason` auto-populated from the contract — caller-supplied `data.reason` cannot override it, preserving `data.reason` as a stable observability identifier. New exports: `TypedFail`, `ReasonOf`, `HandlerContext`, `createFail`, `attachTypedFail` (internal) on `@cyanheads/mcp-ts-core`; `ErrorContract`, `ERROR_CONTRACT_META_KEY`, `buildMetaWithErrorContract` (internal) on `/errors`. Contracts surface in `tools/list` and `resources/list` under `_meta['mcp-ts-core/errors']`. The `appTool` / `appResource` builders forward the third type parameter so MCP App tools get the same compile-time guarantees as standard tools. The auto-task path in `tool-registration.ts` mirrors the standard handler factory's ctx construction so auto-task handlers calling `ctx.fail(...)` work — without `attachTypedFail` they would crash with `ctx.fail is not a function` when the contract is declared.
13
- - **`httpErrorFromResponse(response, opts?)` and `httpStatusToErrorCode(status)` in `/utils`** — replaces hand-rolled status→code ladders that consumer servers tend to write (and get wrong, especially for 401/403/408/422). Maps the full 4xx/5xx range with specific handling for auth failures, conflicts, validation, rate limits, and timeouts — see the table in the file's JSDoc. Captures the response body (truncated to 500 bytes by default) and `Retry-After` header into `error.data`, supports `cause` chaining, accepts a `service` name for the message subject and a `codeOverride` for upstream-specific quirks. New exports: `httpErrorFromResponse`, `httpStatusToErrorCode`, `HttpErrorFromResponseOptions`.
14
- - **`partialResult` / `partialResultSchema` / `failureEntrySchema` in `/utils`** — first-class helpers for the "partial success" tool-output pattern (a tool that processes N items and returns the ones that succeeded plus a structured list of the ones that failed, each with a stable `reason` enum). `failureEntrySchema` builds a `{ [idKey]: string; reason: TReason; detail?: string }` Zod object; `partialResultSchema` composes that with a typed succeeded array and totals; `partialResult` constructs the runtime object with the failed array conditionally omitted when empty so `structuredContent` stays clean. Generic over the success/failure key names so the surface mirrors the existing public API of any tool (no forced rename to `succeeded` / `failed`). New exports: `partialResult`, `partialResultSchema`, `failureEntrySchema`, `PartialResultObject`.
15
- - **Three more error factories: `internalError`, `serializationError`, `databaseError`** — fills the gaps in the factory set so handlers don't fall back to `new McpError(JsonRpcErrorCode.X, …)` for these common codes. All accept the standard `(message, data?, options?)` signature with `cause` chaining support.
16
- - **Handler-body lint rules** (`src/linter/rules/handler-body-rules.ts`) heuristic source-text scan that flags four common error-handling anti-patterns in tool and resource handlers: `prefer-mcp-error-in-handler` (plain `throw new Error(...)` should use `McpError` or a factory), `prefer-error-factory` (`new McpError(JsonRpcErrorCode.NotFound, …)` should use `notFound(…)`), `preserve-cause-on-rethrow` (`catch (e) { throw notFound(...) }` without `{ cause: e }` loses the chain), `no-stringify-upstream-error` (throwing a message containing `JSON.stringify(...)` risks leaking raw upstream traces — sanitize first or attach to `data`). All warnings, all surfaced via `bun run devcheck`, all hit at most once per definition to avoid noisy reports.
17
- - **Error-contract conformance lints** (`src/linter/rules/error-contract-rules.ts`) — when a definition declares `errors[]`, the linter validates structural invariants (`errors` is an array, each entry has `{ code, reason, when }`, `code` is a real `JsonRpcErrorCode`, `reason` is snake_case and unique) plus two cross-checks against the handler body: `error-contract-conformance` flags codes thrown from the handler that aren't declared (suggests adding them), `error-contract-prefer-fail` flags codes that ARE declared but get thrown via factory or `new McpError` instead of `ctx.fail(reason, …)`. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`) bubble from anywhere and are auto-allowed by conformance, modeled after how OpenAPI-driven frameworks treat 5xx — implicit, not required to enumerate per-endpoint. Source-text scan only; codes thrown from called services are invisible (still classify correctly at runtime via the auto-classifier, just without lint enforcement).
18
- - **`createMockContext({ errors })` test option** pass the definition's own `errors` array (`createMockContext({ errors: myTool.errors })`) to attach a typed `ctx.fail` against the contract's reasons. Tests can then assert on `data.reason` without manually composing `createFail`. Mirrors the production handler factory's `attachTypedFail` wiring.
19
- - **Tests** `tests/unit/core/typed-fail.test.ts`, `tests/unit/linter/error-contract-rules.test.ts`, `tests/unit/linter/handler-body-rules.test.ts`, `tests/unit/mcp-server/tools/typed-error-contract.test.ts`, `tests/unit/utils/network/httpError.test.ts`, `tests/unit/utils/formatting/partialResult.test.ts`, plus expansion of `tests/unit/mcp-server/tools/tool-registration.lifecycle.test.ts` and `tests/unit/mcp-server/apps/appBuilders.test.ts` for typed-fail propagation through the auto-task and app-tool paths.
12
+ - **`errors: [{ reason, code, when, retryable? }]`** on `tool()` / `resource()` / `appTool()` / `appResource()`. Surfaces in `tools/list` and `resources/list` under `_meta['mcp-ts-core/errors']`. Const-tuple inference flows the reason union into the handler's `ctx.fail`.
13
+ - **`ctx.fail(reason, msg?, data?, opts?)`** — typed `TypedFail<R>` keyed against the contract's reasons. `ctx.fail('typo')` is a TS error. Constructs an `McpError` with the contract's code, message (defaults to `when`), and `data.reason` auto-populated from the contract caller-supplied `data.reason` cannot override it.
14
+ - **New core exports:** `TypedFail`, `ReasonOf`, `HandlerContext`, `createFail`. **New `/errors` exports:** `ErrorContract`, `ERROR_CONTRACT_META_KEY`, `buildMetaWithErrorContract`.
15
+ - **`httpErrorFromResponse(response, opts?)` and `httpStatusToErrorCode(status)` in `/utils`** — full 4xx/5xx `JsonRpcErrorCode` table (401/403/408/422/429/5xx). Captures truncated body and `Retry-After` into `error.data`. Supports `service`, `cause`, `codeOverride`. Exports `HttpErrorFromResponseOptions`.
16
+ - **`partialResult` / `partialResultSchema` / `failureEntrySchema` in `/utils`** typed succeeded/failed pattern with stable reason enums. Generic over key names. `failed[]` is omitted when empty. Exports `PartialResultObject`.
17
+ - **Three more factories on `/errors`:** `internalError`, `serializationError`, `databaseError`. Same `(message, data?, options?)` signature as the rest.
18
+ - **Handler-body lints** (warnings, surfaced in devcheck): `prefer-mcp-error-in-handler`, `prefer-error-factory`, `preserve-cause-on-rethrow`, `no-stringify-upstream-error`.
19
+ - **Contract lints** (warnings): structural validation (`error-contract-{type,empty,entry-type,code-type,code-unknown,reason-required,reason-format,reason-unique,when-required,retryable-type}`); conformance — `error-contract-conformance` flags undeclared codes thrown from the handler, `error-contract-prefer-fail` flags declared codes thrown directly instead of via `ctx.fail`. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`) auto-allowed.
20
+ - **`createMockContext({ errors })`** — attaches `ctx.fail` against a contract for tests.
21
+ - **Tests** for typed-fail propagation, the two new lint families, `httpErrorFromResponse`, `partialResult`, and the auto-task / app-tool integration paths.
20
22
 
21
23
  ## Changed
22
24
 
23
- - **`AGENTS.md` / `CLAUDE.md` / `templates/AGENTS.md` / `templates/CLAUDE.md` Error Handling sections rewritten** — leads with the typed error contract as the recommended path (with concrete `errors[]` + `ctx.fail` example), demotes plain-throw + factory usage to the fallback for ad-hoc cases, documents baseline-code auto-allow, references the new conformance and handler-body lint diagnostics, and links to `httpErrorFromResponse` for upstream API mapping. Exports table updated to include `createFail`, `TypedFail`, `ReasonOf`, `HandlerContext`. Available factory list expanded to include `internalError`, `serializationError`, `databaseError`.
24
- - **`README.md` "Structured errors" feature bullet rewritten** to lead with typed contracts (the headline 0.8.0 feature) and demote factories + auto-classification to the fallback path. Mirrors the framing in `AGENTS.md` / `CLAUDE.md`.
25
- - **Skills sync** `add-tool`, `add-resource`, `add-service`, `add-test`, `add-app-tool`, `api-context`, `api-errors`, `api-linter`, `api-testing`, `api-utils`, `design-mcp-server`, `field-test` updated to cover the typed-contract API surface, the new utilities, the new lint rules, and the expanded factory set. `maintenance` v1.7 → v1.8 (Step 4 adoption-check row broadened from "new error factories" to "new `/errors` surface — factories, typed contracts, `httpErrorFromResponse`"). `security-pass` v1.1 → v1.2 (Axis 7 leakage check now greps `ctx.fail(` and `httpErrorFromResponse(` alongside `new McpError`, since all three flow through the same `data` payload). `report-issue-framework` v1.3 → v1.4 (`errors` scope row expanded to mention typed contracts, conformance lint, and `httpErrorFromResponse`).
26
- - **`ToolDefinition` / `ResourceDefinition` gain a third type parameter** `TErrors extends readonly ErrorContract[] | undefined = undefined` — defaults to `undefined`, so existing `ToolDefinition<X, Y>` and `ResourceDefinition<X, Y, Z>` annotations continue to work and the handler signature collapses to plain `Context`. `AnyToolDefinition` and `AnyResourceDefinition` are widened to `readonly ErrorContract[] | undefined` for the new parameter so type-erased arrays passed to `createApp()` still accept mixed contracts.
27
- - **`docs/tree.md`** regenerated to reflect new files (`error-contract-rules.ts`, `handler-body-rules.ts`, `source-text.ts`, `partialResult.ts`, `httpError.ts`, `check-framework-antipatterns.ts`) and corresponding test files.
25
+ - **`ToolDefinition` / `ResourceDefinition`** gain a third type parameter `TErrors extends readonly ErrorContract[] | undefined = undefined`. Existing `ToolDefinition<X, Y>` annotations keep working; handler signature collapses to plain `Context` when no contract is declared. `AnyToolDefinition` / `AnyResourceDefinition` widened accordingly.
26
+ - **`AGENTS.md` / `CLAUDE.md` / `templates/AGENTS.md` / `templates/CLAUDE.md`** — Error Handling sections rewritten to lead with the contract path, factories demoted to fallback. Exports table and factory list updated.
27
+ - **`README.md`**"Structured errors" feature bullet rewritten as "Typed error contracts".
28
+ - **Skills sync** `add-tool`, `add-resource`, `add-service`, `add-test`, `add-app-tool`, `api-context`, `api-errors`, `api-linter`, `api-testing`, `api-utils`, `design-mcp-server`, `field-test` updated for the new surface. `maintenance` v1.7 v1.8, `security-pass` v1.1 v1.2, `report-issue-framework` v1.3 v1.4.
29
+ - **`docs/tree.md`** regenerated.
28
30
 
29
31
  ## Fixed
30
32
 
31
- - **Stale `@see docs/service-resilience.md` reference removed** from `src/utils/network/retry.ts` — the linked doc no longer exists in the repo, the JSDoc above already explains the retry boundary semantics.
33
+ - Stale `@see docs/service-resilience.md` reference removed from `src/utils/network/retry.ts`.
@@ -0,0 +1,17 @@
1
+ ---
2
+ summary: "Skills sync — service-thrown contract reasons (pass `data: { reason }` from factories), field-test helper hardening, maintenance skill-version paradox check, factory-choice semantic audit"
3
+ breaking: false
4
+ ---
5
+
6
+ # 0.8.1 — 2026-04-28
7
+
8
+ Skill-only follow-up to 0.8.0. Documents the missing pattern for carrying contract `reason` from service-layer throws (services don't have `ctx.fail`), hardens the field-test helper, and teaches `maintenance` to self-detect a skill-version paradox.
9
+
10
+ ## Changed
11
+
12
+ - **`api-errors` v1.0 → v1.1** — adds "Carrying contract `reason` from services" section. Services pass `data: { reason: 'X' }` to error factories; the auto-classifier preserves `data` on the wire so clients see the same `error.data.reason` they'd see from `ctx.fail`. Trade-off noted: lint-time conformance enforcement is lost; compensate with one wire-shape test per reason.
13
+ - **`add-service` v1.3 → v1.4** — same pattern surfaced as a "Carry contract `reason` via `data: { reason }`" bullet under the "Services don't declare contracts" rule.
14
+ - **`add-tool` v1.8 → v1.9** — cross-references the service-thrown reason path in the `ctx.fail` section, so handler authors see it where they're already reading.
15
+ - **`field-test` v2.0 → v2.1** — `/tmp/mcp-field-test.sh` helper hardened: build/start failures auto-tail their logs; `mcp_init` captures HTTP status + body on failure; `mcp_call` surfaces 4xx/5xx with body + falls through to raw body when SSE framing is absent (so plain-JSON errors aren't swallowed); new `mcp_log [N]` tails server log for surprising responses; `mcp_stop` waits for SIGTERM, escalates to SIGKILL, reports residual processes.
16
+ - **`maintenance` v1.8 → v1.9** — adds skill-version paradox check: if `node_modules/@cyanheads/mcp-ts-core/skills/maintenance/SKILL.md` version exceeds the running one, run Phase A first and re-invoke. Also adds factory-choice semantic audit row: `invalidParams` is for malformed JSON-RPC params (rare post-Zod); semantic post-shape validation should use `validationError`. Wrong codes degrade `mcp_error_classified_code` observability and break client retry logic.
17
+ - **`templates/changelog/template.md`** — example issue/PR numbers updated from placeholder `#12`/`#17` to concrete `#38`/`#42` against `cyanheads/mcp-ts-core` so the linking guidance reads correctly when copied.
@@ -27,6 +27,19 @@ 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.
@@ -1,4 +1,4 @@
1
- {"level":50,"time":1777411375566,"env":"testing","version":"0.0.0-test","pid":21464,"requestId":"AWQQL-93X1J","timestamp":"2026-04-28T21:22:55.565Z","operation":"HandleToolRequest","critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"d9659eae41503420a9fbe1a137386f211e1880fefe827828f600907f56f92f0e","toolName":"scoped_echo","tenantId":"authz-tenant","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant"},"errorData":{"sessionId":"d9659eae41503420a9fbe1a137386f211e1880fefe827828f600907f56f92f0e","toolName":"scoped_echo","requestId":"AWQQL-93X1J","timestamp":"2026-04-28T21:22:55.565Z","tenantId":"authz-tenant","operation":"HandleToolRequest","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant"},"originalErrorName":"McpError","originalMessage":"Insufficient permissions.","originalStack":"McpError: Insufficient permissions.\n at forbidden (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:84:58)\n at withRequiredScopes (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/authUtils.js:61:15)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:72:17)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Insufficient permissions.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:107:42)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)","msg":"Error in tool:scoped_echo: Insufficient permissions."}
2
- {"level":50,"time":1777411376971,"env":"testing","version":"0.8.0","pid":21504,"requestId":"T5AF5-CUWA1","timestamp":"2026-04-28T21:22:56.971Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"T5AF5-CUWA1","timestamp":"2026-04-28T21:22:56.971Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Missing or invalid Authorization header. Bearer scheme required.","originalStack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at authMiddleware (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/authMiddleware.js:64:19)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpTransport.js:232:22)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at cors2 (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/middleware/cors/index.js:82:11)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Missing or invalid Authorization header. Bearer scheme required."}
3
- {"level":50,"time":1777411376988,"env":"testing","version":"0.8.0","pid":21504,"requestId":"AYH25-LS56D","timestamp":"2026-04-28T21:22:56.988Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"AYH25-LS56D","timestamp":"2026-04-28T21:22:56.988Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Token has expired.","originalStack":"McpError: Token has expired.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at handleJoseVerifyError (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/claimParser.js:56:11)\n at verify (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/strategies/jwtStrategy.js:91:13)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Token has expired.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Token has expired."}
4
- {"level":50,"time":1777411376991,"env":"testing","version":"0.8.0","pid":21504,"requestId":"0BLY1-FMRXN","timestamp":"2026-04-28T21:22:56.991Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"GET","errorData":{"path":"/mcp","method":"GET","requestId":"0BLY1-FMRXN","timestamp":"2026-04-28T21:22:56.991Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Missing or invalid Authorization header. Bearer scheme required.","originalStack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at authMiddleware (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/authMiddleware.js:64:19)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpTransport.js:232:22)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at cors2 (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/middleware/cors/index.js:82:11)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Missing or invalid Authorization header. Bearer scheme required."}
1
+ {"level":50,"time":1777415595855,"env":"testing","version":"0.0.0-test","pid":99019,"requestId":"63RGD-YP65A","timestamp":"2026-04-28T22:33:15.853Z","operation":"HandleToolRequest","critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"5a13b0091a5f90f81e77c9eb13f93c113b034065cf9fae82c794997d56c9cf18","toolName":"scoped_echo","tenantId":"authz-tenant","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant"},"errorData":{"sessionId":"5a13b0091a5f90f81e77c9eb13f93c113b034065cf9fae82c794997d56c9cf18","toolName":"scoped_echo","requestId":"63RGD-YP65A","timestamp":"2026-04-28T22:33:15.853Z","tenantId":"authz-tenant","operation":"HandleToolRequest","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant"},"originalErrorName":"McpError","originalMessage":"Insufficient permissions.","originalStack":"McpError: Insufficient permissions.\n at forbidden (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:84:58)\n at withRequiredScopes (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/authUtils.js:61:15)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:72:17)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Insufficient permissions.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:107:42)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)","msg":"Error in tool:scoped_echo: Insufficient permissions."}
2
+ {"level":50,"time":1777415596525,"env":"testing","version":"0.8.1","pid":99023,"requestId":"SJ7A2-YIFMY","timestamp":"2026-04-28T22:33:16.524Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"SJ7A2-YIFMY","timestamp":"2026-04-28T22:33:16.524Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Missing or invalid Authorization header. Bearer scheme required.","originalStack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at authMiddleware (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/authMiddleware.js:64:19)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpTransport.js:232:22)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at cors2 (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/middleware/cors/index.js:82:11)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Missing or invalid Authorization header. Bearer scheme required."}
3
+ {"level":50,"time":1777415596539,"env":"testing","version":"0.8.1","pid":99023,"requestId":"OO6W9-ZL8EV","timestamp":"2026-04-28T22:33:16.539Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"OO6W9-ZL8EV","timestamp":"2026-04-28T22:33:16.539Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Token has expired.","originalStack":"McpError: Token has expired.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at handleJoseVerifyError (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/claimParser.js:56:11)\n at verify (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/strategies/jwtStrategy.js:91:13)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Token has expired.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Token has expired."}
4
+ {"level":50,"time":1777415596544,"env":"testing","version":"0.8.1","pid":99023,"requestId":"3NQXB-GTIJC","timestamp":"2026-04-28T22:33:16.544Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"GET","errorData":{"path":"/mcp","method":"GET","requestId":"3NQXB-GTIJC","timestamp":"2026-04-28T22:33:16.544Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Missing or invalid Authorization header. Bearer scheme required.","originalStack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at authMiddleware (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/authMiddleware.js:64:19)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpTransport.js:232:22)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at cors2 (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/middleware/cors/index.js:82:11)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Missing or invalid Authorization header. Bearer scheme required."}
@@ -1,4 +1,4 @@
1
- {"level":50,"time":1777411375566,"env":"testing","version":"0.0.0-test","pid":21464,"requestId":"AWQQL-93X1J","timestamp":"2026-04-28T21:22:55.565Z","operation":"HandleToolRequest","critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"d9659eae41503420a9fbe1a137386f211e1880fefe827828f600907f56f92f0e","toolName":"scoped_echo","tenantId":"authz-tenant","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant"},"errorData":{"sessionId":"d9659eae41503420a9fbe1a137386f211e1880fefe827828f600907f56f92f0e","toolName":"scoped_echo","requestId":"AWQQL-93X1J","timestamp":"2026-04-28T21:22:55.565Z","tenantId":"authz-tenant","operation":"HandleToolRequest","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant"},"originalErrorName":"McpError","originalMessage":"Insufficient permissions.","originalStack":"McpError: Insufficient permissions.\n at forbidden (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:84:58)\n at withRequiredScopes (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/authUtils.js:61:15)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:72:17)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Insufficient permissions.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:107:42)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)","msg":"Error in tool:scoped_echo: Insufficient permissions."}
2
- {"level":50,"time":1777411376971,"env":"testing","version":"0.8.0","pid":21504,"requestId":"T5AF5-CUWA1","timestamp":"2026-04-28T21:22:56.971Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"T5AF5-CUWA1","timestamp":"2026-04-28T21:22:56.971Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Missing or invalid Authorization header. Bearer scheme required.","originalStack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at authMiddleware (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/authMiddleware.js:64:19)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpTransport.js:232:22)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at cors2 (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/middleware/cors/index.js:82:11)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Missing or invalid Authorization header. Bearer scheme required."}
3
- {"level":50,"time":1777411376988,"env":"testing","version":"0.8.0","pid":21504,"requestId":"AYH25-LS56D","timestamp":"2026-04-28T21:22:56.988Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"AYH25-LS56D","timestamp":"2026-04-28T21:22:56.988Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Token has expired.","originalStack":"McpError: Token has expired.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at handleJoseVerifyError (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/claimParser.js:56:11)\n at verify (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/strategies/jwtStrategy.js:91:13)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Token has expired.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Token has expired."}
4
- {"level":50,"time":1777411376991,"env":"testing","version":"0.8.0","pid":21504,"requestId":"0BLY1-FMRXN","timestamp":"2026-04-28T21:22:56.991Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"GET","errorData":{"path":"/mcp","method":"GET","requestId":"0BLY1-FMRXN","timestamp":"2026-04-28T21:22:56.991Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Missing or invalid Authorization header. Bearer scheme required.","originalStack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at authMiddleware (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/authMiddleware.js:64:19)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpTransport.js:232:22)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at cors2 (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/middleware/cors/index.js:82:11)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Missing or invalid Authorization header. Bearer scheme required."}
1
+ {"level":50,"time":1777415595855,"env":"testing","version":"0.0.0-test","pid":99019,"requestId":"63RGD-YP65A","timestamp":"2026-04-28T22:33:15.853Z","operation":"HandleToolRequest","critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"5a13b0091a5f90f81e77c9eb13f93c113b034065cf9fae82c794997d56c9cf18","toolName":"scoped_echo","tenantId":"authz-tenant","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant"},"errorData":{"sessionId":"5a13b0091a5f90f81e77c9eb13f93c113b034065cf9fae82c794997d56c9cf18","toolName":"scoped_echo","requestId":"63RGD-YP65A","timestamp":"2026-04-28T22:33:15.853Z","tenantId":"authz-tenant","operation":"HandleToolRequest","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant"},"originalErrorName":"McpError","originalMessage":"Insufficient permissions.","originalStack":"McpError: Insufficient permissions.\n at forbidden (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:84:58)\n at withRequiredScopes (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/authUtils.js:61:15)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:72:17)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Insufficient permissions.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:107:42)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)","msg":"Error in tool:scoped_echo: Insufficient permissions."}
2
+ {"level":50,"time":1777415596525,"env":"testing","version":"0.8.1","pid":99023,"requestId":"SJ7A2-YIFMY","timestamp":"2026-04-28T22:33:16.524Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"SJ7A2-YIFMY","timestamp":"2026-04-28T22:33:16.524Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Missing or invalid Authorization header. Bearer scheme required.","originalStack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at authMiddleware (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/authMiddleware.js:64:19)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpTransport.js:232:22)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at cors2 (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/middleware/cors/index.js:82:11)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Missing or invalid Authorization header. Bearer scheme required."}
3
+ {"level":50,"time":1777415596539,"env":"testing","version":"0.8.1","pid":99023,"requestId":"OO6W9-ZL8EV","timestamp":"2026-04-28T22:33:16.539Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"POST","errorData":{"path":"/mcp","method":"POST","requestId":"OO6W9-ZL8EV","timestamp":"2026-04-28T22:33:16.539Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Token has expired.","originalStack":"McpError: Token has expired.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at handleJoseVerifyError (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/claimParser.js:56:11)\n at verify (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/strategies/jwtStrategy.js:91:13)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Token has expired.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Token has expired."}
4
+ {"level":50,"time":1777415596544,"env":"testing","version":"0.8.1","pid":99023,"requestId":"3NQXB-GTIJC","timestamp":"2026-04-28T22:33:16.544Z","operation":"httpErrorHandler","critical":false,"errorCode":-32006,"originalErrorType":"McpError","finalErrorType":"McpError","path":"/mcp","method":"GET","errorData":{"path":"/mcp","method":"GET","requestId":"3NQXB-GTIJC","timestamp":"2026-04-28T22:33:16.544Z","operation":"httpErrorHandler","originalErrorName":"McpError","originalMessage":"Missing or invalid Authorization header. Bearer scheme required.","originalStack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at unauthorized (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:86:61)\n at authMiddleware (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/authMiddleware.js:64:19)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpTransport.js:232:22)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:22:23)\n at cors2 (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/middleware/cors/index.js:82:11)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Missing or invalid Authorization header. Bearer scheme required.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:169:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/http/httpErrorHandler.js:59:39)\n at dispatch (/Users/casey/Developer/github/mcp-ts-core/node_modules/hono/dist/compose.js:26:25)\n at processTicksAndRejections (native:7:39)","msg":"Error in httpTransport: Missing or invalid Authorization header. Bearer scheme required."}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyanheads/mcp-ts-core",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "mcpName": "io.github.cyanheads/mcp-ts-core",
5
5
  "description": "Agent-native TypeScript framework for building MCP servers. Declarative definitions with auth, multi-backend storage, OpenTelemetry, and first-class support for Bun/Node/Cloudflare Workers.",
6
6
  "main": "dist/core/index.js",
@@ -4,7 +4,7 @@ description: >
4
4
  Scaffold a new service integration. Use when the user asks to add a service, integrate an external API, or create a reusable domain module with its own initialization and state.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.3"
7
+ version: "1.4"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -234,6 +234,12 @@ Services don't declare `errors: [...]` contracts and don't have `ctx.fail` — t
234
234
  ```
235
235
 
236
236
  - **Tool/resource handlers bubble service errors unchanged** — the contract advertises the *advertised* failure surface, and any code thrown from a service still reaches the client correctly via the auto-classifier. The conformance lint scans handler source text only, so service-thrown codes aren't flagged.
237
+ - **Carry contract `reason` via `data: { reason }`** when the calling tool declares an `errors[]` contract entry for this failure mode. Services can't call `ctx.fail`, but passing the reason in `data` flows through the auto-classifier untouched, so clients see the same `error.data.reason` they'd see from `ctx.fail` — no handler-side catch-and-rethrow needed:
238
+
239
+ ```ts
240
+ // tool declares: errors: [{ reason: 'empty_expression', code: JsonRpcErrorCode.ValidationError, when: '…' }]
241
+ throw validationError('Expression cannot be empty.', { reason: 'empty_expression' });
242
+ ```
237
243
 
238
244
  ## API Efficiency
239
245
 
@@ -4,7 +4,7 @@ description: >
4
4
  Scaffold a new MCP tool definition. Use when the user asks to add a tool, create a new tool, or implement a new capability for the server.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.8"
7
+ version: "1.9"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -269,6 +269,8 @@ export const fetchArticles = tool('fetch_articles', {
269
269
 
270
270
  `ctx.fail` accepts an optional 4th `options` argument for ES2022 cause chaining: `throw ctx.fail('upstream_error', 'Upstream returned 500', { url }, { cause: e })`.
271
271
 
272
+ **Service-thrown contract reasons.** When the throw happens in a called service rather than the handler itself, `ctx.fail` isn't reachable — services don't have `ctx`. Pass `data: { reason: 'X' }` to the factory in the service; the framework's auto-classifier preserves `data` on the wire, so the contract reason rides through unchanged. The handler bubbles the error without catching. See `add-service` for the pattern.
273
+
272
274
  **Fallback: error factories.** Use when no contract entry fits — ad-hoc throws, prototype tools, or service-layer code. The framework also auto-classifies plain `throw new Error()` from message patterns as a last resort.
273
275
 
274
276
  ```typescript
@@ -4,7 +4,7 @@ description: >
4
4
  McpError constructor, JsonRpcErrorCode reference, and error handling patterns for `@cyanheads/mcp-ts-core`. Use when looking up error codes, understanding where errors should be thrown vs. caught, or using ErrorHandler.tryCatch in services.
5
5
  metadata:
6
6
  author: cyanheads
7
- version: "1.0"
7
+ version: "1.1"
8
8
  audience: external
9
9
  type: reference
10
10
  ---
@@ -69,6 +69,26 @@ export const fetchTool = tool('fetch_articles', {
69
69
 
70
70
  > **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
71
 
72
+ ### Carrying contract `reason` from services
73
+
74
+ Services don't have `ctx`, so they can't call `ctx.fail`. To make a service-thrown failure carry the contract's `reason` on the wire, **pass `data: { reason: 'X' }` to the factory**. The framework's auto-classifier preserves `data` unchanged, so clients see the same `error.data.reason` they'd see from `ctx.fail`:
75
+
76
+ ```ts
77
+ // my-service.ts
78
+ throw validationError('Expression cannot be empty.', { reason: 'empty_expression' });
79
+ throw serviceUnavailable('Upstream timeout', { reason: 'evaluation_timeout' });
80
+ ```
81
+
82
+ ```ts
83
+ // my-tool.tool.ts
84
+ errors: [
85
+ { reason: 'empty_expression', code: JsonRpcErrorCode.ValidationError, when: 'Input is empty.' },
86
+ { reason: 'evaluation_timeout', code: JsonRpcErrorCode.ServiceUnavailable, when: 'Upstream exceeded the configured timeout.' },
87
+ ]
88
+ ```
89
+
90
+ The handler doesn't catch and re-throw — letting service errors bubble unchanged keeps "logic throws, framework catches" intact. The contract still publishes in `tools/list`, 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
+
72
92
  ---
73
93
 
74
94
  ## Error Factories (fallback)
@@ -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.0"
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
- || { echo "BUILD FAILED — see /tmp/mcp-build.log"; return 1; }
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 — see /tmp/mcp-server.log"
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
- curl -sS -D "$hdr" -X POST "$MCP_URL" \
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.0"}}}' >/dev/null
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" ] && { echo "no session id returned"; return 1; }
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 (SSE framing stripped). Pipe to `jq`.
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
- curl -sS -X POST "$MCP_URL" \
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" | sed -n 's/^data: //p'
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
- [ -n "$MCP_PID" ] && kill "$MCP_PID" 2>/dev/null
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
 
@@ -190,6 +248,8 @@ Use `TaskCreate` — one task per definition. Mark complete as you go. Don't bat
190
248
 
191
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).
192
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
+
193
253
  **Interpreting responses**
194
254
 
195
255
  - Tool domain errors return `{result: {content: [...], isError: true}}` — they live in `result`, not `error`. Check `isError`, not the JSON-RPC error field.
@@ -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.8"
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:
@@ -66,6 +68,7 @@ Scan specifically for:
66
68
  | Area | Adoption Check |
67
69
  |:-----|:---------------|
68
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. |
@@ -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.1.x/0.1.0.md)
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
- [#12](https://github.com/<owner>/<repo>/issues/12) ← issue
35
- [#17](https://github.com/<owner>/<repo>/pull/17) ← PR
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 — `#17` for "my
39
- upcoming PR" will quietly resolve to whatever real item already owns 17,
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