@burtthecoder/mcp-shodan 1.0.6 → 1.0.8
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 +61 -12
- package/build/index.js +160 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
# Shodan MCP Server
|
|
2
2
|
|
|
3
|
-
A Model Context Protocol (MCP) server for querying the [Shodan API](https://shodan.io). This server provides tools for IP lookups, device searches, DNS lookups, vulnerability queries, and more. It is designed to integrate seamlessly with MCP-compatible applications like [Claude Desktop](https://claude.ai).
|
|
3
|
+
A Model Context Protocol (MCP) server for querying the [Shodan API](https://shodan.io) and [Shodan CVEDB](https://cvedb.shodan.io). This server provides tools for IP lookups, device searches, DNS lookups, vulnerability queries, CPE lookups, and more. It is designed to integrate seamlessly with MCP-compatible applications like [Claude Desktop](https://claude.ai).
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- **IP Lookup**: Retrieve detailed information about an IP address
|
|
8
8
|
- **Search**: Search for devices on Shodan matching specific queries
|
|
9
9
|
- **Ports**: Get a list of ports that Shodan is scanning
|
|
10
|
-
- **
|
|
10
|
+
- **CVE Lookup**: Fetch detailed information about specific CVEs using Shodan's CVEDB
|
|
11
|
+
- **CPE Lookup**: Search for Common Platform Enumeration (CPE) entries by product name
|
|
12
|
+
- **CVEs by Product**: Search for all CVEs affecting a specific product or CPE
|
|
11
13
|
- **DNS Lookup**: Resolve hostnames to IP addresses
|
|
12
14
|
|
|
13
15
|
## Tools
|
|
@@ -25,13 +27,54 @@ A Model Context Protocol (MCP) server for querying the [Shodan API](https://shod
|
|
|
25
27
|
* `query` (required): Shodan search query
|
|
26
28
|
* `max_results` (optional, default: 10): Number of results to return
|
|
27
29
|
|
|
28
|
-
### 3.
|
|
29
|
-
- Name: `
|
|
30
|
-
- Description: Fetch information about
|
|
30
|
+
### 3. CVE Lookup Tool
|
|
31
|
+
- Name: `cve_lookup`
|
|
32
|
+
- Description: Fetch detailed information about CVEs using Shodan's CVEDB
|
|
31
33
|
- Parameters:
|
|
32
|
-
* `cve` (required): CVE identifier
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
* `cve` (required): CVE identifier in format CVE-YYYY-NNNNN (e.g., CVE-2021-44228)
|
|
35
|
+
- Returns:
|
|
36
|
+
* CVE details including:
|
|
37
|
+
- CVSS v2 and v3 scores
|
|
38
|
+
- EPSS score and ranking
|
|
39
|
+
- KEV status
|
|
40
|
+
- Proposed action
|
|
41
|
+
- Ransomware campaign information
|
|
42
|
+
- Affected products (CPEs)
|
|
43
|
+
- References
|
|
44
|
+
|
|
45
|
+
### 4. CPE Lookup Tool
|
|
46
|
+
- Name: `cpe_lookup`
|
|
47
|
+
- Description: Search for Common Platform Enumeration (CPE) entries by product name
|
|
48
|
+
- Parameters:
|
|
49
|
+
* `product` (required): Name of the product to search for
|
|
50
|
+
* `count` (optional, default: false): If true, returns only the count of matching CPEs
|
|
51
|
+
* `skip` (optional, default: 0): Number of CPEs to skip (for pagination)
|
|
52
|
+
* `limit` (optional, default: 1000): Maximum number of CPEs to return
|
|
53
|
+
- Returns:
|
|
54
|
+
* When count is true: Total number of matching CPEs
|
|
55
|
+
* When count is false: List of CPEs with pagination details
|
|
56
|
+
|
|
57
|
+
### 5. CVEs by Product Tool
|
|
58
|
+
- Name: `cves_by_product`
|
|
59
|
+
- Description: Search for CVEs affecting a specific product or CPE
|
|
60
|
+
- Parameters:
|
|
61
|
+
* `cpe23` (optional): CPE 2.3 identifier (format: cpe:2.3:part:vendor:product:version)
|
|
62
|
+
* `product` (optional): Name of the product to search for CVEs
|
|
63
|
+
* `count` (optional, default: false): If true, returns only the count of matching CVEs
|
|
64
|
+
* `is_kev` (optional, default: false): If true, returns only CVEs with KEV flag set
|
|
65
|
+
* `sort_by_epss` (optional, default: false): If true, sorts CVEs by EPSS score
|
|
66
|
+
* `skip` (optional, default: 0): Number of CVEs to skip (for pagination)
|
|
67
|
+
* `limit` (optional, default: 1000): Maximum number of CVEs to return
|
|
68
|
+
* `start_date` (optional): Start date for filtering CVEs (format: YYYY-MM-DDTHH:MM:SS)
|
|
69
|
+
* `end_date` (optional): End date for filtering CVEs (format: YYYY-MM-DDTHH:MM:SS)
|
|
70
|
+
- Notes:
|
|
71
|
+
* Must provide either cpe23 or product, but not both
|
|
72
|
+
* Date filtering uses published time of CVEs
|
|
73
|
+
- Returns:
|
|
74
|
+
* When count is true: Total number of matching CVEs
|
|
75
|
+
* When count is false: List of CVEs with pagination details and query parameters
|
|
76
|
+
|
|
77
|
+
### 6. DNS Lookup Tool
|
|
35
78
|
- Name: `dns_lookup`
|
|
36
79
|
- Description: Resolve hostnames to IP addresses
|
|
37
80
|
- Parameters:
|
|
@@ -91,8 +134,8 @@ There are two ways to configure the Shodan MCP server in Claude Desktop:
|
|
|
91
134
|
{
|
|
92
135
|
"mcpServers": {
|
|
93
136
|
"shodan-mcp": {
|
|
94
|
-
"command": "
|
|
95
|
-
"args": ["@burtthecoder/mcp-shodan"],
|
|
137
|
+
"command": "npm",
|
|
138
|
+
"args": ["exec", "@burtthecoder/mcp-shodan"],
|
|
96
139
|
"env": {
|
|
97
140
|
"SHODAN_API_KEY": "your_shodan_api_key",
|
|
98
141
|
"DEBUG": "*"
|
|
@@ -102,7 +145,7 @@ There are two ways to configure the Shodan MCP server in Claude Desktop:
|
|
|
102
145
|
}
|
|
103
146
|
```
|
|
104
147
|
|
|
105
|
-
The
|
|
148
|
+
The npm exec method automatically downloads and runs the latest version of the package from npm.
|
|
106
149
|
|
|
107
150
|
Configuration file location:
|
|
108
151
|
|
|
@@ -133,9 +176,15 @@ The server includes comprehensive error handling for:
|
|
|
133
176
|
- Rate limiting
|
|
134
177
|
- Network errors
|
|
135
178
|
- Invalid input parameters
|
|
179
|
+
- Invalid CVE formats
|
|
180
|
+
- Invalid CPE lookup parameters
|
|
181
|
+
- Invalid date formats
|
|
182
|
+
- Mutually exclusive parameter validation
|
|
136
183
|
|
|
137
184
|
## Version History
|
|
138
185
|
|
|
186
|
+
- v1.0.7: Added CVEs by Product search functionality and renamed vulnerabilities tool to cve_lookup
|
|
187
|
+
- v1.0.6: Added CVEDB integration for enhanced CVE lookups and CPE search functionality
|
|
139
188
|
- v1.0.0: Initial release with core functionality
|
|
140
189
|
|
|
141
190
|
## Contributing
|
|
@@ -148,4 +197,4 @@ The server includes comprehensive error handling for:
|
|
|
148
197
|
|
|
149
198
|
## License
|
|
150
199
|
|
|
151
|
-
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
200
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
package/build/index.js
CHANGED
|
@@ -41,7 +41,7 @@ const SearchArgsSchema = z.object({
|
|
|
41
41
|
.default(10)
|
|
42
42
|
.describe("Maximum results to return."),
|
|
43
43
|
});
|
|
44
|
-
const
|
|
44
|
+
const CVELookupArgsSchema = z.object({
|
|
45
45
|
cve: z.string()
|
|
46
46
|
.regex(/^CVE-\d{4}-\d{4,}$/i, "Must be a valid CVE ID format (e.g., CVE-2021-44228)")
|
|
47
47
|
.describe("The CVE identifier to query (format: CVE-YYYY-NNNNN)."),
|
|
@@ -49,6 +49,23 @@ const VulnerabilitiesArgsSchema = z.object({
|
|
|
49
49
|
const DnsLookupArgsSchema = z.object({
|
|
50
50
|
hostnames: z.array(z.string()).describe("List of hostnames to resolve."),
|
|
51
51
|
});
|
|
52
|
+
const CpeLookupArgsSchema = z.object({
|
|
53
|
+
product: z.string().describe("The name of the product to search for CPEs."),
|
|
54
|
+
count: z.boolean().optional().default(false).describe("If true, returns only the count of matching CPEs."),
|
|
55
|
+
skip: z.number().optional().default(0).describe("Number of CPEs to skip (for pagination)."),
|
|
56
|
+
limit: z.number().optional().default(1000).describe("Maximum number of CPEs to return (max 1000)."),
|
|
57
|
+
});
|
|
58
|
+
const CVEsByProductArgsSchema = z.object({
|
|
59
|
+
cpe23: z.string().optional().describe("The CPE version 2.3 identifier (format: cpe:2.3:part:vendor:product:version)."),
|
|
60
|
+
product: z.string().optional().describe("The name of the product to search for CVEs."),
|
|
61
|
+
count: z.boolean().optional().default(false).describe("If true, returns only the count of matching CVEs."),
|
|
62
|
+
is_kev: z.boolean().optional().default(false).describe("If true, returns only CVEs with the KEV flag set."),
|
|
63
|
+
sort_by_epss: z.boolean().optional().default(false).describe("If true, sorts CVEs by EPSS score in descending order."),
|
|
64
|
+
skip: z.number().optional().default(0).describe("Number of CVEs to skip (for pagination)."),
|
|
65
|
+
limit: z.number().optional().default(1000).describe("Maximum number of CVEs to return (max 1000)."),
|
|
66
|
+
start_date: z.string().optional().describe("Start date for filtering CVEs (format: YYYY-MM-DDTHH:MM:SS)."),
|
|
67
|
+
end_date: z.string().optional().describe("End date for filtering CVEs (format: YYYY-MM-DDTHH:MM:SS).")
|
|
68
|
+
}).refine(data => !(data.cpe23 && data.product), { message: "Cannot specify both cpe23 and product. Use only one." }).refine(data => data.cpe23 || data.product, { message: "Must specify either cpe23 or product." });
|
|
52
69
|
// Helper Function to Query Shodan API
|
|
53
70
|
async function queryShodan(endpoint, params) {
|
|
54
71
|
try {
|
|
@@ -81,6 +98,34 @@ async function queryCVEDB(cveId) {
|
|
|
81
98
|
throw new Error(`CVEDB API error: ${error.message}`);
|
|
82
99
|
}
|
|
83
100
|
}
|
|
101
|
+
// Helper Function for CPE lookups using CVEDB
|
|
102
|
+
async function queryCPEDB(params) {
|
|
103
|
+
try {
|
|
104
|
+
logToFile(`Querying CVEDB for CPEs with params: ${JSON.stringify(params)}`);
|
|
105
|
+
const response = await axios.get(`${CVEDB_API_URL}/cpes`, { params });
|
|
106
|
+
return response.data;
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
if (error.response?.status === 422) {
|
|
110
|
+
throw new Error(`Invalid parameters: ${error.response.data?.detail || error.message}`);
|
|
111
|
+
}
|
|
112
|
+
throw new Error(`CVEDB API error: ${error.message}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Helper Function for CVEs by product/CPE lookups using CVEDB
|
|
116
|
+
async function queryCVEsByProduct(params) {
|
|
117
|
+
try {
|
|
118
|
+
logToFile(`Querying CVEDB for CVEs with params: ${JSON.stringify(params)}`);
|
|
119
|
+
const response = await axios.get(`${CVEDB_API_URL}/cves`, { params });
|
|
120
|
+
return response.data;
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
if (error.response?.status === 422) {
|
|
124
|
+
throw new Error(`Invalid parameters: ${error.response.data?.detail || error.message}`);
|
|
125
|
+
}
|
|
126
|
+
throw new Error(`CVEDB API error: ${error.message}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
84
129
|
// Server Setup
|
|
85
130
|
const server = new Server({
|
|
86
131
|
name: "shodan-mcp",
|
|
@@ -106,7 +151,7 @@ server.setRequestHandler(InitializeRequestSchema, async (request) => {
|
|
|
106
151
|
name: "shodan-mcp",
|
|
107
152
|
version: "1.0.0",
|
|
108
153
|
},
|
|
109
|
-
instructions: "This server provides tools for querying Shodan, including IP lookups, searches,
|
|
154
|
+
instructions: "This server provides tools for querying Shodan, including IP lookups, searches, CVE lookups, CPE lookups, and CVE searches by product/CPE.",
|
|
110
155
|
};
|
|
111
156
|
});
|
|
112
157
|
// Register Tools
|
|
@@ -123,15 +168,25 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
123
168
|
inputSchema: zodToJsonSchema(SearchArgsSchema),
|
|
124
169
|
},
|
|
125
170
|
{
|
|
126
|
-
name: "
|
|
171
|
+
name: "cve_lookup",
|
|
127
172
|
description: "Retrieve vulnerability information for a CVE. Use format: CVE-YYYY-NNNNN (e.g., CVE-2021-44228)",
|
|
128
|
-
inputSchema: zodToJsonSchema(
|
|
173
|
+
inputSchema: zodToJsonSchema(CVELookupArgsSchema),
|
|
129
174
|
},
|
|
130
175
|
{
|
|
131
176
|
name: "dns_lookup",
|
|
132
177
|
description: "Perform DNS lookups using Shodan.",
|
|
133
178
|
inputSchema: zodToJsonSchema(DnsLookupArgsSchema),
|
|
134
179
|
},
|
|
180
|
+
{
|
|
181
|
+
name: "cpe_lookup",
|
|
182
|
+
description: "Search for Common Platform Enumeration (CPE) entries by product name.",
|
|
183
|
+
inputSchema: zodToJsonSchema(CpeLookupArgsSchema),
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: "cves_by_product",
|
|
187
|
+
description: "Search for CVEs affecting a specific product or CPE. Provide either product name or CPE 2.3 identifier.",
|
|
188
|
+
inputSchema: zodToJsonSchema(CVEsByProductArgsSchema),
|
|
189
|
+
},
|
|
135
190
|
];
|
|
136
191
|
logToFile("Registered tools.");
|
|
137
192
|
return { tools };
|
|
@@ -175,12 +230,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
175
230
|
],
|
|
176
231
|
};
|
|
177
232
|
}
|
|
178
|
-
case "
|
|
179
|
-
const
|
|
180
|
-
if (!
|
|
233
|
+
case "cve_lookup": {
|
|
234
|
+
const parsedCveArgs = CVELookupArgsSchema.safeParse(args);
|
|
235
|
+
if (!parsedCveArgs.success) {
|
|
181
236
|
throw new Error("Invalid CVE format. Please use format: CVE-YYYY-NNNNN (e.g., CVE-2021-44228)");
|
|
182
237
|
}
|
|
183
|
-
const cveId =
|
|
238
|
+
const cveId = parsedCveArgs.data.cve.toUpperCase();
|
|
184
239
|
logToFile(`Looking up CVE: ${cveId}`);
|
|
185
240
|
try {
|
|
186
241
|
const result = await queryCVEDB(cveId);
|
|
@@ -241,6 +296,103 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
241
296
|
],
|
|
242
297
|
};
|
|
243
298
|
}
|
|
299
|
+
case "cpe_lookup": {
|
|
300
|
+
const parsedCpeArgs = CpeLookupArgsSchema.safeParse(args);
|
|
301
|
+
if (!parsedCpeArgs.success) {
|
|
302
|
+
throw new Error("Invalid cpe_lookup arguments");
|
|
303
|
+
}
|
|
304
|
+
try {
|
|
305
|
+
const result = await queryCPEDB({
|
|
306
|
+
product: parsedCpeArgs.data.product,
|
|
307
|
+
count: parsedCpeArgs.data.count,
|
|
308
|
+
skip: parsedCpeArgs.data.skip,
|
|
309
|
+
limit: parsedCpeArgs.data.limit
|
|
310
|
+
});
|
|
311
|
+
// Format the response based on whether it's a count request or full CPE list
|
|
312
|
+
const formattedResult = parsedCpeArgs.data.count
|
|
313
|
+
? { total_cpes: result.total }
|
|
314
|
+
: {
|
|
315
|
+
cpes: result.cpes,
|
|
316
|
+
skip: parsedCpeArgs.data.skip,
|
|
317
|
+
limit: parsedCpeArgs.data.limit,
|
|
318
|
+
total_returned: result.cpes.length
|
|
319
|
+
};
|
|
320
|
+
return {
|
|
321
|
+
content: [
|
|
322
|
+
{
|
|
323
|
+
type: "text",
|
|
324
|
+
text: JSON.stringify(formattedResult, null, 2),
|
|
325
|
+
},
|
|
326
|
+
],
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
catch (error) {
|
|
330
|
+
return {
|
|
331
|
+
content: [
|
|
332
|
+
{
|
|
333
|
+
type: "text",
|
|
334
|
+
text: error.message,
|
|
335
|
+
},
|
|
336
|
+
],
|
|
337
|
+
isError: true,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
case "cves_by_product": {
|
|
342
|
+
const parsedArgs = CVEsByProductArgsSchema.safeParse(args);
|
|
343
|
+
if (!parsedArgs.success) {
|
|
344
|
+
throw new Error("Invalid arguments. Must provide either cpe23 or product name, but not both.");
|
|
345
|
+
}
|
|
346
|
+
try {
|
|
347
|
+
const result = await queryCVEsByProduct({
|
|
348
|
+
cpe23: parsedArgs.data.cpe23,
|
|
349
|
+
product: parsedArgs.data.product,
|
|
350
|
+
count: parsedArgs.data.count,
|
|
351
|
+
is_kev: parsedArgs.data.is_kev,
|
|
352
|
+
sort_by_epss: parsedArgs.data.sort_by_epss,
|
|
353
|
+
skip: parsedArgs.data.skip,
|
|
354
|
+
limit: parsedArgs.data.limit,
|
|
355
|
+
start_date: parsedArgs.data.start_date,
|
|
356
|
+
end_date: parsedArgs.data.end_date
|
|
357
|
+
});
|
|
358
|
+
// Format the response based on whether it's a count request or full CVE list
|
|
359
|
+
const formattedResult = parsedArgs.data.count
|
|
360
|
+
? { total_cves: result.total }
|
|
361
|
+
: {
|
|
362
|
+
cves: result.cves,
|
|
363
|
+
skip: parsedArgs.data.skip,
|
|
364
|
+
limit: parsedArgs.data.limit,
|
|
365
|
+
total_returned: result.cves.length,
|
|
366
|
+
query_params: {
|
|
367
|
+
cpe23: parsedArgs.data.cpe23,
|
|
368
|
+
product: parsedArgs.data.product,
|
|
369
|
+
is_kev: parsedArgs.data.is_kev,
|
|
370
|
+
sort_by_epss: parsedArgs.data.sort_by_epss,
|
|
371
|
+
start_date: parsedArgs.data.start_date,
|
|
372
|
+
end_date: parsedArgs.data.end_date
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
return {
|
|
376
|
+
content: [
|
|
377
|
+
{
|
|
378
|
+
type: "text",
|
|
379
|
+
text: JSON.stringify(formattedResult, null, 2),
|
|
380
|
+
},
|
|
381
|
+
],
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
catch (error) {
|
|
385
|
+
return {
|
|
386
|
+
content: [
|
|
387
|
+
{
|
|
388
|
+
type: "text",
|
|
389
|
+
text: error.message,
|
|
390
|
+
},
|
|
391
|
+
],
|
|
392
|
+
isError: true,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
}
|
|
244
396
|
default:
|
|
245
397
|
throw new Error(`Unknown tool: ${name}`);
|
|
246
398
|
}
|