Skip to content

cpsc recalls mcp server changelog

io.github.cyanheads/cpsc-recalls-mcp-server

7 changes recorded. MCP Harbor re-reads this listing from the official registry every six hours and asks the live server what it exposes, and keeps what changed between one look and the next.

  • AGENTS.md +27 −93

    https://raw.githubusercontent.com/cyanheads/cpsc-recalls-mcp-server/HEAD/AGENTS.md

    − **Version:** 0.2.2− **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.14`− **MCP SDK:** `@modelcontextprotocol/server` ^2.2.0 (protocol revisions 2026-07-28 and 2025-*)− **Zod:** ^4.6.5− ### Tool− ```ts− import { tool, z } from '@cyanheads/mcp-ts-core';− export const searchItems = tool('search_items', {−   description: 'Search inventory items by query.',−   annotations: { readOnlyHint: true },−   input: z.object({−     query: z.string().describe('Search terms'),−     limit: z.number().int().min(1).max(100).default(10).describe('Max results (1–100)'),−   }),−   output: z.object({−     items: z.array(z.object({−       id: z.string().describe('Item ID'),−       name: z.string().describe('Item name'),−     })).describe('Matching items'),−   }),−   auth: ['inventory:read'],−   async handler(input, ctx) {−     const items = await findItems(input.query, input.limit);−     ctx.log.info('Search completed', { query: input.query, count: items.length });−     return { items };−   },−   // format() populates content[] — the markdown twin of structuredContent.−   // Different clients read different surfaces (Claude Code → structuredContent,−   // Claude Desktop → content[]); both must carry the same data.−   // Enforced at lint time: every field in `output` must appear in the rendered text.−   format: (result) => [{−     type: 'text',−     text: result.items.map(i => `**${i.id}**: ${i.name}`).join('\n'),−   }],− });− ```− ### Resource− ```ts− import { resource, z } from '@cyanheads/mcp-ts-core';− import { notFound } from '@cyanheads/mcp-ts-core/errors';+ **Version:** 0.3.1+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.18`+ **MCP SDK:** `@modelcontextprotocol/server` ^2.4.0 (protocol revisions 2026-07-28 and 2025-*)+ **Zod:** ^4.6.7+ The three registered tools live in `src/mcp-server/tools/definitions/`:+ - `cpsc-search-recalls.tool.ts` applies text/date filters and offset pagination; its record renderer also supplies the byte-budget calculation.+ - `cpsc-get-recall.tool.ts` expands a recall number into the full record and distinguishes quoted CPSC narrative from server guidance.+ - `cpsc-get-recent.tool.ts` browses a date window with the same pagination and response-budget contract as search.+ All three declare typed error contracts and use `getCpscRecallService()`. The service normalizes CPSC text once before returning records. There are no prompts or server-specific environment variables, and no resource definitions: the only resource is the published skill `skill://cpsc-recalls-guide/SKILL.md`, which the framework serves from root `skills/` (`createApp({ skills: 'skills' })` in `src/index.ts`).+ `skills/cpsc-recalls-guide/SKILL.md` carries what the tool descriptions don't: search-formulation gates (one empty search is not "not recalled"), recall-number and identifier quirks, and the jurisdiction edges. Edit it with the `add-skill` skill, and re-check its gates against live tool behavior with `field-test`.+       response-budget.ts                # List tools' 64,000-byte page budget, 10,000 limit ceiling, short-page notices+       cpsc-recall-service.ts            # CPSC API client (50 s per-call deadline, exact recall-number match), init/accessor+ **CPSC prose formatting:** retain `src/mcp-server/tools/markdown-escape.ts` for plain CPSC text, where the framework's CommonMark inline escaper would put a backslash inside an autolinked address. An address GFM autolinks stays bare only when it holds nothing to escape and no escaped `<` follows it; otherwise it renders as `markdownLink(span, href)` over GFM's own link span (href = the span, or `http://` + span for `www.`), with the characters GFM trims from its end escaped after the link (`? ! + Two skill trees live at the project root, written for different readers:+ | Tree | For | Written by |+ |:-----|:----|:-----------|+ | `framework-skills/` | The agent developing this server — framework skills (`metadata.audience: external`) plus any development-time skills of the server's own | `init` and the `maintenance` sync (framework skills); you (your own) |+ | `skills/` | The agents that install the server and call its tools — tool chains, identifier sources, gates | You, deliberately, with the `add-skill` skill. This server publishes one: `cpsc-recalls-guide` |+ Development skills are modular instructions. Read them directly when a task matches — e.g., `framework-skills/add-tool/SKILL.md` when adding a tool. `bun run list-skills` prints the full registry. Their directory is deliberately not `skills/`: Claude Code and Codex auto-load a plugin's root `skills/`, so a server that ships `.claude-plugin/` or `.codex-plugin/` would hand them to every agent that installs it.+ A published skill in root `skills/` reaches plugin installs and the `.mcpb` bundle as-is, ships in the npm package and the Docker image, and reaches MCP clients when the server opts in with `createApp({ skills: 'skills' })`. `lint:packaging` validates the tree.+ **Agent skill directory:** Copy `framework-skills/` into the directory your agent discovers (Claude Code: `.claude/skills/`, others: equivalent). Skills then load as context without referencing `framework-skills/` paths. After framework updates, run the `maintenance` skill — Phase B re-syncs the agent directory.+ | `add-skill` | Author a skill the server publishes in root `skills/` for the agents that use it — when one earns its place, structure, validation, delivery |+ | `bun run lint:packaging` | Packaging surface checks — `server.json`/`manifest.json` env-var parity, `.mcpbignore` coverage, plugin manifests, the published `skills/` tree (run by devcheck) |+ `bun run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. `mcpb pack` reads `.mcpbignore`, not `.gitignore`, so every root path the bundle must not ship — source, tests, docs, caches, logs, agent docs, plugin manifests, gitignored local files — needs a root-anchored line there (`/docs/`, never `docs/`, which also strips `node_modules/*/docs/`); `lint:packaging` names each missing or unanchored line. Root `skills/` ships on purpose. The pack step is followed by+ - [ ] `plugin.json` + `mcp.json` (Agent Plugins 1.0.0 — read by Codex, Cursor, GitHub Copilot, VS Code, Kiro) populated — `plugin.json` `name` = the unscoped repo name (lowercase `a-z`, `0-9`, `-`, `.` only); `version`, `description`, `author`, `homepage`, `repository`, `license`, `keywords` from `package.json`; no other top-level field, `extensions` included. `mcp.json` holds one `"type": "stdio"` entry keyed by the unscoped name, `npx -y <full package name>`, and the same fixed `env` as `.code+ - [ ] Any new root dev path has a root-anchored line in `.mcpbignore`; `bun run bundle` and `lint:packaging` pass+ - [ ] If the server publishes a skill in root `skills/`: `lint:packaging` passes, and `field-test` verified every gate and identifier claim in it against live tool behavior

    Showing the first 40 of each.

  • Resources +1
    • + cpsc-recalls-guide

  • server.json 1 field changed
    version
    0.3.0 0.3.1

  • server.json 1 field changed
    version
    0.2.3 0.3.0

  • server.json 1 field changed
    version
    0.2.2 0.2.3

  • AGENTS.md +11 −10

    https://raw.githubusercontent.com/cyanheads/cpsc-recalls-mcp-server/HEAD/AGENTS.md

    − **Version:** 0.2.1− **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.6`− **MCP SDK:** `@modelcontextprotocol/server` ^2.0.0 (protocol revisions 2026-07-28 and 2025-*)−     limit: z.number().default(10).describe('Max results'),− | `ctx.state` | Tenant-scoped KV — `.get(key)`, `.set(key, value, { ttl? })`, `.delete(key)`, `.getMany(keys)`, `.list(prefix, { cursor, limit })`. Accepts any serializable value. |− | `ctx.inputs` | Reader over a retried request's responses — `.accepted(key, schema)`, `.view(key)`, `.state()`, `.dropped`. Empty on the first round. |− | `ctx.requestId` | Unique request ID. |− **Recommended: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable?, severity?, thrownBy? }]` on `tool()` / `resource()` to receive `ctx.fail(reason, …)` typed against the reason union. TypeScript catches typos at compile time, `data.reason` is auto-populated for observability, linter enforces conformance against the handler body. `recovery` is required (≥ 5 words, lint-validated) — the single source of truth for the agent's next move. Pass `ctx.recoveryFor('reaso−   if (!item) throw ctx.fail('no_match', `No item ${input.id}`, ctx.recoveryFor('no_match'));− **Every release goes through a gated release PR** — `git-wrapup`'s "Release PR mode", mode `gated`. Three separate runs, never one: `git-wrapup` lands the commit stack on `release/<version>`, pushes it, and opens the PR (title = the release commit subject, body = the changelog entry plus a gates section); `release-pr-review` reviews and fixes on that branch (each fix an ordinary commit on top of the stack, pushed plainly — nothing already pushed is ever rewritten, so `main` keeps the record of w+ **Version:** 0.2.2+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.14`+ **MCP SDK:** `@modelcontextprotocol/server` ^2.2.0 (protocol revisions 2026-07-28 and 2025-*)+     limit: z.number().int().min(1).max(100).default(10).describe('Max results (1–100)'),+ | `ctx.state` | Tenant-scoped KV — `.get(key)`, `.set(key, value, { ttl? })`, `.delete(key)`, `.getMany(keys)`, `.list(prefix, { cursor, limit })`. Accepts any JSON-serializable value; reads return its JSON form (a `Date` comes back as an ISO string). |+ | `ctx.inputs` | The request's responses — `.accepted(key, schema)`, `.view(key)`, `.state()`, `.dropped` — limited to what the client declared (`elicitation` and its form/url modes, `sampling`, `roots`). Client-supplied: a consent gate trusts only a `ctx.state` record it stored when it asked, bound to the operation, caller, and target (see the `api-context` skill). |+ | `ctx.clientCapabilities` | What the client declared for this request, `undefined` when no view exists. Decides whether to ask for optional context (e.g. roots); never a reason to skip a consent prompt. |+ | `ctx.requestId` | Request ID — the one every log record of the call carries and its error envelope returns as `data.requestId`. |+ **Recommended: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable?, severity?, thrownBy? }]` on `tool()` / `resource()` to receive `ctx.fail(reason, …)` typed against the reason union. TypeScript catches typos at compile time, `data.reason` is auto-populated for observability, linter enforces conformance against the handler body. `recovery` is required (≥ 5 words, lint-validated) — the single source of truth for the agent's next move. The framework puts it on the+   if (!item) throw ctx.fail('no_match', `No item ${input.id}`);+ **Every release goes through a gated release PR** — `git-wrapup`'s "Release PR mode", mode `gated`. Three separate runs, never one: `git-wrapup` lands the commit stack on `release/<version>`, pushes it, and opens the PR (title = the release commit subject, body = the release digest: theme line, `## Changes`, `## Gates`, changelog link last); `release-pr-review` reviews and fixes on that branch (each fix an ordinary commit on top of the stack, pushed plainly — nothing already pushed is ever rewri

  • server.json 2 fields changed
    description
    US consumer product recalls from the CPSC — hazards, remedies, and affected products. US consumer product recalls from the CPSC: hazards, remedies, and affected products.
    version
    0.2.1 0.2.2