---
summary: "Adopt @cyanheads/mcp-ts-core 0.7.5 → 0.8.7: typed error contracts on every tool, service-layer reason carrying via ctx.recoveryFor, httpStatusToErrorCode replaces manual status mapping (fixes 422 → ValidationError, other 4xx → InvalidRequest)"
breaking: false
---

# 0.6.5 — 2026-04-30

Maintenance release picking up nine versions of `@cyanheads/mcp-ts-core` (0.7.5 → 0.8.7), led by 0.8.0's typed error contracts. Every tool now declares a public failure surface; the OpenAlex service routes upstream errors through `httpStatusToErrorCode` and carries the matching `reason` (plus contract `recovery.hint`) onto the wire, so agents can switch on `error.data.reason` instead of parsing message text. No wire-shape regressions in practice — the only contracts we relied on (`_meta.error` from 0.8.3, retired) weren't read by any caller.

## Added

- **Typed error contracts on every tool** — `errors[]` declared on `openalex_search_entities` (7 reasons: `semantic_per_page_cap`, `entity_not_found`, `rate_limited`, `upstream_unauthorized`, `upstream_forbidden`, `upstream_invalid_params`, `upstream_validation_failed`), `openalex_analyze_trends` and `openalex_resolve_name` (5 reasons each, sans the id-lookup-specific entries). Every entry carries a ≥5-word `recovery` string per 0.8.4's lint rule. The `semantic_per_page_cap` handler-direct throw now routes through `ctx.fail(reason, …)` so `data.reason` is auto-populated and the conformance lint can enforce coverage.
- **Service-layer reason carrying** — `OpenAlexService.throwNormalizedRequestError` consults a single `JsonRpcErrorCode → { factory, reason }` map driven by `httpStatusToErrorCode` from `@cyanheads/mcp-ts-core/utils`. Each factory throw now spreads `ctx.recoveryFor(reason)` so the calling tool's contract recovery flows through to `data.recovery.hint` automatically. Same pattern in `parseResponse` for the `upstream_unavailable` reason.

## Changed

- **`OpenAlexService` HTTP-status mapping uses `httpStatusToErrorCode`** — replaces the hand-rolled switch in `src/services/openalex/openalex-service.ts`. Two visible behavior shifts: HTTP 422 now classifies as `ValidationError` (was `InvalidParams`) and other 4xx (e.g. 413, 410) classify as `InvalidRequest` (was `InvalidParams`). Both align with the framework's canonical table and improve `mcp_error_classified_code` observability. Tests updated to match.
- **`CLAUDE.md` / `AGENTS.md`** — Errors section rewritten to lead with typed contracts (factories demoted to fallback). Context table adds `ctx.fail` and `ctx.recoveryFor`. `tenantId` row covers HTTP+`MCP_AUTH_MODE=none` (`'default'` per 0.8.5).
- **Project skills resynced from `@cyanheads/mcp-ts-core`** — `add-service` 1.3 → 1.5, `add-tool` 1.8 → 2.4, `api-context` 1.1 → 1.2, `api-errors` 1.0 → 1.4, `design-mcp-server` 2.7 → 2.8, `field-test` 2.0 → 2.3, `maintenance` 1.6 → 2.0, `release-and-publish` 2.1 → 2.2, `report-issue-framework` 1.3 → 1.4, `security-pass` 1.1 → 1.2, `setup` 1.5 → 1.6.
- **Framework `scripts/` resynced** — added `scripts/split-changelog.ts`; no other scripts changed.

## Removed

- **`dev:stdio` and `dev:http` scripts in `package.json`** — both gone from `@cyanheads/mcp-ts-core` 0.8.6/0.8.7. Use `bun run rebuild && bun run start:stdio` (or `start:http`) for production-shape smoke tests; the `Commands` table in `CLAUDE.md`/`AGENTS.md` was updated to match.

## Maintenance

- `@cyanheads/mcp-ts-core` ^0.7.5 → ^0.8.7. No other dependency changes.
- Field-tested live against OpenAlex: `openalex_resolve_name`, `openalex_search_entities` by DOI, `openalex_analyze_trends` group-by-year, plus both new error paths (`semantic_per_page_cap` via `ctx.fail` and `entity_not_found` via service factory) — wire shape verified clean on `structuredContent.error` and mirrored into `content[]` text per 0.8.3's parity invariant.
