<div align="center"> <h1>@cyanheads/cpsc-recalls-mcp-server</h1> <p><b>Search and retrieve US consumer product recalls from the CPSC (Consumer Product Safety Commission) via MCP. STDIO or Streamable HTTP.</b> <div>3 Tools</div> </p> </div>
<div align="center">
      
</div>
<div align="center">
  

</div>
<div align="center">
Public Hosted Server: https://cpsc-recalls.caseyjhand.com/mcp
</div>
---
Tools
3 tools for searching and retrieving CPSC consumer product recall data:
| 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: Consumer products only — toys, electronics, furniture, appliances, children's products, tools, and clothing. Food and drugs (FDA), motor vehicles and tires (NHTSA), boats (USCG), and pesticides (EPA) are not in this database. Every response includes a jurisdiction note.
Data sourced from the U.S. Consumer Product Safety Commission via the CPSC public recalls API.
cpsc_search_recalls
Search recalls with flexible filtering across title, product, organization, hazard, remedy, and description fields.
- Filter by
title_search,product_name,manufacturer,retailer,importer,distributor,remedy, ordescription_search— all optional substring matches, applied upstream, combining with AND title_searchis usually the highest-signal filter: CPSC titles name the brand, the product, and the hazardhazard_searchis 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 upstreamHazardparameter is recognized but never matches, so it is not exposedremedysearches the free-text remedy instructions, not theremedy_optionstype enum —remedy: "repair"matches records whoseremedy_optionslist onlyRefundbut whose instructions describe a free repair kit- Two independent date axes:
date_start/date_endbound the recall issue date,updated_start/updated_endbound the date CPSC last published the record. A 2003 recall re-published in 2025 matchesupdated_start: "2025-01-01". All four must be real calendar dates —2026-02-31and2026-99-99are rejected at input validation, and a reversed range fails withinvalid_date_rangerather than silently returning nothing - Client-side limit (1–200, default 20) and
offset(default 0) applied after fetching all matching records. Page withoffset: 0, 20, 40; an offset at or pasttotal_foundreturns an empty result set rather than an error total_foundcounts matches afterhazard_searchis applied and beforeoffset/limitnarrow the window;has_moreis the paging signal;truncatedstays limit-only and does not move withoffset- Returns hazard descriptions, remedy options, remedy instructions, product list, UPCs, manufacturer/importer/retailer names, images, and the CPSC recall page URL
- Manufacturer and importer are reported and rendered as separate roles — CPSC org names contain commas, so multiple orgs in one role are separated with
; - Per-recall
data_quality_notesrecords gaps in the upstream record (no hazard text, no product entries); empty when nothing is missing - When
manufacturerreturns no results, tryimporter,retailer, ordistributor— many recalls list one of those as the primary organization - Deterministic upstream rejections surface as
upstream_rejected(retryable: false) and carry the CPSC message; transient outages stay onupstream_error(retryable: true)
---
cpsc_get_recall
Full detail for a single CPSC recall by recall number.
- Accepts modern 5-digit recall numbers (e.g.
"25043") and historical 1998–2001 records with letter suffixes (e.g."99003a") - 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
- Model numbers are typically embedded in the description text, not in a structured field
descriptionis 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- Manufacturer and importer are rendered under separate headings, so role attribution survives into
content[] data_quality_notesrecords gaps in the upstream record (absent description, no hazard text, no product entries); empty when nothing is missing- Use
cpsc_search_recallsorcpsc_get_recentto find a recall number first
---
cpsc_get_recent
Fetch the most recent CPSC recalls, ordered newest-first.
- Configurable look-back window of 1–365 days (default 30)
- Limit of 1–100 results (default 20), with
offset(default 0) to page throughtotal_found. Narrowingdayscannot page — the window is anchored to today, so shrinking it drops the oldest records rather than advancing past the newest has_moreis the paging signal;truncatedstays limit-only and does not move withoffset- Always applies a date window — without one, the upstream API returns 9,800+ records
- Returns a lightweight record per recall: number, date, title, hazards, remedy types, product names, and CPSC URL
- Per-recall
data_quality_notesrecords gaps in the upstream record (no hazard text, no product entries); empty when nothing is missing - Use
cpsc_get_recallto retrieve full detail for any result
Features
Built on @cyanheads/mcp-ts-core:
- Declarative tool definitions — single file per tool, framework handles registration and validation
- Unified error handling — handlers throw, framework catches, classifies, and formats
- Pluggable auth:
none,jwt,oauth - Swappable storage backends:
in-memory,filesystem,Supabase,Cloudflare KV/R2/D1 - Structured logging with optional OpenTelemetry tracing
- STDIO and Streamable HTTP transports
CPSC-specific:
- Full integration with the CPSC saferproducts.gov public recalls API
- Client-side filtering applied over complete API result sets for accurate
total_foundcounts - Jurisdiction boundary documented in every response — prevents misattribution of food, vehicle, or drug recalls
Agent-friendly output:
- Jurisdiction note on every response — agents can route callers to the correct agency (FDA, NHTSA, USCG, EPA) when the product is out of scope
total_found,offset,has_more, andtruncatedfields on search/recent responses — agents can tell when results are clipped and page through the rest withoffsetcpsc_urlon every recall — authoritative source link for consumer verificationsource_noteon 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 guidancedata_quality_noteson every response — gaps observed in the upstream record, derived from which fields CPSC left empty rather than any judgement about the recall
Getting started
Public Hosted Instance
A 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"
}
}
}
Self-Hosted / Local
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
Prerequisites
- Bun v1.3.0 or higher (or Node.js v24+).
- No API key required — the CPSC public recalls API is freely accessible.
Installation
- Clone the repository:
git clone https://github.com/cyanheads/cpsc-recalls-mcp-server.git
- Navigate into the directory:
cd cpsc-recalls-mcp-server
- Install dependencies:
bun install
- Configure environment:
cp .env.example .env
# edit .env if needed — no required API keys
Configuration
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_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 | | 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.
Running the server
Local development
- 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
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.
Project structure
| Directory | Purpose | |:---|:---| | src/index.ts | createApp() entry point — registers tools and inits the CPSC service | | src/mcp-server/tools | Tool definitions (*.tool.ts). Three tools: search, get-recall, get-recent | | src/services/cpsc-recall | CPSC recall service — API client, types, normalization |
Development guide
See CLAUDE.md / AGENTS.md for development guidelines and architectural rules. The short version:
- Handlers throw, framework catches — no
try/catchin tool logic - Use
ctx.logfor request-scoped logging,ctx.statefor tenant-scoped storage - Register new tools in the
createApp()arrays insrc/index.ts - Wrap external API calls: validate raw → normalize to domain type → return output schema; never fabricate missing fields
Contributing
Issues and pull requests are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
License
Apache-2.0 — see LICENSE for details.











