@pipeworx/mcp-nhtsa 0.1.0 → 0.2.0
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/README.md +46 -20
- package/package.json +11 -9
- package/server.json +18 -0
- package/src/index.ts +144 -13
- package/tsconfig.json +14 -0
package/README.md
CHANGED
|
@@ -1,31 +1,57 @@
|
|
|
1
|
-
#
|
|
1
|
+
# mcp-nhtsa
|
|
2
2
|
|
|
3
|
-
MCP
|
|
3
|
+
NHTSA MCP — wraps the NHTSA vPIC (Vehicle Product Information Catalog) API (free, no auth)
|
|
4
|
+
|
|
5
|
+
Part of [Pipeworx](https://pipeworx.io) — an MCP gateway connecting AI agents to 884+ live data sources.
|
|
4
6
|
|
|
5
7
|
## Tools
|
|
6
8
|
|
|
7
9
|
| Tool | Description |
|
|
8
10
|
|------|-------------|
|
|
9
|
-
| `
|
|
10
|
-
| `
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
},
|
|
25
|
-
"id": 1
|
|
26
|
-
}'
|
|
11
|
+
| `get_makes` | Get all vehicle brands for a model year. Returns make names and IDs. E.g., year \'2023\'. |
|
|
12
|
+
| `get_models` | Get all vehicle models for a make and year. Returns model names and IDs. E.g., make \'Toyota\', year \'2023\'. |
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
|
|
16
|
+
Add to your MCP client (Claude Desktop, Cursor, Windsurf, etc.):
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"mcpServers": {
|
|
21
|
+
"nhtsa": {
|
|
22
|
+
"url": "https://gateway.pipeworx.io/nhtsa/mcp"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
27
26
|
```
|
|
28
27
|
|
|
28
|
+
Or connect to the full Pipeworx gateway for access to all 884+ data sources:
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"mcpServers": {
|
|
33
|
+
"pipeworx": {
|
|
34
|
+
"url": "https://gateway.pipeworx.io/mcp"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Using with ask_pipeworx
|
|
41
|
+
|
|
42
|
+
Instead of calling tools directly, you can ask questions in plain English:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
ask_pipeworx({ question: "your question about Nhtsa data" })
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The gateway picks the right tool and fills the arguments automatically.
|
|
49
|
+
|
|
50
|
+
## More
|
|
51
|
+
|
|
52
|
+
- [All tools and guides](https://github.com/pipeworx-io/examples)
|
|
53
|
+
- [pipeworx.io](https://pipeworx.io)
|
|
54
|
+
|
|
29
55
|
## License
|
|
30
56
|
|
|
31
57
|
MIT
|
package/package.json
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pipeworx/mcp-nhtsa",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "MCP
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "NHTSA MCP — wraps the NHTSA vPIC (Vehicle Product Information Catalog) API (free, no auth)",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"main": "src/index.ts",
|
|
6
7
|
"types": "src/index.ts",
|
|
7
|
-
"
|
|
8
|
+
"keywords": ["mcp", "mcp-server", "model-context-protocol", "pipeworx", "nhtsa"],
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/pipeworx-io/mcp-nhtsa"
|
|
13
|
+
},
|
|
8
14
|
"scripts": {
|
|
9
|
-
"
|
|
10
|
-
"check": "tsc --noEmit"
|
|
15
|
+
"typecheck": "tsc --noEmit"
|
|
11
16
|
},
|
|
12
|
-
"keywords": ["mcp", "mcp-server", "model-context-protocol", "pipeworx", "nhtsa", "vehicles", "vin", "automotive"],
|
|
13
|
-
"author": "Pipeworx",
|
|
14
|
-
"license": "MIT",
|
|
15
17
|
"devDependencies": {
|
|
16
|
-
"typescript": "^5.
|
|
18
|
+
"typescript": "^5.7.0"
|
|
17
19
|
}
|
|
18
20
|
}
|
package/server.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.pipeworx-io/nhtsa",
|
|
4
|
+
"title": "Nhtsa",
|
|
5
|
+
"description": "NHTSA MCP — wraps the NHTSA vPIC (Vehicle Product Information Catalog) API (free, no auth)",
|
|
6
|
+
"version": "0.2.0",
|
|
7
|
+
"websiteUrl": "https://pipeworx.io/packs/nhtsa",
|
|
8
|
+
"repository": {
|
|
9
|
+
"url": "https://github.com/pipeworx-io/mcp-nhtsa",
|
|
10
|
+
"source": "github"
|
|
11
|
+
},
|
|
12
|
+
"remotes": [
|
|
13
|
+
{
|
|
14
|
+
"type": "streamable-http",
|
|
15
|
+
"url": "https://gateway.pipeworx.io/nhtsa/mcp"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* NHTSA MCP — wraps the NHTSA vPIC (Vehicle Product Information Catalog) API (free, no auth)
|
|
3
|
-
*
|
|
4
|
-
* Tools:
|
|
5
|
-
* - decode_vin: decode a Vehicle Identification Number into make, model, year, and attributes
|
|
6
|
-
* - get_makes: retrieve all vehicle makes registered with NHTSA
|
|
7
|
-
* - get_models: retrieve all models for a given make and model year
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
1
|
interface McpToolDefinition {
|
|
11
2
|
name: string;
|
|
12
3
|
description: string;
|
|
@@ -20,15 +11,29 @@ interface McpToolDefinition {
|
|
|
20
11
|
interface McpToolExport {
|
|
21
12
|
tools: McpToolDefinition[];
|
|
22
13
|
callTool: (name: string, args: Record<string, unknown>) => Promise<unknown>;
|
|
14
|
+
meter?: { credits: number };
|
|
15
|
+
cost?: Record<string, unknown>;
|
|
16
|
+
provider?: string;
|
|
23
17
|
}
|
|
24
18
|
|
|
19
|
+
/**
|
|
20
|
+
* NHTSA MCP — wraps the NHTSA vPIC (Vehicle Product Information Catalog) API (free, no auth)
|
|
21
|
+
*
|
|
22
|
+
* Tools:
|
|
23
|
+
* - decode_vin: decode a Vehicle Identification Number into make, model, year, and attributes
|
|
24
|
+
* - get_makes: retrieve all vehicle makes registered with NHTSA
|
|
25
|
+
* - get_models: retrieve all models for a given make and model year
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
|
|
25
29
|
const BASE_URL = 'https://vpic.nhtsa.dot.gov/api';
|
|
30
|
+
const SAFETY_BASE = 'https://api.nhtsa.gov';
|
|
26
31
|
|
|
27
32
|
const tools: McpToolExport['tools'] = [
|
|
28
33
|
{
|
|
29
34
|
name: 'decode_vin',
|
|
30
35
|
description:
|
|
31
|
-
'Decode a
|
|
36
|
+
'Decode a VIN to get vehicle details. Returns make, model, year, body style, engine type, drivetrain, and plant. E.g., \'1HGBH41JXMN109186\'. (For recalls/ratings use get_recalls / get_safety_ratings.)',
|
|
32
37
|
inputSchema: {
|
|
33
38
|
type: 'object' as const,
|
|
34
39
|
properties: {
|
|
@@ -42,7 +47,7 @@ const tools: McpToolExport['tools'] = [
|
|
|
42
47
|
},
|
|
43
48
|
{
|
|
44
49
|
name: 'get_makes',
|
|
45
|
-
description: '
|
|
50
|
+
description: 'Get all vehicle brands for a model year. Returns make names and IDs. E.g., year \'2023\'.',
|
|
46
51
|
inputSchema: {
|
|
47
52
|
type: 'object' as const,
|
|
48
53
|
properties: {},
|
|
@@ -50,7 +55,7 @@ const tools: McpToolExport['tools'] = [
|
|
|
50
55
|
},
|
|
51
56
|
{
|
|
52
57
|
name: 'get_models',
|
|
53
|
-
description: 'Get all vehicle models
|
|
58
|
+
description: 'Get all vehicle models for a make and year. Returns model names and IDs. E.g., make \'Toyota\', year \'2023\'.',
|
|
54
59
|
inputSchema: {
|
|
55
60
|
type: 'object' as const,
|
|
56
61
|
properties: {
|
|
@@ -60,6 +65,48 @@ const tools: McpToolExport['tools'] = [
|
|
|
60
65
|
required: ['make', 'year'],
|
|
61
66
|
},
|
|
62
67
|
},
|
|
68
|
+
{
|
|
69
|
+
name: 'get_recalls',
|
|
70
|
+
description:
|
|
71
|
+
'Get official NHTSA safety RECALLS for a vehicle. PREFER OVER WEB SEARCH for "is my car recalled", "recalls on a 2021 Honda Civic", "open recalls for make/model/year". Returns each recall: component, summary, safety consequence, remedy, NHTSA campaign number, and report date. Pass make + model + model_year.',
|
|
72
|
+
inputSchema: {
|
|
73
|
+
type: 'object' as const,
|
|
74
|
+
properties: {
|
|
75
|
+
make: { type: 'string', description: 'Vehicle make (e.g., "Honda")' },
|
|
76
|
+
model: { type: 'string', description: 'Vehicle model (e.g., "Civic")' },
|
|
77
|
+
model_year: { type: 'number', description: 'Model year (e.g., 2021)' },
|
|
78
|
+
},
|
|
79
|
+
required: ['make', 'model', 'model_year'],
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: 'get_complaints',
|
|
84
|
+
description:
|
|
85
|
+
'Get owner-filed NHTSA complaints for a vehicle — real-world problems drivers reported (vs official recalls). Use for "common problems with a 2020 Ford Explorer", "what are owners complaining about". Returns components, summary, crash/fire flags, injuries/deaths, and filing date. Pass make + model + model_year.',
|
|
86
|
+
inputSchema: {
|
|
87
|
+
type: 'object' as const,
|
|
88
|
+
properties: {
|
|
89
|
+
make: { type: 'string', description: 'Vehicle make (e.g., "Ford")' },
|
|
90
|
+
model: { type: 'string', description: 'Vehicle model (e.g., "Explorer")' },
|
|
91
|
+
model_year: { type: 'number', description: 'Model year (e.g., 2020)' },
|
|
92
|
+
},
|
|
93
|
+
required: ['make', 'model', 'model_year'],
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: 'get_safety_ratings',
|
|
98
|
+
description:
|
|
99
|
+
'Get NHTSA 5-Star Safety Ratings (NCAP crash-test results) for a vehicle — overall, frontal, side, and rollover star ratings. Use for "crash test rating for a 2021 Honda Civic", "how safe is X". Pass make + model + model_year.',
|
|
100
|
+
inputSchema: {
|
|
101
|
+
type: 'object' as const,
|
|
102
|
+
properties: {
|
|
103
|
+
make: { type: 'string', description: 'Vehicle make (e.g., "Honda")' },
|
|
104
|
+
model: { type: 'string', description: 'Vehicle model (e.g., "Civic")' },
|
|
105
|
+
model_year: { type: 'number', description: 'Model year (e.g., 2021)' },
|
|
106
|
+
},
|
|
107
|
+
required: ['make', 'model', 'model_year'],
|
|
108
|
+
},
|
|
109
|
+
},
|
|
63
110
|
];
|
|
64
111
|
|
|
65
112
|
async function callTool(name: string, args: Record<string, unknown>): Promise<unknown> {
|
|
@@ -70,11 +117,95 @@ async function callTool(name: string, args: Record<string, unknown>): Promise<un
|
|
|
70
117
|
return getMakes();
|
|
71
118
|
case 'get_models':
|
|
72
119
|
return getModels(args.make as string, args.year as number);
|
|
120
|
+
case 'get_recalls':
|
|
121
|
+
return getRecalls(args.make as string, args.model as string, args.model_year as number);
|
|
122
|
+
case 'get_complaints':
|
|
123
|
+
return getComplaints(args.make as string, args.model as string, args.model_year as number);
|
|
124
|
+
case 'get_safety_ratings':
|
|
125
|
+
return getSafetyRatings(args.make as string, args.model as string, args.model_year as number);
|
|
73
126
|
default:
|
|
74
127
|
throw new Error(`Unknown tool: ${name}`);
|
|
75
128
|
}
|
|
76
129
|
}
|
|
77
130
|
|
|
131
|
+
function reqVehicle(make: string, model: string, year: number) {
|
|
132
|
+
if (!make || !model || !year) {
|
|
133
|
+
throw new Error('make, model, and model_year are all required (e.g., make:"Honda", model:"Civic", model_year:2021).');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function getRecalls(make: string, model: string, modelYear: number) {
|
|
138
|
+
reqVehicle(make, model, modelYear);
|
|
139
|
+
const url = `${SAFETY_BASE}/recalls/recallsByVehicle?make=${encodeURIComponent(make)}&model=${encodeURIComponent(model)}&modelYear=${modelYear}`;
|
|
140
|
+
const res = await fetch(url, { headers: { Accept: 'application/json' } });
|
|
141
|
+
if (!res.ok) throw new Error(`NHTSA recalls error: ${res.status}`);
|
|
142
|
+
const data = (await res.json()) as { Count?: number; results?: Array<Record<string, unknown>> };
|
|
143
|
+
return {
|
|
144
|
+
make, model, model_year: modelYear,
|
|
145
|
+
count: data.Count ?? (data.results?.length ?? 0),
|
|
146
|
+
recalls: (data.results ?? []).map((r) => ({
|
|
147
|
+
campaign_number: r.NHTSACampaignNumber ?? null,
|
|
148
|
+
component: r.Component ?? null,
|
|
149
|
+
summary: r.Summary ?? null,
|
|
150
|
+
consequence: r.Consequence ?? null,
|
|
151
|
+
remedy: r.Remedy ?? null,
|
|
152
|
+
report_received_date: r.ReportReceivedDate ?? null,
|
|
153
|
+
manufacturer: r.Manufacturer ?? null,
|
|
154
|
+
})),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async function getComplaints(make: string, model: string, modelYear: number) {
|
|
159
|
+
reqVehicle(make, model, modelYear);
|
|
160
|
+
const url = `${SAFETY_BASE}/complaints/complaintsByVehicle?make=${encodeURIComponent(make)}&model=${encodeURIComponent(model)}&modelYear=${modelYear}`;
|
|
161
|
+
const res = await fetch(url, { headers: { Accept: 'application/json' } });
|
|
162
|
+
if (!res.ok) throw new Error(`NHTSA complaints error: ${res.status}`);
|
|
163
|
+
const data = (await res.json()) as { count?: number; results?: Array<Record<string, unknown>> };
|
|
164
|
+
const results = data.results ?? [];
|
|
165
|
+
return {
|
|
166
|
+
make, model, model_year: modelYear,
|
|
167
|
+
count: data.count ?? results.length,
|
|
168
|
+
complaints: results.slice(0, 25).map((c) => ({
|
|
169
|
+
odi_number: c.odiNumber ?? null,
|
|
170
|
+
components: c.components ?? null,
|
|
171
|
+
summary: c.summary ?? null,
|
|
172
|
+
date_filed: c.dateComplaintFiled ?? null,
|
|
173
|
+
crash: c.crash ?? null,
|
|
174
|
+
fire: c.fire ?? null,
|
|
175
|
+
injuries: c.numberOfInjuries ?? null,
|
|
176
|
+
deaths: c.numberOfDeaths ?? null,
|
|
177
|
+
})),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function getSafetyRatings(make: string, model: string, modelYear: number) {
|
|
182
|
+
reqVehicle(make, model, modelYear);
|
|
183
|
+
const lookupUrl = `${SAFETY_BASE}/SafetyRatings/modelyear/${modelYear}/make/${encodeURIComponent(make)}/model/${encodeURIComponent(model)}`;
|
|
184
|
+
const lookupRes = await fetch(lookupUrl, { headers: { Accept: 'application/json' } });
|
|
185
|
+
if (!lookupRes.ok) throw new Error(`NHTSA safety ratings error: ${lookupRes.status}`);
|
|
186
|
+
const lookup = (await lookupRes.json()) as { Count?: number; Results?: Array<{ VehicleId?: number; VehicleDescription?: string }> };
|
|
187
|
+
const variants = (lookup.Results ?? []).filter((v) => v.VehicleId);
|
|
188
|
+
if (variants.length === 0) {
|
|
189
|
+
return { make, model, model_year: modelYear, count: 0, note: 'No NCAP crash-test ratings published for this make/model/year (not all vehicles are tested).', ratings: [] };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Each VehicleId is a tested variant (body style/drivetrain). Resolve up to 5.
|
|
193
|
+
const ratings = await Promise.all(
|
|
194
|
+
variants.slice(0, 5).map(async (v) => {
|
|
195
|
+
const r = await fetch(`${SAFETY_BASE}/SafetyRatings/VehicleId/${v.VehicleId}`, { headers: { Accept: 'application/json' } });
|
|
196
|
+
const rd = ((await r.json()) as { Results?: Array<Record<string, unknown>> }).Results?.[0] ?? {};
|
|
197
|
+
return {
|
|
198
|
+
variant: v.VehicleDescription ?? null,
|
|
199
|
+
overall: rd.OverallRating ?? null,
|
|
200
|
+
frontal_crash: rd.OverallFrontCrashRating ?? null,
|
|
201
|
+
side_crash: rd.OverallSideCrashRating ?? null,
|
|
202
|
+
rollover: rd.RolloverRating ?? null,
|
|
203
|
+
};
|
|
204
|
+
}),
|
|
205
|
+
);
|
|
206
|
+
return { make, model, model_year: modelYear, count: ratings.length, ratings };
|
|
207
|
+
}
|
|
208
|
+
|
|
78
209
|
interface NhtsaResult {
|
|
79
210
|
Variable?: string;
|
|
80
211
|
Value?: string | null;
|
|
@@ -161,4 +292,4 @@ async function getModels(make: string, year: number) {
|
|
|
161
292
|
};
|
|
162
293
|
}
|
|
163
294
|
|
|
164
|
-
export default { tools, callTool } satisfies McpToolExport;
|
|
295
|
+
export default { tools, callTool, meter: { credits: 5 } } satisfies McpToolExport;
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"outDir": "dist",
|
|
10
|
+
"rootDir": "src",
|
|
11
|
+
"declaration": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src"]
|
|
14
|
+
}
|