@cyanheads/cpsc-recalls-mcp-server 0.1.3 → 0.1.5

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.
Files changed (26) hide show
  1. package/AGENTS.md +40 -18
  2. package/CLAUDE.md +40 -18
  3. package/Dockerfile +11 -7
  4. package/README.md +10 -2
  5. package/changelog/0.1.x/0.1.4.md +26 -0
  6. package/changelog/0.1.x/0.1.5.md +52 -0
  7. package/changelog/template.md +5 -3
  8. package/dist/mcp-server/tools/definitions/cpsc-get-recall.tool.d.ts +3 -1
  9. package/dist/mcp-server/tools/definitions/cpsc-get-recall.tool.d.ts.map +1 -1
  10. package/dist/mcp-server/tools/definitions/cpsc-get-recall.tool.js +111 -32
  11. package/dist/mcp-server/tools/definitions/cpsc-get-recall.tool.js.map +1 -1
  12. package/dist/mcp-server/tools/definitions/cpsc-get-recent.tool.d.ts +2 -0
  13. package/dist/mcp-server/tools/definitions/cpsc-get-recent.tool.d.ts.map +1 -1
  14. package/dist/mcp-server/tools/definitions/cpsc-get-recent.tool.js +38 -9
  15. package/dist/mcp-server/tools/definitions/cpsc-get-recent.tool.js.map +1 -1
  16. package/dist/mcp-server/tools/definitions/cpsc-search-recalls.tool.d.ts +9 -2
  17. package/dist/mcp-server/tools/definitions/cpsc-search-recalls.tool.d.ts.map +1 -1
  18. package/dist/mcp-server/tools/definitions/cpsc-search-recalls.tool.js +82 -39
  19. package/dist/mcp-server/tools/definitions/cpsc-search-recalls.tool.js.map +1 -1
  20. package/dist/services/cpsc-recall/cpsc-recall-service.d.ts.map +1 -1
  21. package/dist/services/cpsc-recall/cpsc-recall-service.js +26 -0
  22. package/dist/services/cpsc-recall/cpsc-recall-service.js.map +1 -1
  23. package/dist/services/cpsc-recall/types.d.ts +10 -2
  24. package/dist/services/cpsc-recall/types.d.ts.map +1 -1
  25. package/package.json +12 -11
  26. package/server.json +3 -3
package/AGENTS.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Server:** cpsc-recalls-mcp-server
4
- **Version:** 0.1.3
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.10.6`
4
+ **Version:** 0.1.5
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.11.0`
6
6
  **Engines:** Bun ≥1.3.0, Node ≥24.0.0
7
7
  **MCP SDK:** `@modelcontextprotocol/sdk` ^1.29.0
8
8
  **Zod:** ^4.4.3
@@ -35,7 +35,7 @@ Tailor suggestions to what's actually missing or stale — don't recite the full
35
35
  - **Logic throws, framework catches.** Tool/resource handlers are pure — throw on failure, no `try/catch`. Plain `Error` is fine; the framework catches, classifies, and formats. Use error factories (`notFound()`, `validationError()`, etc.) when the error code matters.
36
36
  - **Use `ctx.log`** for request-scoped logging. No `console` calls.
37
37
  - **Use `ctx.state`** for tenant-scoped storage. Never access persistence directly.
38
- - **Check `ctx.elicit` / `ctx.sample`** for presence before calling.
38
+ - **Check `ctx.elicit`** for presence before calling.
39
39
  - **Secrets in env vars only** — never hardcoded.
40
40
  - **Close the loop on issues.** When implementing work tracked by a GitHub issue, comment on the issue with what landed and close it. Do both — a comment without a close leaves stale issues open; a close without a comment leaves no record of what shipped. The comment is for future readers — state the concrete changes, not the conversation that produced them.
41
41
 
@@ -139,9 +139,24 @@ export function getServerConfig() {
139
139
 
140
140
  `parseEnvConfig` maps Zod schema paths → env var names so errors name the variable (`MY_API_KEY`) not the path (`apiKey`). Throws `ConfigurationError`, which the framework prints as a clean startup banner.
141
141
 
142
- ### Server instructions
142
+ For env booleans use `z.stringbool()`, never `z.coerce.boolean()` — `Boolean("false")` is `true`, so a coerced flag can't be disabled through the environment. `z.stringbool()` parses `true/false/1/0/yes/no/on/off` and rejects anything else, so `=false` actually disables.
143
143
 
144
- `createApp({ instructions })` — optional server-level orientation, sent to clients on every `initialize` as session-level context. Use it for deployment guidance (connection aliases, regional notes, scope hints) instead of repeating the same context across tool descriptions. Client adoption is uneven, but there's no downside when set.
144
+ ### Server identity and instructions
145
+
146
+ `createApp()` accepts optional identity fields forwarded to the SDK's `initialize` response and the server manifest (`/.well-known/mcp.json`):
147
+
148
+ ```ts
149
+ await createApp({
150
+ name: 'cpsc-recalls-mcp-server',
151
+ title: 'cpsc-recalls-mcp-server', // display name — the hyphenated repo name
152
+ icons: [{ src: 'https://example.com/icon.png', sizes: ['48x48'], mimeType: 'image/png' }],
153
+ instructions: 'Use shortcut alpha for the most common case.', // session-level context
154
+ });
155
+ ```
156
+
157
+ `description` and `websiteUrl` are also accepted but derive from `package.json` — setting them here duplicates the canonical source and drifts.
158
+
159
+ `instructions` is optional server-level orientation, sent on every `initialize` as session-level context. Use it for deployment guidance (connection aliases, regional notes, scope hints) instead of repeating the same context across tool descriptions. Client adoption is uneven, but there's no downside when set.
145
160
 
146
161
  ---
147
162
 
@@ -152,13 +167,14 @@ Handlers receive a unified `ctx` object. Key properties:
152
167
  | Property | Description |
153
168
  |:---------|:------------|
154
169
  | `ctx.log` | Request-scoped logger — `.debug()`, `.info()`, `.notice()`, `.warning()`, `.error()`. Auto-correlates requestId, traceId, tenantId. |
155
- | `ctx.state` | Tenant-scoped KV — `.get(key)`, `.set(key, value, { ttl? })`, `.delete(key)`, `.list(prefix, { cursor, limit })`. Accepts any serializable value. |
156
- | `ctx.elicit` | Ask user for structured input. **Check for presence first:** `if (ctx.elicit) { ... }` |
157
- | `ctx.sample` | Request LLM completion from the client. **Check for presence first:** `if (ctx.sample) { ... }` |
170
+ | `ctx.state` | Tenant-scoped KV — `.get(key)`, `.set(key, value, { ttl? })`, `.delete(key)`, `.getMany(keys)`, `.list(prefix, { cursor, limit })`. Accepts any serializable value. |
171
+ | `ctx.elicit` | Ask user for structured input — form call `(message, schema)` or `.url(message, url)` for an external link. **Check for presence first:** `if (ctx.elicit) { ... }` |
172
+ | `ctx.enrich` | Success-path agent context (empty-result notices, query echo, pagination totals) — `ctx.enrich(...)` or `.notice()` / `.total()` / `.echo()` / `.truncated()`. Reaches `structuredContent` and `content[]`; lands only when the definition declares an `enrichment` block (no-op otherwise). |
173
+ | `ctx.content` | Non-text content blocks — `.image(data, mimeType)`, `.audio(data, mimeType)`, or `ctx.content(block)` for a raw block. Prepended to `content[]` after `format()`; never enters `structuredContent`. |
158
174
  | `ctx.signal` | `AbortSignal` for cancellation. |
159
175
  | `ctx.progress` | Task progress (present when `task: true`) — `.setTotal(n)`, `.increment()`, `.update(message)`. |
160
176
  | `ctx.requestId` | Unique request ID. |
161
- | `ctx.tenantId` | Tenant ID from JWT or `'default'` for stdio. |
177
+ | `ctx.tenantId` | Tenant ID from JWT; `'default'` for stdio or HTTP with auth off. |
162
178
 
163
179
  ---
164
180
 
@@ -166,7 +182,7 @@ Handlers receive a unified `ctx` object. Key properties:
166
182
 
167
183
  Handlers throw — the framework catches, classifies, and formats.
168
184
 
169
- **Recommended: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable? }]` 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 descriptive metadata for the agent's next move (≥ 5 words, lint-validated); for the wire `data.recovery.hint` (mirrored into `content[]` text), pass explicitly at the throw site when dynamic context matters: `ctx.fail('reason', msg, { recovery: { hint: '...' } })`. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`) bubble freely and don't need declaring.
185
+ **Recommended: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable? }]` 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('reason')` as the throw's data to put it on the wire (`data.recovery.hint`, mirrored into `content[]` text); override with an explicit `{ recovery: { hint: '...' } }` when dynamic runtime context matters. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`) bubble freely and don't need declaring.
170
186
 
171
187
  ```ts
172
188
  import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
@@ -178,7 +194,7 @@ errors: [
178
194
  ],
179
195
  async handler(input, ctx) {
180
196
  const item = await db.find(input.id);
181
- if (!item) throw ctx.fail('no_match', `No item ${input.id}`);
197
+ if (!item) throw ctx.fail('no_match', `No item ${input.id}`, ctx.recoveryFor('no_match'));
182
198
  return item;
183
199
  }
184
200
  ```
@@ -237,7 +253,9 @@ src/
237
253
 
238
254
  ## Skills
239
255
 
240
- Skills are modular instructions in `skills/` at the project root. Read them directly when a task matches — e.g., `skills/add-tool/SKILL.md` when adding a tool.
256
+ Skills are modular instructions in `skills/` at the project root. Read them directly when a task matches — e.g., `skills/add-tool/SKILL.md` when adding a tool. `bun run list-skills` prints the full registry.
257
+
258
+ **Chaining skills into pipelines.** When the user wants a multi-phase effort — build this server out, QA-and-fix the surface, update-and-ship — *and you can spawn sub-agents*, `skills/orchestrations/SKILL.md` sequences the task skills below into a gated pipeline with verification at each step. Read it to drive the run. Optional: skip it if you can't orchestrate sub-agents, and ignore it entirely if you were *spawned* as one — you've already been scoped to a single phase.
241
259
 
242
260
  **Agent skill directory:** Copy skills into the directory your agent discovers (Claude Code: `.claude/skills/`, others: equivalent). Skills then load as context without referencing `skills/` paths. After framework updates, run the `maintenance` skill — Phase B re-syncs the agent directory.
243
261
 
@@ -257,7 +275,6 @@ Available skills:
257
275
  | `tool-defs-analysis` | Read-only audit of MCP definition language across the surface — voice, leaks, defaults, recovery hints, output descriptions |
258
276
  | `security-pass` | Audit server for MCP-flavored security gaps: output injection, scope blast radius, input sinks, tenant isolation |
259
277
  | `code-simplifier` | Post-session cleanup against `git diff` — modernize syntax, consolidate duplication, align with the codebase |
260
- | `devcheck` | Lint, format, typecheck, audit |
261
278
  | `polish-docs-meta` | Finalize docs, README, metadata, and agent protocol for shipping |
262
279
  | `git-wrapup` | Land working-tree changes as a versioned commit + annotated tag — version bump, changelog, verify, tag. Local only. |
263
280
  | `release-and-publish` | Push + npm + MCP Registry + GH Release + Docker. Picks up from `git-wrapup` |
@@ -265,12 +282,14 @@ Available skills:
265
282
  | `orchestrations` | Chain task skills into a gated multi-phase pipeline — build-out, QA-fix, update-ship — when you can spawn sub-agents |
266
283
  | `report-issue-framework` | File a bug or feature request against `@cyanheads/mcp-ts-core` via `gh` CLI |
267
284
  | `report-issue-local` | File a bug or feature request against this server's own repo via `gh` CLI |
285
+ | `techniques` | Catalog of response/data-shaping techniques — overflow handling, payload shaping, retrieval patterns |
268
286
  | `api-auth` | Auth modes, scopes, JWT/OAuth |
269
287
  | `api-canvas` | DataCanvas: register tabular data, run SQL, export, plus the `spillover()` helper for big result sets — Tier 3 opt-in |
270
288
  | `api-config` | AppConfig, parseConfig, env vars |
271
289
  | `api-context` | Context interface, logger, state, progress |
272
290
  | `api-errors` | McpError, JsonRpcErrorCode, error patterns |
273
291
  | `api-linter` | Definition linter rule catalog — invoked by `bun run lint:mcp` and `devcheck` |
292
+ | `api-mirror` | MirrorService: persistent self-refreshing local mirror (embedded SQLite + FTS5) of a bulk upstream dataset — Tier 3 opt-in |
274
293
  | `api-services` | LLM, Speech, Graph services |
275
294
  | `api-testing` | createMockContext, test patterns |
276
295
  | `api-utils` | Formatting, parsing, security, pagination, scheduling, telemetry helpers |
@@ -293,20 +312,23 @@ When you complete a skill's checklist, check the boxes and add a completion time
293
312
  | `bun run devcheck` | Lint + format + typecheck + security + changelog sync |
294
313
  | `bun run audit:refresh` | Delete `bun.lock`, reinstall, and re-run `bun audit`. Use when `devcheck` flags a transitive advisory — Bun's `update` is sticky on transitive resolutions, so the advisory may be a stale-lockfile false positive. If it survives the refresh, it's real. |
295
314
  | `bun run tree` | Generate directory structure doc |
315
+ | `bun run lint:mcp` | Run the MCP definition linter standalone (rule catalog: `api-linter` skill) |
316
+ | `bun run lint:packaging` | Packaging surface checks — `server.json`/`manifest.json` env-var parity (run by devcheck) |
317
+ | `bun run list-skills` | Print the skill registry |
296
318
  | `bun run format` | Auto-fix formatting (safe fixes only) |
297
319
  | `bun run format:unsafe` | Also apply Biome's unsafe autofixes — review the diff; they can change behavior |
298
- | `bun run test` | Run tests |
320
+ | `bun run test` | Run tests (Vitest — use `bun run test`, not `bun test`) |
299
321
  | `bun run start:stdio` | Production mode (stdio) |
300
322
  | `bun run start:http` | Production mode (HTTP) |
301
323
  | `bun run changelog:build` | Regenerate `CHANGELOG.md` from `changelog/*.md` |
302
324
  | `bun run changelog:check` | Verify `CHANGELOG.md` is in sync (used by devcheck) |
303
- | `bun run bundle` | Build and pack as `.mcpb` for one-click Claude Desktop install |
325
+ | `bun run bundle` | Build, pack, and clean a `.mcpb` for one-click Claude Desktop install |
304
326
 
305
327
  ---
306
328
 
307
329
  ## Bundling
308
330
 
309
- `npm run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. MCPB is stdio-only — HTTP and Cloudflare Workers deployments are unaffected. Consumers who don't need it can delete `manifest.json` and `.mcpbignore`; `lint:packaging` skips cleanly.
331
+ `bun run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. The pack step is followed by `scripts/clean-mcpb.ts`, which prunes dev dependencies (`mcpb clean`) and strips two classes of `node_modules/**` content that root-anchored `.mcpbignore` patterns cannot reach: dependency-shipped agent docs (`skills/`, `.claude/`, `.agents/`, `SKILL.md`) and platform-specific native bindings, which would otherwise lock the bundle to the platform it was packed on. MCPB is stdio-only — HTTP and Cloudflare Workers deployments are unaffected. Consumers who don't need it can delete `manifest.json` and `.mcpbignore`; `lint:packaging` skips cleanly.
310
332
 
311
333
  **Adding an env var requires both files:** `server.json` (registry discovery, `environmentVariables[]`) and `manifest.json` (bundle install UX, `mcp_config.env` + `user_config`). `lint:packaging` (run by `devcheck`) verifies the env var names match.
312
334
 
@@ -324,14 +346,14 @@ Each per-version file opens with YAML frontmatter:
324
346
  ---
325
347
  summary: "One-line headline, ≤350 chars" # required — powers the rollup index
326
348
  breaking: false # optional — true flags breaking changes
327
- security: false # optional — true flags security fixes
349
+ security: false # optional — true ONLY for a source-code security fix, never a dependency CVE bump
328
350
  ---
329
351
 
330
352
  # 0.1.0 — YYYY-MM-DD
331
353
  ...
332
354
  ```
333
355
 
334
- `breaking: true` renders a `· ⚠️ Breaking` badge — use it when consumers must update code on upgrade (signature changes, removed APIs, config renames). `security: true` renders a `· 🛡️ Security` badge and pairs with a `## Security` body section. When both are set, badges render `· ⚠️ Breaking · 🛡️ Security`.
356
+ `breaking: true` renders a `· ⚠️ Breaking` badge — use it when consumers must update code on upgrade (signature changes, removed APIs, config renames). `security: true` renders a `· 🛡️ Security` badge and pairs with a `## Security` body section — set it only for a security fix in this server's *own source code*, never for a routine dependency or transitive CVE bump (record those under `## Dependencies`). When both are set, badges render `· ⚠️ Breaking · 🛡️ Security`.
335
357
 
336
358
  `agent-notes` is an optional free-form field for maintenance agents processing the release downstream. Content here won't appear in the rendered CHANGELOG — it's consumed by agents running the `maintenance` skill. Use it for adoption instructions that don't fit the human-facing sections: new files to create, fields to populate, one-time migration steps. Omit entirely when there's nothing to say.
337
359
 
package/CLAUDE.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Server:** cpsc-recalls-mcp-server
4
- **Version:** 0.1.3
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.10.6`
4
+ **Version:** 0.1.5
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.11.0`
6
6
  **Engines:** Bun ≥1.3.0, Node ≥24.0.0
7
7
  **MCP SDK:** `@modelcontextprotocol/sdk` ^1.29.0
8
8
  **Zod:** ^4.4.3
@@ -35,7 +35,7 @@ Tailor suggestions to what's actually missing or stale — don't recite the full
35
35
  - **Logic throws, framework catches.** Tool/resource handlers are pure — throw on failure, no `try/catch`. Plain `Error` is fine; the framework catches, classifies, and formats. Use error factories (`notFound()`, `validationError()`, etc.) when the error code matters.
36
36
  - **Use `ctx.log`** for request-scoped logging. No `console` calls.
37
37
  - **Use `ctx.state`** for tenant-scoped storage. Never access persistence directly.
38
- - **Check `ctx.elicit` / `ctx.sample`** for presence before calling.
38
+ - **Check `ctx.elicit`** for presence before calling.
39
39
  - **Secrets in env vars only** — never hardcoded.
40
40
  - **Close the loop on issues.** When implementing work tracked by a GitHub issue, comment on the issue with what landed and close it. Do both — a comment without a close leaves stale issues open; a close without a comment leaves no record of what shipped. The comment is for future readers — state the concrete changes, not the conversation that produced them.
41
41
 
@@ -139,9 +139,24 @@ export function getServerConfig() {
139
139
 
140
140
  `parseEnvConfig` maps Zod schema paths → env var names so errors name the variable (`MY_API_KEY`) not the path (`apiKey`). Throws `ConfigurationError`, which the framework prints as a clean startup banner.
141
141
 
142
- ### Server instructions
142
+ For env booleans use `z.stringbool()`, never `z.coerce.boolean()` — `Boolean("false")` is `true`, so a coerced flag can't be disabled through the environment. `z.stringbool()` parses `true/false/1/0/yes/no/on/off` and rejects anything else, so `=false` actually disables.
143
143
 
144
- `createApp({ instructions })` — optional server-level orientation, sent to clients on every `initialize` as session-level context. Use it for deployment guidance (connection aliases, regional notes, scope hints) instead of repeating the same context across tool descriptions. Client adoption is uneven, but there's no downside when set.
144
+ ### Server identity and instructions
145
+
146
+ `createApp()` accepts optional identity fields forwarded to the SDK's `initialize` response and the server manifest (`/.well-known/mcp.json`):
147
+
148
+ ```ts
149
+ await createApp({
150
+ name: 'cpsc-recalls-mcp-server',
151
+ title: 'cpsc-recalls-mcp-server', // display name — the hyphenated repo name
152
+ icons: [{ src: 'https://example.com/icon.png', sizes: ['48x48'], mimeType: 'image/png' }],
153
+ instructions: 'Use shortcut alpha for the most common case.', // session-level context
154
+ });
155
+ ```
156
+
157
+ `description` and `websiteUrl` are also accepted but derive from `package.json` — setting them here duplicates the canonical source and drifts.
158
+
159
+ `instructions` is optional server-level orientation, sent on every `initialize` as session-level context. Use it for deployment guidance (connection aliases, regional notes, scope hints) instead of repeating the same context across tool descriptions. Client adoption is uneven, but there's no downside when set.
145
160
 
146
161
  ---
147
162
 
@@ -152,13 +167,14 @@ Handlers receive a unified `ctx` object. Key properties:
152
167
  | Property | Description |
153
168
  |:---------|:------------|
154
169
  | `ctx.log` | Request-scoped logger — `.debug()`, `.info()`, `.notice()`, `.warning()`, `.error()`. Auto-correlates requestId, traceId, tenantId. |
155
- | `ctx.state` | Tenant-scoped KV — `.get(key)`, `.set(key, value, { ttl? })`, `.delete(key)`, `.list(prefix, { cursor, limit })`. Accepts any serializable value. |
156
- | `ctx.elicit` | Ask user for structured input. **Check for presence first:** `if (ctx.elicit) { ... }` |
157
- | `ctx.sample` | Request LLM completion from the client. **Check for presence first:** `if (ctx.sample) { ... }` |
170
+ | `ctx.state` | Tenant-scoped KV — `.get(key)`, `.set(key, value, { ttl? })`, `.delete(key)`, `.getMany(keys)`, `.list(prefix, { cursor, limit })`. Accepts any serializable value. |
171
+ | `ctx.elicit` | Ask user for structured input — form call `(message, schema)` or `.url(message, url)` for an external link. **Check for presence first:** `if (ctx.elicit) { ... }` |
172
+ | `ctx.enrich` | Success-path agent context (empty-result notices, query echo, pagination totals) — `ctx.enrich(...)` or `.notice()` / `.total()` / `.echo()` / `.truncated()`. Reaches `structuredContent` and `content[]`; lands only when the definition declares an `enrichment` block (no-op otherwise). |
173
+ | `ctx.content` | Non-text content blocks — `.image(data, mimeType)`, `.audio(data, mimeType)`, or `ctx.content(block)` for a raw block. Prepended to `content[]` after `format()`; never enters `structuredContent`. |
158
174
  | `ctx.signal` | `AbortSignal` for cancellation. |
159
175
  | `ctx.progress` | Task progress (present when `task: true`) — `.setTotal(n)`, `.increment()`, `.update(message)`. |
160
176
  | `ctx.requestId` | Unique request ID. |
161
- | `ctx.tenantId` | Tenant ID from JWT or `'default'` for stdio. |
177
+ | `ctx.tenantId` | Tenant ID from JWT; `'default'` for stdio or HTTP with auth off. |
162
178
 
163
179
  ---
164
180
 
@@ -166,7 +182,7 @@ Handlers receive a unified `ctx` object. Key properties:
166
182
 
167
183
  Handlers throw — the framework catches, classifies, and formats.
168
184
 
169
- **Recommended: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable? }]` 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 descriptive metadata for the agent's next move (≥ 5 words, lint-validated); for the wire `data.recovery.hint` (mirrored into `content[]` text), pass explicitly at the throw site when dynamic context matters: `ctx.fail('reason', msg, { recovery: { hint: '...' } })`. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`) bubble freely and don't need declaring.
185
+ **Recommended: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable? }]` 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('reason')` as the throw's data to put it on the wire (`data.recovery.hint`, mirrored into `content[]` text); override with an explicit `{ recovery: { hint: '...' } }` when dynamic runtime context matters. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`) bubble freely and don't need declaring.
170
186
 
171
187
  ```ts
172
188
  import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
@@ -178,7 +194,7 @@ errors: [
178
194
  ],
179
195
  async handler(input, ctx) {
180
196
  const item = await db.find(input.id);
181
- if (!item) throw ctx.fail('no_match', `No item ${input.id}`);
197
+ if (!item) throw ctx.fail('no_match', `No item ${input.id}`, ctx.recoveryFor('no_match'));
182
198
  return item;
183
199
  }
184
200
  ```
@@ -237,7 +253,9 @@ src/
237
253
 
238
254
  ## Skills
239
255
 
240
- Skills are modular instructions in `skills/` at the project root. Read them directly when a task matches — e.g., `skills/add-tool/SKILL.md` when adding a tool.
256
+ Skills are modular instructions in `skills/` at the project root. Read them directly when a task matches — e.g., `skills/add-tool/SKILL.md` when adding a tool. `bun run list-skills` prints the full registry.
257
+
258
+ **Chaining skills into pipelines.** When the user wants a multi-phase effort — build this server out, QA-and-fix the surface, update-and-ship — *and you can spawn sub-agents*, `skills/orchestrations/SKILL.md` sequences the task skills below into a gated pipeline with verification at each step. Read it to drive the run. Optional: skip it if you can't orchestrate sub-agents, and ignore it entirely if you were *spawned* as one — you've already been scoped to a single phase.
241
259
 
242
260
  **Agent skill directory:** Copy skills into the directory your agent discovers (Claude Code: `.claude/skills/`, others: equivalent). Skills then load as context without referencing `skills/` paths. After framework updates, run the `maintenance` skill — Phase B re-syncs the agent directory.
243
261
 
@@ -257,7 +275,6 @@ Available skills:
257
275
  | `tool-defs-analysis` | Read-only audit of MCP definition language across the surface — voice, leaks, defaults, recovery hints, output descriptions |
258
276
  | `security-pass` | Audit server for MCP-flavored security gaps: output injection, scope blast radius, input sinks, tenant isolation |
259
277
  | `code-simplifier` | Post-session cleanup against `git diff` — modernize syntax, consolidate duplication, align with the codebase |
260
- | `devcheck` | Lint, format, typecheck, audit |
261
278
  | `polish-docs-meta` | Finalize docs, README, metadata, and agent protocol for shipping |
262
279
  | `git-wrapup` | Land working-tree changes as a versioned commit + annotated tag — version bump, changelog, verify, tag. Local only. |
263
280
  | `release-and-publish` | Push + npm + MCP Registry + GH Release + Docker. Picks up from `git-wrapup` |
@@ -265,12 +282,14 @@ Available skills:
265
282
  | `orchestrations` | Chain task skills into a gated multi-phase pipeline — build-out, QA-fix, update-ship — when you can spawn sub-agents |
266
283
  | `report-issue-framework` | File a bug or feature request against `@cyanheads/mcp-ts-core` via `gh` CLI |
267
284
  | `report-issue-local` | File a bug or feature request against this server's own repo via `gh` CLI |
285
+ | `techniques` | Catalog of response/data-shaping techniques — overflow handling, payload shaping, retrieval patterns |
268
286
  | `api-auth` | Auth modes, scopes, JWT/OAuth |
269
287
  | `api-canvas` | DataCanvas: register tabular data, run SQL, export, plus the `spillover()` helper for big result sets — Tier 3 opt-in |
270
288
  | `api-config` | AppConfig, parseConfig, env vars |
271
289
  | `api-context` | Context interface, logger, state, progress |
272
290
  | `api-errors` | McpError, JsonRpcErrorCode, error patterns |
273
291
  | `api-linter` | Definition linter rule catalog — invoked by `bun run lint:mcp` and `devcheck` |
292
+ | `api-mirror` | MirrorService: persistent self-refreshing local mirror (embedded SQLite + FTS5) of a bulk upstream dataset — Tier 3 opt-in |
274
293
  | `api-services` | LLM, Speech, Graph services |
275
294
  | `api-testing` | createMockContext, test patterns |
276
295
  | `api-utils` | Formatting, parsing, security, pagination, scheduling, telemetry helpers |
@@ -293,20 +312,23 @@ When you complete a skill's checklist, check the boxes and add a completion time
293
312
  | `bun run devcheck` | Lint + format + typecheck + security + changelog sync |
294
313
  | `bun run audit:refresh` | Delete `bun.lock`, reinstall, and re-run `bun audit`. Use when `devcheck` flags a transitive advisory — Bun's `update` is sticky on transitive resolutions, so the advisory may be a stale-lockfile false positive. If it survives the refresh, it's real. |
295
314
  | `bun run tree` | Generate directory structure doc |
315
+ | `bun run lint:mcp` | Run the MCP definition linter standalone (rule catalog: `api-linter` skill) |
316
+ | `bun run lint:packaging` | Packaging surface checks — `server.json`/`manifest.json` env-var parity (run by devcheck) |
317
+ | `bun run list-skills` | Print the skill registry |
296
318
  | `bun run format` | Auto-fix formatting (safe fixes only) |
297
319
  | `bun run format:unsafe` | Also apply Biome's unsafe autofixes — review the diff; they can change behavior |
298
- | `bun run test` | Run tests |
320
+ | `bun run test` | Run tests (Vitest — use `bun run test`, not `bun test`) |
299
321
  | `bun run start:stdio` | Production mode (stdio) |
300
322
  | `bun run start:http` | Production mode (HTTP) |
301
323
  | `bun run changelog:build` | Regenerate `CHANGELOG.md` from `changelog/*.md` |
302
324
  | `bun run changelog:check` | Verify `CHANGELOG.md` is in sync (used by devcheck) |
303
- | `bun run bundle` | Build and pack as `.mcpb` for one-click Claude Desktop install |
325
+ | `bun run bundle` | Build, pack, and clean a `.mcpb` for one-click Claude Desktop install |
304
326
 
305
327
  ---
306
328
 
307
329
  ## Bundling
308
330
 
309
- `npm run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. MCPB is stdio-only — HTTP and Cloudflare Workers deployments are unaffected. Consumers who don't need it can delete `manifest.json` and `.mcpbignore`; `lint:packaging` skips cleanly.
331
+ `bun run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. The pack step is followed by `scripts/clean-mcpb.ts`, which prunes dev dependencies (`mcpb clean`) and strips two classes of `node_modules/**` content that root-anchored `.mcpbignore` patterns cannot reach: dependency-shipped agent docs (`skills/`, `.claude/`, `.agents/`, `SKILL.md`) and platform-specific native bindings, which would otherwise lock the bundle to the platform it was packed on. MCPB is stdio-only — HTTP and Cloudflare Workers deployments are unaffected. Consumers who don't need it can delete `manifest.json` and `.mcpbignore`; `lint:packaging` skips cleanly.
310
332
 
311
333
  **Adding an env var requires both files:** `server.json` (registry discovery, `environmentVariables[]`) and `manifest.json` (bundle install UX, `mcp_config.env` + `user_config`). `lint:packaging` (run by `devcheck`) verifies the env var names match.
312
334
 
@@ -324,14 +346,14 @@ Each per-version file opens with YAML frontmatter:
324
346
  ---
325
347
  summary: "One-line headline, ≤350 chars" # required — powers the rollup index
326
348
  breaking: false # optional — true flags breaking changes
327
- security: false # optional — true flags security fixes
349
+ security: false # optional — true ONLY for a source-code security fix, never a dependency CVE bump
328
350
  ---
329
351
 
330
352
  # 0.1.0 — YYYY-MM-DD
331
353
  ...
332
354
  ```
333
355
 
334
- `breaking: true` renders a `· ⚠️ Breaking` badge — use it when consumers must update code on upgrade (signature changes, removed APIs, config renames). `security: true` renders a `· 🛡️ Security` badge and pairs with a `## Security` body section. When both are set, badges render `· ⚠️ Breaking · 🛡️ Security`.
356
+ `breaking: true` renders a `· ⚠️ Breaking` badge — use it when consumers must update code on upgrade (signature changes, removed APIs, config renames). `security: true` renders a `· 🛡️ Security` badge and pairs with a `## Security` body section — set it only for a security fix in this server's *own source code*, never for a routine dependency or transitive CVE bump (record those under `## Dependencies`). When both are set, badges render `· ⚠️ Breaking · 🛡️ Security`.
335
357
 
336
358
  `agent-notes` is an optional free-form field for maintenance agents processing the release downstream. Content here won't appear in the rendered CHANGELOG — it's consumed by agents running the `maintenance` skill. Use it for adoption instructions that don't fit the human-facing sections: new files to create, fields to populate, one-time migration steps. Omit entirely when there's nothing to say.
337
359
 
package/Dockerfile CHANGED
@@ -4,15 +4,17 @@
4
4
  # This stage installs all dependencies (including dev), builds the TypeScript
5
5
  # source code into JavaScript, and prepares the production assets.
6
6
  # ==============================================================================
7
- FROM oven/bun:1.3 AS build
7
+ FROM oven/bun:1.3.14 AS build
8
8
 
9
9
  WORKDIR /usr/src/app
10
10
 
11
11
  # Copy dependency manifests for optimized layer caching
12
12
  COPY package.json bun.lock ./
13
13
 
14
- # Install all dependencies (including dev dependencies for building)
15
- RUN bun install --frozen-lockfile
14
+ # Install all dependencies (including dev dependencies for building).
15
+ # The BuildKit cache mount persists Bun's global package cache across builds.
16
+ RUN --mount=type=cache,target=/root/.bun/install/cache \
17
+ bun install --frozen-lockfile --ignore-scripts
16
18
 
17
19
  # Copy the rest of the source code
18
20
  COPY . .
@@ -28,7 +30,7 @@ RUN bun run build
28
30
  # application. It uses a slim base image and only includes production
29
31
  # dependencies and build artifacts.
30
32
  # ==============================================================================
31
- FROM oven/bun:1.3-slim AS production
33
+ FROM oven/bun:1.3.14-slim AS production
32
34
 
33
35
  WORKDIR /usr/src/app
34
36
 
@@ -49,14 +51,16 @@ COPY package.json bun.lock ./
49
51
 
50
52
  # Install only production dependencies, ignoring any lifecycle scripts (like 'prepare')
51
53
  # that are not needed in the final production image.
52
- RUN bun install --production --frozen-lockfile --ignore-scripts
54
+ RUN --mount=type=cache,target=/root/.bun/install/cache \
55
+ bun install --production --frozen-lockfile --ignore-scripts
53
56
 
54
57
  # Conditionally install OpenTelemetry optional peer dependencies (Tier 3).
55
58
  # These are not bundled by default to keep the base image lean. Enable at build time
56
59
  # with: docker build --build-arg OTEL_ENABLED=true
57
60
  ARG OTEL_ENABLED=true
58
- RUN if [ "$OTEL_ENABLED" = "true" ]; then \
59
- bun add @hono/otel \
61
+ RUN --mount=type=cache,target=/root/.bun/install/cache \
62
+ if [ "$OTEL_ENABLED" = "true" ]; then \
63
+ bun add --omit=dev --ignore-scripts @hono/otel \
60
64
  @opentelemetry/instrumentation-http \
61
65
  @opentelemetry/exporter-metrics-otlp-http \
62
66
  @opentelemetry/exporter-trace-otlp-http \
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  <div align="center">
9
9
 
10
- [![Version](https://img.shields.io/badge/Version-0.1.3-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/cpsc-recalls-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^1.29.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/@cyanheads/cpsc-recalls-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/cpsc-recalls-mcp-server) [![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.0+-blueviolet.svg?style=flat-square)](https://bun.sh/)
10
+ [![Version](https://img.shields.io/badge/Version-0.1.5-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/cpsc-recalls-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^1.29.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/@cyanheads/cpsc-recalls-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/cpsc-recalls-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.3.0+-blueviolet.svg?style=flat-square)](https://bun.sh/)
11
11
 
12
12
  </div>
13
13
 
@@ -46,9 +46,11 @@ Data sourced from the [U.S. Consumer Product Safety Commission](https://www.safe
46
46
  Search recalls with flexible filtering across product, organization, and description fields.
47
47
 
48
48
  - Filter by product name, manufacturer, retailer, importer, or description keyword — all fields are optional substring matches that combine with AND
49
- - Date range filtering via `date_start` / `date_end` (ISO 8601)
49
+ - Date range filtering via `date_start` / `date_end` (ISO 8601). Both must be real calendar dates — `2026-02-31` and `2026-99-99` are rejected at input validation, and a reversed range (`date_start` after `date_end`) fails with `invalid_date_range` rather than silently returning nothing
50
50
  - Client-side limit (1–200, default 20) applied after fetching all matching records
51
51
  - Returns hazard descriptions, remedy options, remedy instructions, product list, UPCs, manufacturer/importer/retailer names, images, and the CPSC recall page URL
52
+ - Manufacturer and importer are reported and rendered as separate roles — CPSC org names contain commas, so multiple orgs in one role are separated with `; `
53
+ - Per-recall `data_quality_notes` records gaps in the upstream record (no hazard text, no product entries); empty when nothing is missing
52
54
  - Includes `total_found` and `truncated` fields for pagination awareness
53
55
  - Note: the `Hazard` filter param is non-functional in the upstream API — use `description_search` for hazard-type keywords like "fire", "choking", or "burn"
54
56
  - When `manufacturer` returns no results, try `importer` or `retailer` — many recalls list the importer as the primary organization
@@ -62,6 +64,9 @@ Full detail for a single CPSC recall by recall number.
62
64
  - Accepts modern 5-digit recall numbers (e.g. `"25043"`) and historical 1998–2001 records with letter suffixes (e.g. `"99003a"`)
63
65
  - Returns the complete record: full description, all hazard descriptions, remedy type and instructions, all product variants with unit counts, UPCs, incident/injury narrative, manufacturer and importer names, retailer names with sale date ranges, country of manufacture, images, and coordinated agency recall URLs
64
66
  - Model numbers are typically embedded in the description text, not in a structured field
67
+ - `description` is nullable — a small number of genuine CPSC records carry no description; the record is still returned and the absence is stated rather than rendered as a blank section
68
+ - Manufacturer and importer are rendered under separate headings, so role attribution survives into `content[]`
69
+ - `data_quality_notes` records gaps in the upstream record (absent description, no hazard text, no product entries); empty when nothing is missing
65
70
  - Use `cpsc_search_recalls` or `cpsc_get_recent` to find a recall number first
66
71
 
67
72
  ---
@@ -74,6 +79,7 @@ Fetch the most recent CPSC recalls, ordered newest-first.
74
79
  - Limit of 1–100 results (default 20)
75
80
  - Always applies a date window — without one, the upstream API returns 9,800+ records
76
81
  - Returns a lightweight record per recall: number, date, title, hazards, remedy types, product names, and CPSC URL
82
+ - Per-recall `data_quality_notes` records gaps in the upstream record (no hazard text, no product entries); empty when nothing is missing
77
83
  - Use `cpsc_get_recall` to retrieve full detail for any result
78
84
 
79
85
  ## Features
@@ -98,6 +104,8 @@ Agent-friendly output:
98
104
  - Jurisdiction note on every response — agents can route callers to the correct agency (FDA, NHTSA, USCG, EPA) when the product is out of scope
99
105
  - `total_found` + `truncated` fields on search/recent responses — agents can detect when results are clipped and suggest narrowing filters
100
106
  - `cpsc_url` on every recall — authoritative source link for consumer verification
107
+ - `source_note` on every response, plus `(CPSC source text)` labels and blockquotes in the rendered output — relayed CPSC narrative is marked as source data, distinct from the server's own guidance
108
+ - `data_quality_notes` on every response — gaps observed in the upstream record, derived from which fields CPSC left empty rather than any judgement about the recall
101
109
 
102
110
  ## Getting started
103
111
 
@@ -0,0 +1,26 @@
1
+ ---
2
+ summary: "Maintenance: @cyanheads/mcp-ts-core ^0.10.6 → ^0.10.9; plugin-manifest packaging checks enabled; biome 2.5, @types/node 26, vitest 4.1.9 refresh"
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.4 — 2026-06-20
8
+
9
+ Framework and toolchain refresh adopting `@cyanheads/mcp-ts-core` 0.10.7–0.10.9. No tool surface or behavior changes — the three CPSC tools are unaffected.
10
+
11
+ ## Added
12
+
13
+ - **`devcheck.config.json` `packaging.pluginManifests: true`** — opts into the framework's new `lint:packaging` check 10, validating `.claude-plugin/plugin.json`, `.codex-plugin/plugin.json`, and `.codex-plugin/mcp.json` for non-empty descriptions, unscoped display fields, and a full-package-name `npx -y` install arg (mcp-ts-core [#240](https://github.com/cyanheads/mcp-ts-core/issues/240)).
14
+ - **`scripts/check-dependency-specifiers.ts`** — new devcheck step synced from the framework; hard-fails on floating dependency specifiers (`latest`, `*`, pre-release dist-tags) in `package.json` and the `bun.lock` workspaces map, catching a `bun update --latest` writing a `latest` dist-tag into the lock (mcp-ts-core [#246](https://github.com/cyanheads/mcp-ts-core/issues/246)).
15
+
16
+ ## Changed
17
+
18
+ - **devcheck fresh-scaffold guards** — `build-changelog.ts`, `devcheck.ts`, `check-framework-antipatterns.ts`, and `check-skill-versions.ts` re-synced: changelog `--check` exits cleanly on an empty version tree, git-dependent checks skip when `.git` is absent, and a `SKILL.md` deleted from the worktree no longer crashes the version check (mcp-ts-core [#237](https://github.com/cyanheads/mcp-ts-core/issues/237), [#242](https://github.com/cyanheads/mcp-ts-core/issues/242), [#243](https://github.com/cyanheads/mcp-ts-core/issues/243)).
19
+ - **14 `skills/<name>/SKILL.md` re-synced** — picks up the framework's pre-0.9.18 body drift propagated via `metadata.version` bumps (`api-auth`, `api-errors`, `api-services`, `api-telemetry`, `field-test`, `report-issue-local`, `tool-defs-analysis`) plus the `git-wrapup` v1.4 / `orchestrations` v1.4 guidance updates (mcp-ts-core [#238](https://github.com/cyanheads/mcp-ts-core/issues/238)).
20
+
21
+ ## Dependency bumps
22
+
23
+ - `@cyanheads/mcp-ts-core` ^0.10.6 → ^0.10.9
24
+ - `@biomejs/biome` ^2.4.16 → ^2.5.0 (dev)
25
+ - `@types/node` ^25.9.3 → ^26.0.0 (dev)
26
+ - `vitest` ^4.1.8 → ^4.1.9 (dev)
@@ -0,0 +1,52 @@
1
+ ---
2
+ summary: "Reject impossible dates and sentinel error rows, fix null-Description and manufacturer/importer rendering, frame CPSC narrative as source text; mcp-ts-core ^0.11.0, TypeScript ^7.0.2"
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.1.5 — 2026-07-27
8
+
9
+ ## Added
10
+
11
+ - **Calendar-valid date filters** — `date_start`/`date_end` on `cpsc_search_recalls` now parse with `z.iso.date()` instead of a digit-shape regex, so `2026-99-99` and `2026-02-31` are rejected at input validation instead of reaching the upstream API ([#1](https://github.com/cyanheads/cpsc-recalls-mcp-server/issues/1)).
12
+ - **`invalid_date_range` error** — `date_start` later than `date_end` now throws a declared `ValidationError` naming both values instead of silently returning `no_results` ([#1](https://github.com/cyanheads/cpsc-recalls-mcp-server/issues/1)).
13
+ - **CPSC error-row rejection** — `CpscRecallService.fetchRecalls` now recognizes the one-row sentinel array CPSC substitutes for malformed requests (identifying fields null, upstream message in `Title`) and throws a non-retryable `serviceUnavailable` instead of crashing on `null.slice()` during normalization. Shared by `search`, `getByNumber`, and `getRecent` ([#1](https://github.com/cyanheads/cpsc-recalls-mcp-server/issues/1)).
14
+ - **`data_quality_notes`** — new array output field on all three tools recording upstream gaps (absent description, no hazard text, no product entries) observed directly from empty fields, with no judgement about the recall itself ([#3](https://github.com/cyanheads/cpsc-recalls-mcp-server/issues/3)).
15
+ - **`source_note`** — new string output field on all three tools stating that recall fields are relayed verbatim from CPSC, unedited and unverified, and that `cpsc_url` should be checked before a consumer-facing decision ([#3](https://github.com/cyanheads/cpsc-recalls-mcp-server/issues/3)).
16
+ - **Source-text framing** — `cpsc_get_recall.format()` renders relayed CPSC narrative (hazard, remedy, contact, description, injuries, retailers, image captions) as markdown blockquotes under `(CPSC source text)` headings, visually separated from server-authored guidance; `cpsc_search_recalls.format()` labels its per-record relayed block `CPSC source fields:` ([#3](https://github.com/cyanheads/cpsc-recalls-mcp-server/issues/3)).
17
+ - **`tests/services/cpsc-recall/cpsc-recall-service.test.ts`** — new service-level test file covering error-row rejection on the `search` and `getRecent` paths and a sparse-but-genuine record passing through untouched.
18
+
19
+ ## Changed
20
+
21
+ - **`description` is nullable** — `cpsc_get_recall`'s output field is now `z.string().nullable()` instead of a normalized empty string, so recall `04084` (a genuine record with no upstream description) returns instead of failing output validation. `format()` renders `_Not provided by CPSC._` for the null, empty, and whitespace-only cases ([#4](https://github.com/cyanheads/cpsc-recalls-mcp-server/issues/4)).
22
+ - **`RawRecall.Description`** — retyped `string | null` to match observed upstream reality.
23
+ - **Manufacturer and importer render as separate roles** — `cpsc_get_recall.format()` splits the merged `## Manufactured By / Imported By` section into `## Manufactured By` and `## Imported By`, each shown only when populated; `cpsc_search_recalls.format()` emits separate `**Manufacturer:**` / `**Importer:**` lines and joins multiple orgs within a role with `; ` instead of `, ` (CPSC org names contain commas, which made the merged `, `-joined line unparseable) ([#5](https://github.com/cyanheads/cpsc-recalls-mcp-server/issues/5)).
24
+ - **`cpsc_search_recalls.format()` product list** — joined with `; ` instead of `, ` for the same comma-collision reason.
25
+
26
+ ## Fixed
27
+
28
+ - **`cpsc_get_recall` returns recall `04084`** — previously failed output validation with "expected string, received null" ([#4](https://github.com/cyanheads/cpsc-recalls-mcp-server/issues/4)).
29
+ - **Impossible calendar dates no longer crash the search handler** — previously threw `null is not an object (evaluating 'r.RecallDate.slice')` when CPSC's sentinel row reached normalization ([#1](https://github.com/cyanheads/cpsc-recalls-mcp-server/issues/1)).
30
+ - **Manufacturer/importer attribution no longer collapses into one unparseable line** in `content[]` output ([#5](https://github.com/cyanheads/cpsc-recalls-mcp-server/issues/5)).
31
+
32
+ ## Dependencies
33
+
34
+ - `@cyanheads/mcp-ts-core` ^0.10.9 → ^0.11.0
35
+ - `typescript` ^6.0.3 → ^7.0.2 (dev)
36
+ - `@biomejs/biome` ^2.5.0 → ^2.5.5 (dev)
37
+ - `@types/node` ^26.0.0 → ^26.1.1 (dev)
38
+ - `tsc-alias` ^1.8.17 → ^1.9.1 (dev)
39
+ - `vitest` ^4.1.9 → ^4.1.10 (dev)
40
+ - `ignore` ^7.0.5 → ^7.0.6 (dev)
41
+ - `@socketsecurity/bun-security-scanner` ^1.1.2 added (dev) — Socket install-time scanner wired into `bunfig.toml`'s new `[install.security]` block
42
+ - Two all-transitive advisories remain with no in-range fix (`depcheck › minimatch › brace-expansion`, `@cyanheads/mcp-ts-core › @hono/node-server`); `devcheck`'s Security Audit step reports them as a warning with the run passing
43
+
44
+ ## Other
45
+
46
+ - `bunfig.toml` — `minimumReleaseAge` (3-day supply-chain delay) with `@cyanheads/mcp-ts-core` excluded for same-day framework adoption; Socket scanner wired via `[install.security]`.
47
+ - `Dockerfile` — pinned `oven/bun:1.3.14` (was floating `1.3`), BuildKit cache mounts on every `bun install` layer, `--ignore-scripts` on the OTel peer-dependency install.
48
+ - `.gitignore` — anchored `data/` to `/data/` so a nested `data/` directory in `src/` isn't accidentally ignored.
49
+ - `devcheck.config.json` — added `@socketsecurity/bun-security-scanner` to the outdated-check allowlist.
50
+ - Repo metadata sync: `package.json` description, author, and `packageManager` (`bun@1.3.14`); `manifest.json` gained a `license` field; both plugin manifests' descriptions updated to match.
51
+ - Added `.gitattributes` (LF normalization, binary asset handling, `linguist-generated` markers), `.github/SECURITY.md`, `.github/FUNDING.yml`.
52
+ - `scripts/`, `skills/`, `CLAUDE.md`/`AGENTS.md`, and `changelog/template.md` re-synced from the `@cyanheads/mcp-ts-core` 0.11.0 project template (new `lint:mcp`/`lint:packaging` standalone commands, `list-skills`, `ctx.enrich`/`ctx.content` docs, `api-mirror` skill, tightened `security:` changelog-flag guidance).
@@ -15,9 +15,11 @@ summary: ""
15
15
  # usage. Flagged as `Breaking` in the rollup.
16
16
  breaking: false
17
17
 
18
- # Set `true` if this release contains any security fix. Pairs with the
19
- # `## Security` section below. Flagged as `Security` in the rollup so
20
- # users can triage upgrade urgency at a glance.
18
+ # Set `true` ONLY for a security fix in THIS project's own source code — a
19
+ # vulnerability or hardening in code you ship. A dependency or transitive CVE
20
+ # bump is routine maintenance, NOT a security release: record it under
21
+ # `## Dependencies` (with the advisory ID) and leave this `false`. When true,
22
+ # pairs with the `## Security` section below and flags `Security` in the rollup.
21
23
  security: false
22
24
 
23
25
  # Optional free-form notes for maintenance agents processing this release.
@@ -11,7 +11,7 @@ export declare const cpscGetRecall: import("@cyanheads/mcp-ts-core").ToolDefinit
11
11
  recall_date: z.ZodString;
12
12
  last_updated: z.ZodString;
13
13
  title: z.ZodString;
14
- description: z.ZodString;
14
+ description: z.ZodNullable<z.ZodString>;
15
15
  cpsc_url: z.ZodString;
16
16
  consumer_contact: z.ZodNullable<z.ZodString>;
17
17
  hazards: z.ZodArray<z.ZodObject<{
@@ -35,7 +35,9 @@ export declare const cpscGetRecall: import("@cyanheads/mcp-ts-core").ToolDefinit
35
35
  caption: z.ZodString;
36
36
  }, z.core.$strip>>;
37
37
  coordinated_recalls: z.ZodArray<z.ZodString>;
38
+ data_quality_notes: z.ZodArray<z.ZodString>;
38
39
  cpsc_jurisdiction: z.ZodString;
40
+ source_note: z.ZodString;
39
41
  }, z.core.$strip>, readonly [{
40
42
  readonly reason: "not_found";
41
43
  readonly code: JsonRpcErrorCode.NotFound;
@@ -1 +1 @@
1
- {"version":3,"file":"cpsc-get-recall.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/cpsc-get-recall.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAQjE,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA8RxB,CAAC"}
1
+ {"version":3,"file":"cpsc-get-recall.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/cpsc-get-recall.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAmCjE,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAuWxB,CAAC"}