@cyanheads/cpsc-recalls-mcp-server 0.1.8 → 0.2.1
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 +10 -6
- package/CLAUDE.md +10 -6
- package/README.md +16 -13
- package/changelog/0.2.x/0.2.0.md +31 -0
- package/changelog/0.2.x/0.2.1.md +19 -0
- package/dist/index.js +1 -5
- package/dist/index.js.map +1 -1
- package/dist/mcp-server/tools/definitions/cpsc-get-recall.tool.d.ts +4 -4
- 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 +36 -52
- 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 +6 -4
- 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 +145 -116
- 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 +12 -9
- 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 +369 -245
- package/dist/mcp-server/tools/definitions/cpsc-search-recalls.tool.js.map +1 -1
- package/dist/mcp-server/tools/markdown-escape.d.ts +29 -0
- package/dist/mcp-server/tools/markdown-escape.d.ts.map +1 -0
- package/dist/mcp-server/tools/markdown-escape.js +93 -0
- package/dist/mcp-server/tools/markdown-escape.js.map +1 -0
- package/dist/mcp-server/tools/response-budget.d.ts +34 -0
- package/dist/mcp-server/tools/response-budget.d.ts.map +1 -0
- package/dist/mcp-server/tools/response-budget.js +53 -0
- package/dist/mcp-server/tools/response-budget.js.map +1 -0
- package/dist/services/cpsc-recall/cpsc-recall-service.d.ts +15 -1
- package/dist/services/cpsc-recall/cpsc-recall-service.d.ts.map +1 -1
- package/dist/services/cpsc-recall/cpsc-recall-service.js +57 -17
- package/dist/services/cpsc-recall/cpsc-recall-service.js.map +1 -1
- package/dist/services/cpsc-recall/normalize-text.d.ts +18 -0
- package/dist/services/cpsc-recall/normalize-text.d.ts.map +1 -0
- package/dist/services/cpsc-recall/normalize-text.js +108 -0
- package/dist/services/cpsc-recall/normalize-text.js.map +1 -0
- package/dist/services/cpsc-recall/types.d.ts +2 -1
- package/dist/services/cpsc-recall/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/server.json +3 -3
|
@@ -34,14 +34,16 @@ export declare const cpscGetRecent: import("@cyanheads/mcp-ts-core").ToolDefinit
|
|
|
34
34
|
}, z.core.$strip>, readonly [{
|
|
35
35
|
readonly reason: "upstream_error";
|
|
36
36
|
readonly code: JsonRpcErrorCode.ServiceUnavailable;
|
|
37
|
-
readonly when: "The saferproducts.gov
|
|
38
|
-
readonly recovery: "The CPSC
|
|
37
|
+
readonly when: "The CPSC recall service (saferproducts.gov) was unavailable, timed out, reported a temporary failure reading its recall data, or sent an unreadable response";
|
|
38
|
+
readonly recovery: "The CPSC recall service is occasionally unavailable. Retry in a few seconds.";
|
|
39
39
|
readonly retryable: true;
|
|
40
40
|
}, {
|
|
41
41
|
readonly reason: "upstream_rejected";
|
|
42
42
|
readonly code: JsonRpcErrorCode.ServiceUnavailable;
|
|
43
|
-
readonly when: "
|
|
43
|
+
readonly when: "CPSC rejected the request instead of returning recalls, and it rejects the same request every time";
|
|
44
44
|
readonly recovery: "Do not retry this request unchanged — it fails deterministically. Read the message for what CPSC rejected, then try a different days window.";
|
|
45
45
|
readonly retryable: false;
|
|
46
|
-
}],
|
|
46
|
+
}], {
|
|
47
|
+
readonly notice: z.ZodOptional<z.ZodString>;
|
|
48
|
+
}>;
|
|
47
49
|
//# sourceMappingURL=cpsc-get-recent.tool.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cpsc-get-recent.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/cpsc-get-recent.tool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAY,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"cpsc-get-recent.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/cpsc-get-recent.tool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAY,MAAM,+BAA+B,CAAC;AAsI3E,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkMxB,CAAC"}
|
|
@@ -5,32 +5,113 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { tool, z } from '@cyanheads/mcp-ts-core';
|
|
7
7
|
import { JsonRpcErrorCode, McpError } from '@cyanheads/mcp-ts-core/errors';
|
|
8
|
+
import { linkDestination, escapeMarkdown as md } from '../markdown-escape.js';
|
|
9
|
+
import { budgetCutNotice, countWithinBudget, pageOverheadBytes, } from '../response-budget.js';
|
|
8
10
|
import { getCpscRecallService } from '../../../services/cpsc-recall/cpsc-recall-service.js';
|
|
9
11
|
/** Static jurisdiction note included in every response. */
|
|
10
|
-
const JURISDICTION = 'CPSC covers consumer products — toys, electronics, furniture, appliances, tools, clothing. '
|
|
11
|
-
'Does NOT cover: food/drugs (FDA), motor vehicles/tires (NHTSA), boats (USCG), pesticides (EPA), firearms (ATF).';
|
|
12
|
+
const JURISDICTION = 'CPSC covers consumer products — toys, electronics, furniture, appliances, tools, clothing. Does NOT cover: food/drugs (FDA), motor vehicles/tires (NHTSA), boats (USCG), pesticides (EPA), firearms (ATF).';
|
|
12
13
|
/** Static provenance caveat included in every response. */
|
|
13
|
-
const SOURCE_NOTE = 'Recall fields are
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
const SOURCE_NOTE = 'Recall fields are CPSC record text, with HTML markup and character codes converted to plain text; this server does not otherwise edit or verify them. CPSC records occasionally carry missing or inconsistent text. Check cpsc_url before acting on a recall for a consumer-facing decision.';
|
|
15
|
+
/** The longest window `days` accepts. */
|
|
16
|
+
const MAX_DAYS = 365;
|
|
16
17
|
/** Format a Date as "YYYY-MM-DD". */
|
|
17
18
|
function toIsoDate(d) {
|
|
18
19
|
return d.toISOString().slice(0, 10);
|
|
19
20
|
}
|
|
21
|
+
const recentRecallSchema = z
|
|
22
|
+
.object({
|
|
23
|
+
recall_number: z
|
|
24
|
+
.string()
|
|
25
|
+
.describe('Recall identifier. Pass to cpsc_get_recall for full detail.'),
|
|
26
|
+
recall_date: z.string().describe('Date issued, ISO 8601.'),
|
|
27
|
+
title: z.string().describe('Recall title.'),
|
|
28
|
+
hazards: z.array(z.string().describe('Hazard description.')).describe('What is dangerous.'),
|
|
29
|
+
remedy_options: z
|
|
30
|
+
.array(z.string().describe('Remedy type.'))
|
|
31
|
+
.describe('Remedy types: Refund, Repair, Replace, New Instructions, Dispose, Label, No Remedy Available, Inspect. Multiple may apply. Often empty — CPSC classified the remedy on fewer than half its records; call cpsc_get_recall for the remedy instructions when this is empty.'),
|
|
32
|
+
products: z.array(z.string().describe('Product name.')).describe('Product names recalled.'),
|
|
33
|
+
cpsc_url: z.string().describe('Official CPSC recall page URL.'),
|
|
34
|
+
data_quality_notes: z
|
|
35
|
+
.array(z.string().describe('One gap found in the CPSC record.'))
|
|
36
|
+
.describe('Gaps this server observed in the CPSC record — absent hazard text, absent product entries. Derived from which fields CPSC left empty, not from any judgement about the recall itself. Empty when nothing is missing.'),
|
|
37
|
+
})
|
|
38
|
+
.describe('A recent CPSC recall.');
|
|
39
|
+
/** Shapes one upstream record for the response. */
|
|
40
|
+
function toRecentRecall(r) {
|
|
41
|
+
const hazards = r.Hazards.map((h) => h.Name).filter(Boolean);
|
|
42
|
+
const products = r.Products.map((p) => p.Name).filter(Boolean);
|
|
43
|
+
const data_quality_notes = [];
|
|
44
|
+
if (hazards.length === 0) {
|
|
45
|
+
data_quality_notes.push('CPSC listed no hazard description for this recall.');
|
|
46
|
+
}
|
|
47
|
+
if (products.length === 0) {
|
|
48
|
+
data_quality_notes.push('CPSC listed no product entries for this recall.');
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
recall_number: r.RecallNumber,
|
|
52
|
+
recall_date: r.RecallDate.slice(0, 10),
|
|
53
|
+
title: r.Title,
|
|
54
|
+
hazards,
|
|
55
|
+
remedy_options: r.RemedyOptions.map((o) => o.Option).filter(Boolean),
|
|
56
|
+
products,
|
|
57
|
+
cpsc_url: r.URL,
|
|
58
|
+
data_quality_notes,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* One recall's `content[]` block. `format()` and the response budget both call it, so the
|
|
63
|
+
* bytes charged for a record are the bytes rendered for it — Markdown escaping included,
|
|
64
|
+
* which is why the escaping happens here rather than in `format()`.
|
|
65
|
+
*/
|
|
66
|
+
function renderRecallBlock(r) {
|
|
67
|
+
const hazardText = r.hazards.length > 0 ? md(r.hazards.join('; ')) : 'Not specified';
|
|
68
|
+
const remedyText = r.remedy_options.length > 0 ? md(r.remedy_options.join(', ')) : 'Not specified';
|
|
69
|
+
const productText = r.products.length > 0 ? md(r.products.join(', ')) : 'Not specified';
|
|
70
|
+
const lines = [
|
|
71
|
+
`**${r.recall_date}** — [${r.recall_number}] ${md(r.title)}`,
|
|
72
|
+
'CPSC source fields:',
|
|
73
|
+
`Hazard: ${hazardText} | Remedy: ${remedyText}`,
|
|
74
|
+
`Products: ${productText}`,
|
|
75
|
+
`[CPSC page](${linkDestination(r.cpsc_url)})`,
|
|
76
|
+
];
|
|
77
|
+
if (r.data_quality_notes.length > 0) {
|
|
78
|
+
lines.push(`**Data quality (server-assessed):** ${r.data_quality_notes.join(' ')}`);
|
|
79
|
+
}
|
|
80
|
+
lines.push('---');
|
|
81
|
+
return lines.join('\n');
|
|
82
|
+
}
|
|
83
|
+
/** The text before the record blocks, for a page that returned `returned` records. */
|
|
84
|
+
function renderHeader(page, returned) {
|
|
85
|
+
const { start, end, days } = page.period;
|
|
86
|
+
const truncNote = page.truncated ? ' (truncated)' : '';
|
|
87
|
+
const windowNote = page.truncated || page.offset > 0
|
|
88
|
+
? `, showing ${returned} from offset ${page.offset}${truncNote}`
|
|
89
|
+
: '';
|
|
90
|
+
const lines = [
|
|
91
|
+
`# Recent CPSC Recalls — ${start} to ${end} (${days} day${days === 1 ? '' : 's'})`,
|
|
92
|
+
'',
|
|
93
|
+
`Found ${page.total_found} recall${page.total_found !== 1 ? 's' : ''}${windowNote}.`,
|
|
94
|
+
];
|
|
95
|
+
if (page.has_more) {
|
|
96
|
+
lines.push(`More available — repeat with offset ${page.offset + returned}.`);
|
|
97
|
+
}
|
|
98
|
+
lines.push('', '---');
|
|
99
|
+
return lines.join('\n');
|
|
100
|
+
}
|
|
101
|
+
/** The text after the record blocks. */
|
|
102
|
+
function renderFooter(page) {
|
|
103
|
+
return `Source: ${page.source_note}\nCPSC jurisdiction: ${page.cpsc_jurisdiction}`;
|
|
104
|
+
}
|
|
20
105
|
export const cpscGetRecent = tool('cpsc_get_recent', {
|
|
21
106
|
title: 'Get Recent CPSC Recalls',
|
|
22
|
-
description: 'Fetch the most recent CPSC consumer product recalls,
|
|
23
|
-
'Use for "what\'s been recalled lately?" or a product safety feed. ' +
|
|
24
|
-
'Always applies a date window (default: last 30 days) — without a date filter the API returns all 9,800+ records. ' +
|
|
25
|
-
'Page past limit with offset: narrowing days cannot page, because the window is anchored to today and shrinking it drops the oldest records rather than advancing past the newest. ' +
|
|
26
|
-
'CPSC jurisdiction: consumer products only — food, vehicles, drugs, and pesticides are covered by other agencies.',
|
|
107
|
+
description: 'Fetch the most recent CPSC consumer product recalls, newest-first, from a window of the last N days (default 30, up to 365). Use for "what\'s been recalled lately?" or a product safety feed; to find recalls by product, brand, hazard, or an older date range, use cpsc_search_recalls. Page past limit with offset: narrowing days cannot page, because the window is anchored to today and shrinking it drops the oldest recalls rather than advancing past the newest. A page returns fewer than limit recalls when it reaches the 64,000-byte response size budget; has_more and the notice give the offset to continue from. Pass a recall_number to cpsc_get_recall for the full record. CPSC jurisdiction: consumer products only — food/drugs (FDA), motor vehicles/tires (NHTSA), boats (USCG), pesticides (EPA), and firearms (ATF) are covered by other agencies.',
|
|
27
108
|
annotations: { readOnlyHint: true },
|
|
28
109
|
input: z.object({
|
|
29
110
|
days: z
|
|
30
111
|
.number()
|
|
31
112
|
.int()
|
|
32
113
|
.min(1)
|
|
33
|
-
.max(
|
|
114
|
+
.max(MAX_DAYS)
|
|
34
115
|
.default(30)
|
|
35
116
|
.describe('Look back this many days from today. Defaults to 30. Use 7 for a weekly digest, 90 for a quarterly review.'),
|
|
36
117
|
limit: z
|
|
@@ -39,43 +120,16 @@ export const cpscGetRecent = tool('cpsc_get_recent', {
|
|
|
39
120
|
.min(1)
|
|
40
121
|
.max(100)
|
|
41
122
|
.default(20)
|
|
42
|
-
.describe('Maximum number of recalls to return. Defaults to 20.'),
|
|
123
|
+
.describe('Maximum number of recalls to return. Defaults to 20. A page returns fewer when it reaches the 64,000-byte response size budget.'),
|
|
43
124
|
offset: z
|
|
44
125
|
.number()
|
|
45
126
|
.int()
|
|
46
127
|
.min(0)
|
|
47
128
|
.default(0)
|
|
48
|
-
.describe('Skip this many recalls in the window before returning results.
|
|
49
|
-
'An offset at or past total_found returns an empty result set rather than an error.'),
|
|
129
|
+
.describe('Skip this many recalls in the window before returning results. To page through total_found, raise offset by the number of recalls the previous page returned — limit, unless that page reached the response size budget. An offset at or past total_found returns an empty result set rather than an error.'),
|
|
50
130
|
}),
|
|
51
131
|
output: z.object({
|
|
52
|
-
recalls: z
|
|
53
|
-
.array(z
|
|
54
|
-
.object({
|
|
55
|
-
recall_number: z
|
|
56
|
-
.string()
|
|
57
|
-
.describe('Recall identifier. Pass to cpsc_get_recall for full detail.'),
|
|
58
|
-
recall_date: z.string().describe('Date issued, ISO 8601.'),
|
|
59
|
-
title: z.string().describe('Recall title.'),
|
|
60
|
-
hazards: z
|
|
61
|
-
.array(z.string().describe('Hazard description.'))
|
|
62
|
-
.describe('What is dangerous.'),
|
|
63
|
-
remedy_options: z
|
|
64
|
-
.array(z.string().describe('Remedy type.'))
|
|
65
|
-
.describe('Remedy types: Refund, Repair, Replace, New Instructions, Dispose, Label, No Remedy Available, Inspect. ' +
|
|
66
|
-
'Multiple may apply. Often empty — CPSC classified the remedy on fewer than half its records; ' +
|
|
67
|
-
'call cpsc_get_recall for the remedy narrative when this is empty.'),
|
|
68
|
-
products: z
|
|
69
|
-
.array(z.string().describe('Product name.'))
|
|
70
|
-
.describe('Product names recalled.'),
|
|
71
|
-
cpsc_url: z.string().describe('Official CPSC recall page URL.'),
|
|
72
|
-
data_quality_notes: z
|
|
73
|
-
.array(z.string().describe('One gap found in the upstream record.'))
|
|
74
|
-
.describe('Gaps this server observed in the upstream CPSC record — absent hazard text, absent product entries. ' +
|
|
75
|
-
'Derived from which fields CPSC left empty, not from any judgement about the recall itself. Empty when nothing is missing.'),
|
|
76
|
-
})
|
|
77
|
-
.describe('A recent CPSC recall.'))
|
|
78
|
-
.describe('Recent recalls, newest-first.'),
|
|
132
|
+
recalls: z.array(recentRecallSchema).describe('Recent recalls, newest-first.'),
|
|
79
133
|
period: z
|
|
80
134
|
.object({
|
|
81
135
|
start: z.string().describe('Start date of the query window, ISO 8601.'),
|
|
@@ -85,34 +139,39 @@ export const cpscGetRecent = tool('cpsc_get_recent', {
|
|
|
85
139
|
.describe('Date range queried.'),
|
|
86
140
|
total_found: z
|
|
87
141
|
.number()
|
|
88
|
-
.describe('Total recalls in this period, counted before offset and
|
|
142
|
+
.describe('Total recalls in this period, counted before offset, limit, and the response size budget narrow the window.'),
|
|
89
143
|
truncated: z
|
|
90
144
|
.boolean()
|
|
91
|
-
.describe('True when
|
|
145
|
+
.describe('True when recalls in the window remain past this page — always equal to has_more.'),
|
|
92
146
|
offset: z.number().describe('Number of recalls skipped before this window.'),
|
|
93
147
|
has_more: z
|
|
94
148
|
.boolean()
|
|
95
149
|
.describe('True when recalls remain past this window — call again with offset raised by the number of recalls returned.'),
|
|
96
150
|
cpsc_jurisdiction: z
|
|
97
151
|
.string()
|
|
98
|
-
.describe('CPSC covers
|
|
99
|
-
'Does NOT cover: food/drugs (FDA), motor vehicles/tires (NHTSA), boats (USCG), pesticides (EPA), firearms (ATF).'),
|
|
152
|
+
.describe('Which products CPSC covers and which agencies cover the rest — food/drugs (FDA), motor vehicles/tires (NHTSA), boats (USCG), pesticides (EPA), firearms (ATF).'),
|
|
100
153
|
source_note: z
|
|
101
154
|
.string()
|
|
102
|
-
.describe('Provenance caveat: recall fields are
|
|
155
|
+
.describe('Provenance caveat: recall fields are CPSC record text with HTML markup and character codes converted to plain text, not otherwise edited or verified; check cpsc_url before a consumer-facing decision.'),
|
|
103
156
|
}),
|
|
157
|
+
enrichment: {
|
|
158
|
+
notice: z
|
|
159
|
+
.string()
|
|
160
|
+
.optional()
|
|
161
|
+
.describe('Present when the page needs explaining: a window with no recalls (with a larger days to try), an offset past the last recall, or a page cut short by the response size budget (with the offset to continue from).'),
|
|
162
|
+
},
|
|
104
163
|
errors: [
|
|
105
164
|
{
|
|
106
165
|
reason: 'upstream_error',
|
|
107
166
|
code: JsonRpcErrorCode.ServiceUnavailable,
|
|
108
|
-
when: 'The saferproducts.gov
|
|
109
|
-
recovery: 'The CPSC
|
|
167
|
+
when: 'The CPSC recall service (saferproducts.gov) was unavailable, timed out, reported a temporary failure reading its recall data, or sent an unreadable response',
|
|
168
|
+
recovery: 'The CPSC recall service is occasionally unavailable. Retry in a few seconds.',
|
|
110
169
|
retryable: true,
|
|
111
170
|
},
|
|
112
171
|
{
|
|
113
172
|
reason: 'upstream_rejected',
|
|
114
173
|
code: JsonRpcErrorCode.ServiceUnavailable,
|
|
115
|
-
when: '
|
|
174
|
+
when: 'CPSC rejected the request instead of returning recalls, and it rejects the same request every time',
|
|
116
175
|
recovery: 'Do not retry this request unchanged — it fails deterministically. Read the message for what CPSC rejected, then try a different days window.',
|
|
117
176
|
retryable: false,
|
|
118
177
|
},
|
|
@@ -123,6 +182,7 @@ export const cpscGetRecent = tool('cpsc_get_recent', {
|
|
|
123
182
|
startDate.setDate(endDate.getDate() - input.days);
|
|
124
183
|
const dateStart = toIsoDate(startDate);
|
|
125
184
|
const dateEnd = toIsoDate(endDate);
|
|
185
|
+
const period = { start: dateStart, end: dateEnd, days: input.days };
|
|
126
186
|
ctx.log.info('Fetching recent CPSC recalls', {
|
|
127
187
|
days: input.days,
|
|
128
188
|
date_start: dateStart,
|
|
@@ -143,80 +203,49 @@ export const cpscGetRecent = tool('cpsc_get_recent', {
|
|
|
143
203
|
throw ctx.fail('upstream_error', `CPSC API request failed: ${detail}`, { ...ctx.recoveryFor('upstream_error') }, { cause: err });
|
|
144
204
|
}
|
|
145
205
|
const total_found = raw.length;
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const has_more = input.offset + slice.length < total_found;
|
|
150
|
-
const recalls = slice.map((r) => {
|
|
151
|
-
const hazards = r.Hazards.map((h) => h.Name).filter(Boolean);
|
|
152
|
-
const products = r.Products.map((p) => p.Name).filter(Boolean);
|
|
153
|
-
const data_quality_notes = [];
|
|
154
|
-
if (hazards.length === 0) {
|
|
155
|
-
data_quality_notes.push('CPSC listed no hazard description for this recall.');
|
|
156
|
-
}
|
|
157
|
-
if (products.length === 0) {
|
|
158
|
-
data_quality_notes.push('CPSC listed no product entries for this recall.');
|
|
159
|
-
}
|
|
160
|
-
return {
|
|
161
|
-
recall_number: r.RecallNumber,
|
|
162
|
-
recall_date: r.RecallDate.slice(0, 10),
|
|
163
|
-
title: r.Title,
|
|
164
|
-
hazards,
|
|
165
|
-
remedy_options: r.RemedyOptions.map((o) => o.Option).filter(Boolean),
|
|
166
|
-
products,
|
|
167
|
-
cpsc_url: r.URL,
|
|
168
|
-
data_quality_notes,
|
|
169
|
-
};
|
|
170
|
-
});
|
|
171
|
-
ctx.log.info('Recent recalls fetched', {
|
|
206
|
+
const window = raw.slice(input.offset, input.offset + input.limit).map(toRecentRecall);
|
|
207
|
+
const page = {
|
|
208
|
+
period,
|
|
172
209
|
total_found,
|
|
173
|
-
returned: recalls.length,
|
|
174
210
|
offset: input.offset,
|
|
175
|
-
truncated,
|
|
176
|
-
has_more,
|
|
177
|
-
});
|
|
178
|
-
return {
|
|
179
|
-
recalls,
|
|
180
|
-
period: { start: dateStart, end: dateEnd, days: input.days },
|
|
181
|
-
total_found,
|
|
182
|
-
truncated,
|
|
183
|
-
offset: input.offset,
|
|
184
|
-
has_more,
|
|
185
211
|
cpsc_jurisdiction: JURISDICTION,
|
|
186
212
|
source_note: SOURCE_NOTE,
|
|
187
213
|
};
|
|
214
|
+
/**
|
|
215
|
+
* The overhead reserves the page's worst case: the longest header (truncated, with a
|
|
216
|
+
* next-page offset for a full limit of records) and a budget notice at the same counts.
|
|
217
|
+
*/
|
|
218
|
+
const overhead = pageOverheadBytes({ ...page, recalls: [], truncated: false, has_more: false }, `${renderHeader({ ...page, truncated: true, has_more: true }, input.limit)}\n${renderFooter(page)}`, { notice: budgetCutNotice(input.limit, input.limit, input.offset + input.limit) });
|
|
219
|
+
const returned = countWithinBudget(window, renderRecallBlock, overhead);
|
|
220
|
+
const recalls = window.slice(0, returned);
|
|
221
|
+
const has_more = input.offset + returned < total_found;
|
|
222
|
+
/** One notice slot, last-wins: the three page states are mutually exclusive. */
|
|
223
|
+
const notice = total_found === 0
|
|
224
|
+
? `No CPSC recalls were issued between ${dateStart} and ${dateEnd}.${input.days < MAX_DAYS
|
|
225
|
+
? ` Raise days (up to ${MAX_DAYS}) for a longer window.`
|
|
226
|
+
: ` ${MAX_DAYS} days is the longest window this tool covers — use cpsc_search_recalls with date_start and date_end to reach older recalls.`}`
|
|
227
|
+
: input.offset >= total_found
|
|
228
|
+
? `Offset ${input.offset} is past the last of the ${total_found} recall${total_found === 1 ? '' : 's'} in this window, so this page is empty. Use an offset below ${total_found}.`
|
|
229
|
+
: returned < window.length
|
|
230
|
+
? budgetCutNotice(returned, input.limit, input.offset + returned)
|
|
231
|
+
: undefined;
|
|
232
|
+
if (notice)
|
|
233
|
+
ctx.enrich.notice(notice);
|
|
234
|
+
ctx.log.info('Recent recalls fetched', {
|
|
235
|
+
total_found,
|
|
236
|
+
returned,
|
|
237
|
+
offset: input.offset,
|
|
238
|
+
has_more,
|
|
239
|
+
});
|
|
240
|
+
return { ...page, recalls, truncated: has_more, has_more };
|
|
188
241
|
},
|
|
189
242
|
format(result) {
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
: '';
|
|
197
|
-
lines.push(`Found ${result.total_found} recall${result.total_found !== 1 ? 's' : ''}${windowNote}.`);
|
|
198
|
-
if (result.has_more) {
|
|
199
|
-
lines.push(`More available — repeat with offset ${result.offset + result.recalls.length}.`);
|
|
200
|
-
}
|
|
201
|
-
lines.push('');
|
|
202
|
-
lines.push('---');
|
|
203
|
-
for (const r of result.recalls) {
|
|
204
|
-
const hazardText = r.hazards.length > 0 ? r.hazards.join('; ') : 'Not specified';
|
|
205
|
-
const remedyText = r.remedy_options.length > 0 ? r.remedy_options.join(', ') : 'Not specified';
|
|
206
|
-
const productText = r.products.length > 0 ? r.products.join(', ') : 'Not specified';
|
|
207
|
-
lines.push(`**${r.recall_date}** — [${r.recall_number}] ${r.title}`);
|
|
208
|
-
lines.push('CPSC source fields:');
|
|
209
|
-
lines.push(`Hazard: ${hazardText} | Remedy: ${remedyText}`);
|
|
210
|
-
lines.push(`Products: ${productText}`);
|
|
211
|
-
lines.push(`[CPSC page](${r.cpsc_url})`);
|
|
212
|
-
if (r.data_quality_notes.length > 0) {
|
|
213
|
-
lines.push(`**Data quality (server-assessed):** ${r.data_quality_notes.join(' ')}`);
|
|
214
|
-
}
|
|
215
|
-
lines.push('---');
|
|
216
|
-
}
|
|
217
|
-
lines.push(`Source: ${result.source_note}`);
|
|
218
|
-
lines.push(`CPSC jurisdiction: ${result.cpsc_jurisdiction}`);
|
|
219
|
-
return [{ type: 'text', text: lines.join('\n') }];
|
|
243
|
+
const text = [
|
|
244
|
+
renderHeader(result, result.recalls.length),
|
|
245
|
+
...result.recalls.map(renderRecallBlock),
|
|
246
|
+
renderFooter(result),
|
|
247
|
+
].join('\n');
|
|
248
|
+
return [{ type: 'text', text }];
|
|
220
249
|
},
|
|
221
250
|
});
|
|
222
251
|
//# sourceMappingURL=cpsc-get-recent.tool.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cpsc-get-recent.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/cpsc-get-recent.tool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAC3E,OAAO,EAAE,oBAAoB,EAAE,MAAM,+CAA+C,CAAC;
|
|
1
|
+
{"version":3,"file":"cpsc-get-recent.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/cpsc-get-recent.tool.ts"],"names":[],"mappings":"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"}
|
|
@@ -51,26 +51,29 @@ export declare const cpscSearchRecalls: import("@cyanheads/mcp-ts-core").ToolDef
|
|
|
51
51
|
cpsc_jurisdiction: z.ZodString;
|
|
52
52
|
source_note: z.ZodString;
|
|
53
53
|
}, z.core.$strip>, readonly [{
|
|
54
|
+
readonly reason: "missing_criteria";
|
|
55
|
+
readonly code: JsonRpcErrorCode.ValidationError;
|
|
56
|
+
readonly when: "No search criterion was given: every text filter was omitted or blank and no date bound was set";
|
|
57
|
+
readonly recovery: "Set at least one criterion — title_search, product_name, manufacturer, importer, retailer, distributor, description_search, remedy, hazard_search, or a date bound (date_start, date_end, updated_start, updated_end). To browse the latest recalls without a filter, call cpsc_get_recent instead.";
|
|
58
|
+
}, {
|
|
54
59
|
readonly reason: "invalid_date_range";
|
|
55
60
|
readonly code: JsonRpcErrorCode.ValidationError;
|
|
56
61
|
readonly when: "date_start is later than date_end, or updated_start is later than updated_end, so the range can never match";
|
|
57
62
|
readonly recovery: "Swap the two dates, or drop one of them. date_start and date_end bound the recall issue date; updated_start and updated_end bound the date CPSC last published the record.";
|
|
58
|
-
}, {
|
|
59
|
-
readonly reason: "no_results";
|
|
60
|
-
readonly code: JsonRpcErrorCode.NotFound;
|
|
61
|
-
readonly when: "No recalls matched the search filters, including hazard_search";
|
|
62
|
-
readonly recovery: "Broaden the search — try a shorter product name, fewer filters, or remove the date range. If hazard_search was set, drop it: it narrows the upstream results further, client-side. Check CPSC jurisdiction: food, vehicle, and drug recalls are not in this database.";
|
|
63
63
|
}, {
|
|
64
64
|
readonly reason: "upstream_error";
|
|
65
65
|
readonly code: JsonRpcErrorCode.ServiceUnavailable;
|
|
66
|
-
readonly when: "The saferproducts.gov
|
|
67
|
-
readonly recovery: "The CPSC
|
|
66
|
+
readonly when: "The CPSC recall service (saferproducts.gov) was unavailable, timed out, reported a temporary failure reading its recall data, or sent an unreadable response";
|
|
67
|
+
readonly recovery: "The CPSC recall service is occasionally unavailable. Retry in a few seconds.";
|
|
68
68
|
readonly retryable: true;
|
|
69
69
|
}, {
|
|
70
70
|
readonly reason: "upstream_rejected";
|
|
71
71
|
readonly code: JsonRpcErrorCode.ServiceUnavailable;
|
|
72
|
-
readonly when: "
|
|
72
|
+
readonly when: "CPSC rejected the request instead of returning recalls, and it rejects the same request every time";
|
|
73
73
|
readonly recovery: "Do not retry this request unchanged — it fails deterministically. Read the message for what CPSC rejected, then change a filter value and call again.";
|
|
74
74
|
readonly retryable: false;
|
|
75
|
-
}],
|
|
75
|
+
}], {
|
|
76
|
+
readonly effectiveQuery: z.ZodString;
|
|
77
|
+
readonly notice: z.ZodOptional<z.ZodString>;
|
|
78
|
+
}>;
|
|
76
79
|
//# sourceMappingURL=cpsc-search-recalls.tool.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cpsc-search-recalls.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/cpsc-search-recalls.tool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAY,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"cpsc-search-recalls.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/cpsc-search-recalls.tool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAY,MAAM,+BAA+B,CAAC;AA0V3E,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoW5B,CAAC"}
|