
Connects Claude to the U.S. Consumer Product Safety Commission's public recalls API with three focused tools: keyword search across products, brands, and retailers with optional date filters; full recall detail by recall number including hazards, remedies, and UPC codes; and a configurable recent recalls feed. Built on the mcp-ts-core framework with both stdio and streamable HTTP transports. Useful when you need to check if a consumer product has been recalled, surface safety issues for specific brands or importers, or monitor new CPSC actions. Jurisdiction is consumer products only, so food, vehicles, boats, and drugs are out of scope. No API key required since the CPSC endpoint is public.
Search and retrieve US consumer product recalls from the CPSC (Consumer Product Safety Commission) via MCP. STDIO or Streamable HTTP.
Public Hosted Server: https://cpsc-recalls.caseyjhand.com/mcp
Consumer product recalls from the CPSC saferproducts.gov database — toys, electronics, furniture, appliances, children's products, tools, and clothing. Search recalls by product, brand, retailer, or hazard, fetch full detail for a specific recall number, or pull a recent-recalls feed for a date window. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
| Tool | Description |
|---|---|
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 |
cpsc_get_recall | Full detail for a single recall by recall number — hazards, remedy, products, injuries, images, and the official CPSC page |
cpsc_get_recent | Fetch the most recent recalls ordered newest-first, scoped to a configurable date window |
CPSC jurisdiction is consumer products only — food/drugs (FDA), motor vehicles/tires (NHTSA), boats (USCG), pesticides (EPA), and firearms (ATF) are not in this database; every response carries a jurisdiction note.
cpsc_search_recalls tooltitle_search, usually the highest-signal, plus product_name, manufacturer, retailer, importer, distributor, description_search, remedy, hazard_search) and two date axes (date_start/date_end for the issue date, updated_start/updated_end for the last CPSC publish date); limit (1–200, default 20) and offset page through total_foundcpsc_url, and per-recall data_quality_notes; zero matches is an empty success whose effectiveQuery echoes the criteria and whose notice names the criterion to relax. Failures: missing_criteria, invalid_date_range, upstream_rejected (not retryable), upstream_error (retryable)has_more is the paging signal; a page cut short by the 64,000-byte response budget carries a notice with the offset to continue fromcpsc_get_recall toolrecall_number: 5 digits for 2002–present records ("25043"), or 5 digits plus a letter a–d for 1998–2001 records ("99003a")description (where model numbers usually live), hazards, remedy options and instructions, every product variant, UPCs, injury narrative, company names by role, country of manufacture, images, coordinated-agency recall URLs, and data_quality_notesnot_found (resolve a number through cpsc_search_recalls or cpsc_get_recent), upstream_rejected (not retryable), upstream_error (retryable)cpsc_get_recent tooldays look-back window (1–365, default 30) anchored to today; limit (1–100, default 20) and offset page through total_found, since narrowing days drops the oldest recalls rather than paging. Older recalls are reachable through cpsc_search_recalls date boundscpsc_url, data_quality_notes — for cpsc_get_recall to expand; has_more is the paging signal, and an empty window, an offset past the end, or a budget-cut page carries a notice. Failures: upstream_rejected (not retryable), upstream_error (retryable)Built on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.
CPSC-specific:
total_found and has_more stay accurate; hazard_search (alias hazard) covers hazard text, product names, and remedy instructionsAgent-friendly output:
source_note, cpsc_url, and (CPSC source text) blockquote labels distinguish relayed CPSC narrative from the server's own guidance&, <br>, stray table tags) are converted to plain text before matching and output, and content[] escapes Markdown so model and serial numbers such as 1HFVE05**K4000003 keep every character; recall page, image, and coordinated-recall addresses that contain spaces are percent-encoded in content[] so each renders as one working link, while structuredContent carries them as CPSC stores themtotal_found, offset, has_more, and truncated on every search/recent response so agents can tell when results are clipped and page with offset; each surface of a page stays within a 64,000-byte budget, and a notice gives the next offset when the budget ends a page earlydata_quality_notes on every response — gaps observed in the upstream record (missing hazard text, no product entries), derived from which fields CPSC left blank rather than any judgment callcpsc_jurisdiction) on every response — lets agents route callers to the correct agency (FDA, NHTSA, USCG, EPA, ATF) when a product is out of scopeA public instance is available at https://cpsc-recalls.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"cpsc-recalls-mcp-server": {
"type": "streamable-http",
"url": "https://cpsc-recalls.caseyjhand.com/mcp"
}
}
}
Add the following to your MCP client configuration file.
{
"mcpServers": {
"cpsc-recalls-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/cpsc-recalls-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"cpsc-recalls-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/cpsc-recalls-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"cpsc-recalls-mcp-server": {
"type": "stdio",
"command": "docker",
"args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT_TYPE=stdio", "ghcr.io/cyanheads/cpsc-recalls-mcp-server:latest"]
}
}
}
For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcp
git clone https://github.com/cyanheads/cpsc-recalls-mcp-server.git
cd cpsc-recalls-mcp-server
bun install
cp .env.example .env
# edit .env if needed — no required API keys
All configuration is validated at startup via Zod schemas. Key environment variables:
| Variable | Description | Default |
|---|---|---|
MCP_TRANSPORT_TYPE | Transport: stdio or http | stdio |
MCP_HTTP_PORT | HTTP server port | 3010 |
MCP_HTTP_HOST | HTTP server host | 127.0.0.1 |
MCP_HTTP_ENDPOINT_PATH | HTTP endpoint path | /mcp |
MCP_PUBLIC_URL | Public origin for TLS-terminating reverse-proxy deployments | — |
MCP_SESSION_MODE | Session handling: auto, stateful, or stateless. The server declares stateless in src/index.ts; setting this overrides it. | stateless |
MCP_AUTH_MODE | Authentication: none, jwt, or oauth | none |
MCP_LOG_LEVEL | Log level (debug, info, warning, error) | info |
LOGS_DIR | Directory for log files (Node.js only) | <project-root>/logs |
LOG_TOOL_FAILURE_PAYLOADS | Log each failed tool call's arguments and result, redacted by key name and capped at LOG_TOOL_FAILURE_PAYLOAD_MAX_BYTES (default 16384). A secret inside a free-form value is not redacted. | false |
STORAGE_PROVIDER_TYPE | Storage backend: in-memory, filesystem, supabase, cloudflare-kv/r2/d1 | in-memory |
OTEL_ENABLED | Enable OpenTelemetry | false |
No server-specific API keys are required. See .env.example for the full list of optional overrides.
Build and run the production version:
# One-time build
bun run rebuild
# Run the built server
bun run start:stdio
# or
bun run start:http
Run checks and tests:
bun run devcheck # Lint, format, typecheck, security
bun run test # Vitest test suite
docker build -t cpsc-recalls-mcp-server .
docker run --rm -p 3010:3010 cpsc-recalls-mcp-server
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/cpsc-recalls-mcp-server. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.
| Directory | Purpose |
|---|---|
src/index.ts | createApp() entry point — registers tools and inits the CPSC service |
src/mcp-server/tools | Tool definitions (definitions/*.tool.ts) — search, get-recall, get-recent — plus response-budget.ts, the page size budget the two list tools share, and markdown-escape.ts, the escaping applied to CPSC text in content[] |
src/services/cpsc-recall | CPSC recall service — API client, types, and normalize-text.ts, which converts CPSC's HTML markup and character codes to plain text |
See CLAUDE.md / AGENTS.md for development guidelines and architectural rules. The short version:
try/catch in tool logicctx.log for request-scoped logging, ctx.state for tenant-scoped storagecreateApp() arrays in src/index.tsIssues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
Apache-2.0 — see LICENSE for details.
MCP_LOG_LEVELdefault: infoSets the minimum log level for output (e.g., 'debug', 'info', 'warn').
MCP_HTTP_HOSTdefault: 127.0.0.1The hostname for the HTTP server.
MCP_HTTP_PORTdefault: 3010The port to run the HTTP server on.
MCP_HTTP_ENDPOINT_PATHdefault: /mcpThe endpoint path for the MCP server.
MCP_AUTH_MODEdefault: noneAuthentication mode to use: 'none', 'jwt', or 'oauth'.