@cyanheads/cpsc-recalls-mcp-server 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +40 -18
- package/CLAUDE.md +40 -18
- package/Dockerfile +11 -7
- package/README.md +23 -11
- package/changelog/0.1.x/0.1.5.md +52 -0
- package/changelog/0.1.x/0.1.6.md +29 -0
- package/changelog/template.md +5 -3
- package/dist/mcp-server/tools/definitions/cpsc-get-recall.tool.d.ts +10 -2
- package/dist/mcp-server/tools/definitions/cpsc-get-recall.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/cpsc-get-recall.tool.js +128 -36
- package/dist/mcp-server/tools/definitions/cpsc-get-recall.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/cpsc-get-recent.tool.d.ts +12 -1
- package/dist/mcp-server/tools/definitions/cpsc-get-recent.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/cpsc-get-recent.tool.js +95 -20
- package/dist/mcp-server/tools/definitions/cpsc-get-recent.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/cpsc-search-recalls.tool.d.ts +27 -5
- package/dist/mcp-server/tools/definitions/cpsc-search-recalls.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/cpsc-search-recalls.tool.js +204 -59
- package/dist/mcp-server/tools/definitions/cpsc-search-recalls.tool.js.map +1 -1
- package/dist/services/cpsc-recall/cpsc-recall-service.d.ts.map +1 -1
- package/dist/services/cpsc-recall/cpsc-recall-service.js +31 -0
- package/dist/services/cpsc-recall/cpsc-recall-service.js.map +1 -1
- package/dist/services/cpsc-recall/types.d.ts +25 -2
- package/dist/services/cpsc-recall/types.d.ts.map +1 -1
- package/package.json +12 -11
- 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.
|
|
5
|
-
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.
|
|
4
|
+
**Version:** 0.1.6
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
-
`
|
|
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
|
|
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.
|
|
5
|
-
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.
|
|
4
|
+
**Version:** 0.1.6
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
-
`
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
59
|
-
|
|
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
|
-
[](./CHANGELOG.md) [](./LICENSE) [](https://github.com/users/cyanheads/packages/container/package/cpsc-recalls-mcp-server) [](https://modelcontextprotocol.io/) [](https://www.npmjs.com/package/@cyanheads/cpsc-recalls-mcp-server) [](https://www.typescriptlang.org/) [](https://bun.sh/)
|
|
11
11
|
|
|
12
12
|
</div>
|
|
13
13
|
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
|
|
34
34
|
| Tool | Description |
|
|
35
35
|
|:---|:---|
|
|
36
|
-
| `cpsc_search_recalls` | Search consumer product recalls by product name, brand, retailer, importer, or description keyword, with optional date filtering |
|
|
36
|
+
| `cpsc_search_recalls` | Search consumer product recalls by title, product name, brand, retailer, importer, distributor, hazard, remedy, or description keyword, with optional date filtering and offset paging |
|
|
37
37
|
| `cpsc_get_recall` | Full detail for a single recall by recall number — hazards, remedy, products, injuries, images, and the official CPSC page |
|
|
38
38
|
| `cpsc_get_recent` | Fetch the most recent recalls ordered newest-first, scoped to a configurable date window |
|
|
39
39
|
|
|
@@ -43,15 +43,20 @@ Data sourced from the [U.S. Consumer Product Safety Commission](https://www.safe
|
|
|
43
43
|
|
|
44
44
|
### `cpsc_search_recalls`
|
|
45
45
|
|
|
46
|
-
Search recalls with flexible filtering across product, organization, and description fields.
|
|
46
|
+
Search recalls with flexible filtering across title, product, organization, hazard, remedy, and description fields.
|
|
47
47
|
|
|
48
|
-
- Filter by
|
|
49
|
-
-
|
|
50
|
-
-
|
|
48
|
+
- Filter by `title_search`, `product_name`, `manufacturer`, `retailer`, `importer`, `distributor`, `remedy`, or `description_search` — all optional substring matches, applied upstream, combining with AND
|
|
49
|
+
- `title_search` is usually the highest-signal filter: CPSC titles name the brand, the product, and the hazard
|
|
50
|
+
- `hazard_search` is a client-side filter applied after the upstream fetch — it matches when the term appears in hazard descriptions, product names, **or** remedy instructions (case-insensitive). Use it for hazard concepts like "fire", "choking", or "burn"; the upstream `Hazard` parameter is recognized but never matches, so it is not exposed
|
|
51
|
+
- `remedy` searches the free-text remedy instructions, not the `remedy_options` type enum — `remedy: "repair"` matches records whose `remedy_options` list only `Refund` but whose instructions describe a free repair kit
|
|
52
|
+
- Two independent date axes: `date_start` / `date_end` bound the recall issue date, `updated_start` / `updated_end` bound the date CPSC last published the record. A 2003 recall re-published in 2025 matches `updated_start: "2025-01-01"`. All four must be real calendar dates — `2026-02-31` and `2026-99-99` are rejected at input validation, and a reversed range fails with `invalid_date_range` rather than silently returning nothing
|
|
53
|
+
- Client-side limit (1–200, default 20) and `offset` (default 0) applied after fetching all matching records. Page with `offset: 0, 20, 40`; an offset at or past `total_found` returns an empty result set rather than an error
|
|
54
|
+
- `total_found` counts matches after `hazard_search` is applied and before `offset`/`limit` narrow the window; `has_more` is the paging signal; `truncated` stays limit-only and does not move with `offset`
|
|
51
55
|
- Returns hazard descriptions, remedy options, remedy instructions, product list, UPCs, manufacturer/importer/retailer names, images, and the CPSC recall page URL
|
|
52
|
-
-
|
|
53
|
-
-
|
|
54
|
-
- When `manufacturer` returns no results, try `importer` or `
|
|
56
|
+
- Manufacturer and importer are reported and rendered as separate roles — CPSC org names contain commas, so multiple orgs in one role are separated with `; `
|
|
57
|
+
- Per-recall `data_quality_notes` records gaps in the upstream record (no hazard text, no product entries); empty when nothing is missing
|
|
58
|
+
- When `manufacturer` returns no results, try `importer`, `retailer`, or `distributor` — many recalls list one of those as the primary organization
|
|
59
|
+
- Deterministic upstream rejections surface as `upstream_rejected` (`retryable: false`) and carry the CPSC message; transient outages stay on `upstream_error` (`retryable: true`)
|
|
55
60
|
|
|
56
61
|
---
|
|
57
62
|
|
|
@@ -62,6 +67,9 @@ Full detail for a single CPSC recall by recall number.
|
|
|
62
67
|
- Accepts modern 5-digit recall numbers (e.g. `"25043"`) and historical 1998–2001 records with letter suffixes (e.g. `"99003a"`)
|
|
63
68
|
- 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
69
|
- Model numbers are typically embedded in the description text, not in a structured field
|
|
70
|
+
- `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
|
|
71
|
+
- Manufacturer and importer are rendered under separate headings, so role attribution survives into `content[]`
|
|
72
|
+
- `data_quality_notes` records gaps in the upstream record (absent description, no hazard text, no product entries); empty when nothing is missing
|
|
65
73
|
- Use `cpsc_search_recalls` or `cpsc_get_recent` to find a recall number first
|
|
66
74
|
|
|
67
75
|
---
|
|
@@ -71,9 +79,11 @@ Full detail for a single CPSC recall by recall number.
|
|
|
71
79
|
Fetch the most recent CPSC recalls, ordered newest-first.
|
|
72
80
|
|
|
73
81
|
- Configurable look-back window of 1–365 days (default 30)
|
|
74
|
-
- Limit of 1–100 results (default 20)
|
|
82
|
+
- Limit of 1–100 results (default 20), with `offset` (default 0) to page through `total_found`. Narrowing `days` cannot page — the window is anchored to today, so shrinking it drops the oldest records rather than advancing past the newest
|
|
83
|
+
- `has_more` is the paging signal; `truncated` stays limit-only and does not move with `offset`
|
|
75
84
|
- Always applies a date window — without one, the upstream API returns 9,800+ records
|
|
76
85
|
- Returns a lightweight record per recall: number, date, title, hazards, remedy types, product names, and CPSC URL
|
|
86
|
+
- Per-recall `data_quality_notes` records gaps in the upstream record (no hazard text, no product entries); empty when nothing is missing
|
|
77
87
|
- Use `cpsc_get_recall` to retrieve full detail for any result
|
|
78
88
|
|
|
79
89
|
## Features
|
|
@@ -96,8 +106,10 @@ CPSC-specific:
|
|
|
96
106
|
Agent-friendly output:
|
|
97
107
|
|
|
98
108
|
- 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
|
-
- `total_found`
|
|
109
|
+
- `total_found`, `offset`, `has_more`, and `truncated` fields on search/recent responses — agents can tell when results are clipped and page through the rest with `offset`
|
|
100
110
|
- `cpsc_url` on every recall — authoritative source link for consumer verification
|
|
111
|
+
- `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
|
|
112
|
+
- `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
113
|
|
|
102
114
|
## Getting started
|
|
103
115
|
|
|
@@ -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).
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Add title/distributor/remedy/publish-date filters and client-side hazard search to cpsc_search_recalls, offset paging with has_more on search and recent, and a non-retryable upstream_rejected error distinct from transient failures"
|
|
3
|
+
breaking: false
|
|
4
|
+
security: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 0.1.6 — 2026-07-27
|
|
8
|
+
|
|
9
|
+
## Added
|
|
10
|
+
|
|
11
|
+
- **`title_search`, `distributor`, `remedy`, `updated_start`/`updated_end`** — four new `cpsc_search_recalls` filters mapped to verified-working upstream parameters (`RecallTitle`, `Distributor`, `Remedy`, `LastPublishDateStart`/`End`), forwarded through `CpscSearchParams` and `CpscRecallService.search()`'s param-whitelist object literal ([#6](https://github.com/cyanheads/cpsc-recalls-mcp-server/issues/6)). `remedy` matches the free-text remedy narrative, not the `remedy_options` type enum. `updated_start`/`updated_end` bound `LastPublishDate`, a separate axis from `date_start`/`date_end`'s recall-issue date.
|
|
12
|
+
- **`invalid_date_range` now also covers `updated_start`/`updated_end`** — a reversed publish-date range throws the same validation error as a reversed recall-date range, instead of silently returning nothing ([#6](https://github.com/cyanheads/cpsc-recalls-mcp-server/issues/6)).
|
|
13
|
+
- **`hazard_search`** on `cpsc_search_recalls` — client-side, case-insensitive OR filter across `hazards`, `products[].name`, and `remedy_summary`, applied to the full normalized array before `total_found`/`offset`/`limit` narrow it ([#2](https://github.com/cyanheads/cpsc-recalls-mcp-server/issues/2)). Replaces the prior guidance to use `description_search` for hazard terms, which only reached the upstream `RecallDescription` field.
|
|
14
|
+
- **`offset` and `has_more`** on `cpsc_search_recalls` and `cpsc_get_recent` — pages past the existing `limit` ceiling; `has_more` is `offset + returned < total_found` and is independent of `truncated`, which stays limit-only ([#7](https://github.com/cyanheads/cpsc-recalls-mcp-server/issues/7)). An offset at or past `total_found` returns an empty result set rather than a `no_results` error.
|
|
15
|
+
|
|
16
|
+
## Changed
|
|
17
|
+
|
|
18
|
+
- **`no_results` on `cpsc_search_recalls`** moved off the raw pre-filter count — it now throws on the count after `hazard_search` runs (and before the `offset`/`limit` window), so a filter that legitimately empties the set still reports correctly ([#2](https://github.com/cyanheads/cpsc-recalls-mcp-server/issues/2), [#7](https://github.com/cyanheads/cpsc-recalls-mcp-server/issues/7)).
|
|
19
|
+
- **`remedy_options` output description** on all three tools now lists `Inspect` and `No Remedy Available` alongside the previous six values, and notes the field is often empty (CPSC classifies remedy on fewer than half its records) with a fallback to `remedy_summary`/`remedy_instructions`, then `cpsc_url`.
|
|
20
|
+
- **`format()` footers** on `cpsc_search_recalls` and `cpsc_get_recent` now report the current offset and a "More available — repeat with offset N" line when `has_more` is true.
|
|
21
|
+
- **Tool descriptions and README** — the "`Hazard` filter is non-functional, use `description_search`" framing is gone; hazard concepts route to `hazard_search`, the recall headline to `title_search`, and `description_search` is rescoped to product-detail text (model numbers, colors, sale channels).
|
|
22
|
+
|
|
23
|
+
## Fixed
|
|
24
|
+
|
|
25
|
+
- **`upstream_rejected` error reason** (`ServiceUnavailable`, `retryable: false`) added to all three tools, distinct from `upstream_error` ([#8](https://github.com/cyanheads/cpsc-recalls-mcp-server/issues/8)). Previously every service error — deterministic CPSC rejection or transient outage alike — was reported as `upstream_error` with `retryable: true`, so a client honoring the recovery hint retried a request that could never succeed. Each tool's catch block now routes on `err.data.retryable === false`; the caught error's own message reaches the client on both arms instead of a fixed `CPSC API request failed.` string.
|
|
26
|
+
|
|
27
|
+
## Other
|
|
28
|
+
|
|
29
|
+
- **`docs/design.md`** reconciled to as-built: entries the shipped code now contradicts (the `Hazard`/`description_search` framing, "no pagination") carry a **Superseded** note rather than being rewritten, so the pre-build rationale stays legible alongside the current behavior.
|
package/changelog/template.md
CHANGED
|
@@ -15,9 +15,11 @@ summary: ""
|
|
|
15
15
|
# usage. Flagged as `Breaking` in the rollup.
|
|
16
16
|
breaking: false
|
|
17
17
|
|
|
18
|
-
# Set `true`
|
|
19
|
-
#
|
|
20
|
-
#
|
|
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;
|
|
@@ -44,8 +46,14 @@ export declare const cpscGetRecall: import("@cyanheads/mcp-ts-core").ToolDefinit
|
|
|
44
46
|
}, {
|
|
45
47
|
readonly reason: "upstream_error";
|
|
46
48
|
readonly code: JsonRpcErrorCode.ServiceUnavailable;
|
|
47
|
-
readonly when: "The saferproducts.gov API returned
|
|
49
|
+
readonly when: "The saferproducts.gov API returned a transient error or timed out";
|
|
48
50
|
readonly recovery: "The CPSC API is occasionally unavailable. Retry in a few seconds.";
|
|
49
51
|
readonly retryable: true;
|
|
52
|
+
}, {
|
|
53
|
+
readonly reason: "upstream_rejected";
|
|
54
|
+
readonly code: JsonRpcErrorCode.ServiceUnavailable;
|
|
55
|
+
readonly when: "The saferproducts.gov API answered with an error row instead of a recall record, which the same request will always produce";
|
|
56
|
+
readonly recovery: "Do not retry this request unchanged — it fails deterministically. Read the message for what CPSC rejected, then confirm the recall number with cpsc_search_recalls.";
|
|
57
|
+
readonly retryable: false;
|
|
50
58
|
}], undefined>;
|
|
51
59
|
//# sourceMappingURL=cpsc-get-recall.tool.d.ts.map
|