@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.
Files changed (3) hide show
  1. package/README.md +61 -12
  2. package/build/index.js +160 -8
  3. 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
- - **Vulnerabilities**: Fetch information about known vulnerabilities (CVE)
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. Vulnerabilities Tool
29
- - Name: `vulnerabilities`
30
- - Description: Fetch information about known vulnerabilities
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
- ### 4. DNS Lookup Tool
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": "npx",
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 npx method automatically downloads and runs the latest version of the package from npm.
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 VulnerabilitiesArgsSchema = z.object({
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, and vulnerabilities. For CVE lookups, use the format CVE-YYYY-NNNNN (e.g., CVE-2021-44228).",
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: "vulnerabilities",
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(VulnerabilitiesArgsSchema),
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 "vulnerabilities": {
179
- const parsedVulnArgs = VulnerabilitiesArgsSchema.safeParse(args);
180
- if (!parsedVulnArgs.success) {
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 = parsedVulnArgs.data.cve.toUpperCase();
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
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@burtthecoder/mcp-shodan",
4
- "version": "1.0.6",
4
+ "version": "1.0.8",
5
5
  "description": "A Model Context Protocol server for Shodan API queries.",
6
6
  "main": "./build/index.js",
7
7
  "bin": {