@burtthecoder/mcp-shodan 1.0.5 → 1.0.7
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 +35 -10
- package/build/index.js +128 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
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
|
-
- **Vulnerabilities**: Fetch information about
|
|
10
|
+
- **Vulnerabilities**: Fetch detailed information about CVEs using Shodan's CVEDB
|
|
11
|
+
- **CPE Lookup**: Search for Common Platform Enumeration (CPE) entries by product name
|
|
11
12
|
- **DNS Lookup**: Resolve hostnames to IP addresses
|
|
12
13
|
|
|
13
14
|
## Tools
|
|
@@ -27,11 +28,32 @@ A Model Context Protocol (MCP) server for querying the [Shodan API](https://shod
|
|
|
27
28
|
|
|
28
29
|
### 3. Vulnerabilities Tool
|
|
29
30
|
- Name: `vulnerabilities`
|
|
30
|
-
- Description: Fetch information about
|
|
31
|
+
- Description: Fetch detailed information about CVEs using Shodan's CVEDB
|
|
31
32
|
- Parameters:
|
|
32
|
-
* `cve` (required): CVE identifier
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
* `cve` (required): CVE identifier in format CVE-YYYY-NNNNN (e.g., CVE-2021-44228)
|
|
34
|
+
- Returns:
|
|
35
|
+
* CVE details including:
|
|
36
|
+
- CVSS v2 and v3 scores
|
|
37
|
+
- EPSS score and ranking
|
|
38
|
+
- KEV status
|
|
39
|
+
- Proposed action
|
|
40
|
+
- Ransomware campaign information
|
|
41
|
+
- Affected products (CPEs)
|
|
42
|
+
- References
|
|
43
|
+
|
|
44
|
+
### 4. CPE Lookup Tool
|
|
45
|
+
- Name: `cpe_lookup`
|
|
46
|
+
- Description: Search for Common Platform Enumeration (CPE) entries by product name
|
|
47
|
+
- Parameters:
|
|
48
|
+
* `product` (required): Name of the product to search for
|
|
49
|
+
* `count` (optional, default: false): If true, returns only the count of matching CPEs
|
|
50
|
+
* `skip` (optional, default: 0): Number of CPEs to skip (for pagination)
|
|
51
|
+
* `limit` (optional, default: 1000): Maximum number of CPEs to return
|
|
52
|
+
- Returns:
|
|
53
|
+
* When count is true: Total number of matching CPEs
|
|
54
|
+
* When count is false: List of CPEs with pagination details
|
|
55
|
+
|
|
56
|
+
### 5. DNS Lookup Tool
|
|
35
57
|
- Name: `dns_lookup`
|
|
36
58
|
- Description: Resolve hostnames to IP addresses
|
|
37
59
|
- Parameters:
|
|
@@ -91,8 +113,8 @@ There are two ways to configure the Shodan MCP server in Claude Desktop:
|
|
|
91
113
|
{
|
|
92
114
|
"mcpServers": {
|
|
93
115
|
"shodan-mcp": {
|
|
94
|
-
"command": "
|
|
95
|
-
"args": ["@burtthecoder/mcp-shodan"],
|
|
116
|
+
"command": "npm",
|
|
117
|
+
"args": ["exec", "@burtthecoder/mcp-shodan"],
|
|
96
118
|
"env": {
|
|
97
119
|
"SHODAN_API_KEY": "your_shodan_api_key",
|
|
98
120
|
"DEBUG": "*"
|
|
@@ -102,7 +124,7 @@ There are two ways to configure the Shodan MCP server in Claude Desktop:
|
|
|
102
124
|
}
|
|
103
125
|
```
|
|
104
126
|
|
|
105
|
-
The
|
|
127
|
+
The npm exec method automatically downloads and runs the latest version of the package from npm.
|
|
106
128
|
|
|
107
129
|
Configuration file location:
|
|
108
130
|
|
|
@@ -133,9 +155,12 @@ The server includes comprehensive error handling for:
|
|
|
133
155
|
- Rate limiting
|
|
134
156
|
- Network errors
|
|
135
157
|
- Invalid input parameters
|
|
158
|
+
- Invalid CVE formats
|
|
159
|
+
- Invalid CPE lookup parameters
|
|
136
160
|
|
|
137
161
|
## Version History
|
|
138
162
|
|
|
163
|
+
- v1.0.6: Added CVEDB integration for enhanced CVE lookups and CPE search functionality
|
|
139
164
|
- v1.0.0: Initial release with core functionality
|
|
140
165
|
|
|
141
166
|
## Contributing
|
|
@@ -148,4 +173,4 @@ The server includes comprehensive error handling for:
|
|
|
148
173
|
|
|
149
174
|
## License
|
|
150
175
|
|
|
151
|
-
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
176
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
package/build/index.js
CHANGED
|
@@ -10,13 +10,13 @@ import fs from "fs";
|
|
|
10
10
|
import path from "path";
|
|
11
11
|
import os from "os";
|
|
12
12
|
dotenv.config();
|
|
13
|
-
// Use os.tmpdir() for the log file to ensure we have write permissions
|
|
14
13
|
const logFilePath = path.join(os.tmpdir(), "mcp-shodan-server.log");
|
|
15
14
|
const SHODAN_API_KEY = process.env.SHODAN_API_KEY;
|
|
16
15
|
if (!SHODAN_API_KEY) {
|
|
17
16
|
throw new Error("SHODAN_API_KEY environment variable is required.");
|
|
18
17
|
}
|
|
19
18
|
const API_BASE_URL = "https://api.shodan.io";
|
|
19
|
+
const CVEDB_API_URL = "https://cvedb.shodan.io";
|
|
20
20
|
// Logging Helper Function
|
|
21
21
|
function logToFile(message) {
|
|
22
22
|
try {
|
|
@@ -42,11 +42,19 @@ const SearchArgsSchema = z.object({
|
|
|
42
42
|
.describe("Maximum results to return."),
|
|
43
43
|
});
|
|
44
44
|
const VulnerabilitiesArgsSchema = z.object({
|
|
45
|
-
cve: z.string()
|
|
45
|
+
cve: z.string()
|
|
46
|
+
.regex(/^CVE-\d{4}-\d{4,}$/i, "Must be a valid CVE ID format (e.g., CVE-2021-44228)")
|
|
47
|
+
.describe("The CVE identifier to query (format: CVE-YYYY-NNNNN)."),
|
|
46
48
|
});
|
|
47
49
|
const DnsLookupArgsSchema = z.object({
|
|
48
50
|
hostnames: z.array(z.string()).describe("List of hostnames to resolve."),
|
|
49
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
|
+
});
|
|
50
58
|
// Helper Function to Query Shodan API
|
|
51
59
|
async function queryShodan(endpoint, params) {
|
|
52
60
|
try {
|
|
@@ -62,6 +70,37 @@ async function queryShodan(endpoint, params) {
|
|
|
62
70
|
throw new Error(`Shodan API error: ${errorMessage}`);
|
|
63
71
|
}
|
|
64
72
|
}
|
|
73
|
+
// Helper Function for CVE lookups using CVEDB
|
|
74
|
+
async function queryCVEDB(cveId) {
|
|
75
|
+
try {
|
|
76
|
+
logToFile(`Querying CVEDB for: ${cveId}`);
|
|
77
|
+
const response = await axios.get(`${CVEDB_API_URL}/cve/${cveId}`);
|
|
78
|
+
return response.data;
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
if (error.response?.status === 422) {
|
|
82
|
+
throw new Error(`Invalid CVE ID format: ${cveId}`);
|
|
83
|
+
}
|
|
84
|
+
if (error.response?.status === 404) {
|
|
85
|
+
throw new Error(`CVE not found: ${cveId}`);
|
|
86
|
+
}
|
|
87
|
+
throw new Error(`CVEDB API error: ${error.message}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Helper Function for CPE lookups using CVEDB
|
|
91
|
+
async function queryCPEDB(params) {
|
|
92
|
+
try {
|
|
93
|
+
logToFile(`Querying CVEDB for CPEs with params: ${JSON.stringify(params)}`);
|
|
94
|
+
const response = await axios.get(`${CVEDB_API_URL}/cpes`, { params });
|
|
95
|
+
return response.data;
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
if (error.response?.status === 422) {
|
|
99
|
+
throw new Error(`Invalid parameters: ${error.response.data?.detail || error.message}`);
|
|
100
|
+
}
|
|
101
|
+
throw new Error(`CVEDB API error: ${error.message}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
65
104
|
// Server Setup
|
|
66
105
|
const server = new Server({
|
|
67
106
|
name: "shodan-mcp",
|
|
@@ -87,7 +126,7 @@ server.setRequestHandler(InitializeRequestSchema, async (request) => {
|
|
|
87
126
|
name: "shodan-mcp",
|
|
88
127
|
version: "1.0.0",
|
|
89
128
|
},
|
|
90
|
-
instructions: "This server provides tools for querying Shodan, including IP lookups, searches, and
|
|
129
|
+
instructions: "This server provides tools for querying Shodan, including IP lookups, searches, vulnerabilities, and CPE lookups.",
|
|
91
130
|
};
|
|
92
131
|
});
|
|
93
132
|
// Register Tools
|
|
@@ -105,7 +144,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
105
144
|
},
|
|
106
145
|
{
|
|
107
146
|
name: "vulnerabilities",
|
|
108
|
-
description: "Retrieve vulnerability information for a CVE.",
|
|
147
|
+
description: "Retrieve vulnerability information for a CVE. Use format: CVE-YYYY-NNNNN (e.g., CVE-2021-44228)",
|
|
109
148
|
inputSchema: zodToJsonSchema(VulnerabilitiesArgsSchema),
|
|
110
149
|
},
|
|
111
150
|
{
|
|
@@ -113,6 +152,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
113
152
|
description: "Perform DNS lookups using Shodan.",
|
|
114
153
|
inputSchema: zodToJsonSchema(DnsLookupArgsSchema),
|
|
115
154
|
},
|
|
155
|
+
{
|
|
156
|
+
name: "cpe_lookup",
|
|
157
|
+
description: "Search for Common Platform Enumeration (CPE) entries by product name.",
|
|
158
|
+
inputSchema: zodToJsonSchema(CpeLookupArgsSchema),
|
|
159
|
+
},
|
|
116
160
|
];
|
|
117
161
|
logToFile("Registered tools.");
|
|
118
162
|
return { tools };
|
|
@@ -159,17 +203,45 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
159
203
|
case "vulnerabilities": {
|
|
160
204
|
const parsedVulnArgs = VulnerabilitiesArgsSchema.safeParse(args);
|
|
161
205
|
if (!parsedVulnArgs.success) {
|
|
162
|
-
throw new Error("Invalid
|
|
206
|
+
throw new Error("Invalid CVE format. Please use format: CVE-YYYY-NNNNN (e.g., CVE-2021-44228)");
|
|
207
|
+
}
|
|
208
|
+
const cveId = parsedVulnArgs.data.cve.toUpperCase();
|
|
209
|
+
logToFile(`Looking up CVE: ${cveId}`);
|
|
210
|
+
try {
|
|
211
|
+
const result = await queryCVEDB(cveId);
|
|
212
|
+
return {
|
|
213
|
+
content: [
|
|
214
|
+
{
|
|
215
|
+
type: "text",
|
|
216
|
+
text: JSON.stringify({
|
|
217
|
+
cve_id: result.cve_id,
|
|
218
|
+
summary: result.summary,
|
|
219
|
+
cvss_v3: result.cvss_v3,
|
|
220
|
+
cvss_v2: result.cvss_v2,
|
|
221
|
+
epss: result.epss,
|
|
222
|
+
ranking_epss: result.ranking_epss,
|
|
223
|
+
kev: result.kev,
|
|
224
|
+
propose_action: result.propose_action,
|
|
225
|
+
ransomware_campaign: result.ransomware_campaign,
|
|
226
|
+
published: result.published_time,
|
|
227
|
+
references: result.references,
|
|
228
|
+
affected_products: result.cpes
|
|
229
|
+
}, null, 2),
|
|
230
|
+
},
|
|
231
|
+
],
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
return {
|
|
236
|
+
content: [
|
|
237
|
+
{
|
|
238
|
+
type: "text",
|
|
239
|
+
text: error.message,
|
|
240
|
+
},
|
|
241
|
+
],
|
|
242
|
+
isError: true,
|
|
243
|
+
};
|
|
163
244
|
}
|
|
164
|
-
const result = await queryShodan(`/shodan/cve/${parsedVulnArgs.data.cve}`, {});
|
|
165
|
-
return {
|
|
166
|
-
content: [
|
|
167
|
-
{
|
|
168
|
-
type: "text",
|
|
169
|
-
text: JSON.stringify(result, null, 2),
|
|
170
|
-
},
|
|
171
|
-
],
|
|
172
|
-
};
|
|
173
245
|
}
|
|
174
246
|
case "dns_lookup": {
|
|
175
247
|
const parsedDnsArgs = DnsLookupArgsSchema.safeParse(args);
|
|
@@ -194,6 +266,48 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
194
266
|
],
|
|
195
267
|
};
|
|
196
268
|
}
|
|
269
|
+
case "cpe_lookup": {
|
|
270
|
+
const parsedCpeArgs = CpeLookupArgsSchema.safeParse(args);
|
|
271
|
+
if (!parsedCpeArgs.success) {
|
|
272
|
+
throw new Error("Invalid cpe_lookup arguments");
|
|
273
|
+
}
|
|
274
|
+
try {
|
|
275
|
+
const result = await queryCPEDB({
|
|
276
|
+
product: parsedCpeArgs.data.product,
|
|
277
|
+
count: parsedCpeArgs.data.count,
|
|
278
|
+
skip: parsedCpeArgs.data.skip,
|
|
279
|
+
limit: parsedCpeArgs.data.limit
|
|
280
|
+
});
|
|
281
|
+
// Format the response based on whether it's a count request or full CPE list
|
|
282
|
+
const formattedResult = parsedCpeArgs.data.count
|
|
283
|
+
? { total_cpes: result.total }
|
|
284
|
+
: {
|
|
285
|
+
cpes: result.cpes,
|
|
286
|
+
skip: parsedCpeArgs.data.skip,
|
|
287
|
+
limit: parsedCpeArgs.data.limit,
|
|
288
|
+
total_returned: result.cpes.length
|
|
289
|
+
};
|
|
290
|
+
return {
|
|
291
|
+
content: [
|
|
292
|
+
{
|
|
293
|
+
type: "text",
|
|
294
|
+
text: JSON.stringify(formattedResult, null, 2),
|
|
295
|
+
},
|
|
296
|
+
],
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
return {
|
|
301
|
+
content: [
|
|
302
|
+
{
|
|
303
|
+
type: "text",
|
|
304
|
+
text: error.message,
|
|
305
|
+
},
|
|
306
|
+
],
|
|
307
|
+
isError: true,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
}
|
|
197
311
|
default:
|
|
198
312
|
throw new Error(`Unknown tool: ${name}`);
|
|
199
313
|
}
|