@burtthecoder/mcp-shodan 1.0.4 → 1.0.6
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/build/index.js +73 -18
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -7,19 +7,27 @@ import dotenv from "dotenv";
|
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
9
9
|
import fs from "fs";
|
|
10
|
+
import path from "path";
|
|
11
|
+
import os from "os";
|
|
10
12
|
dotenv.config();
|
|
11
|
-
const logFilePath = "
|
|
13
|
+
const logFilePath = path.join(os.tmpdir(), "mcp-shodan-server.log");
|
|
12
14
|
const SHODAN_API_KEY = process.env.SHODAN_API_KEY;
|
|
13
15
|
if (!SHODAN_API_KEY) {
|
|
14
16
|
throw new Error("SHODAN_API_KEY environment variable is required.");
|
|
15
17
|
}
|
|
16
18
|
const API_BASE_URL = "https://api.shodan.io";
|
|
19
|
+
const CVEDB_API_URL = "https://cvedb.shodan.io";
|
|
17
20
|
// Logging Helper Function
|
|
18
21
|
function logToFile(message) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
try {
|
|
23
|
+
const timestamp = new Date().toISOString();
|
|
24
|
+
const formattedMessage = `[${timestamp}] ${message}\n`;
|
|
25
|
+
fs.appendFileSync(logFilePath, formattedMessage, "utf8");
|
|
26
|
+
console.error(formattedMessage.trim()); // Use stderr for logging to avoid interfering with stdout
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
console.error(`Failed to write to log file: ${error}`);
|
|
30
|
+
}
|
|
23
31
|
}
|
|
24
32
|
// Tool Schemas
|
|
25
33
|
const IpLookupArgsSchema = z.object({
|
|
@@ -34,7 +42,9 @@ const SearchArgsSchema = z.object({
|
|
|
34
42
|
.describe("Maximum results to return."),
|
|
35
43
|
});
|
|
36
44
|
const VulnerabilitiesArgsSchema = z.object({
|
|
37
|
-
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)."),
|
|
38
48
|
});
|
|
39
49
|
const DnsLookupArgsSchema = z.object({
|
|
40
50
|
hostnames: z.array(z.string()).describe("List of hostnames to resolve."),
|
|
@@ -54,6 +64,23 @@ async function queryShodan(endpoint, params) {
|
|
|
54
64
|
throw new Error(`Shodan API error: ${errorMessage}`);
|
|
55
65
|
}
|
|
56
66
|
}
|
|
67
|
+
// Helper Function for CVE lookups using CVEDB
|
|
68
|
+
async function queryCVEDB(cveId) {
|
|
69
|
+
try {
|
|
70
|
+
logToFile(`Querying CVEDB for: ${cveId}`);
|
|
71
|
+
const response = await axios.get(`${CVEDB_API_URL}/cve/${cveId}`);
|
|
72
|
+
return response.data;
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (error.response?.status === 422) {
|
|
76
|
+
throw new Error(`Invalid CVE ID format: ${cveId}`);
|
|
77
|
+
}
|
|
78
|
+
if (error.response?.status === 404) {
|
|
79
|
+
throw new Error(`CVE not found: ${cveId}`);
|
|
80
|
+
}
|
|
81
|
+
throw new Error(`CVEDB API error: ${error.message}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
57
84
|
// Server Setup
|
|
58
85
|
const server = new Server({
|
|
59
86
|
name: "shodan-mcp",
|
|
@@ -79,7 +106,7 @@ server.setRequestHandler(InitializeRequestSchema, async (request) => {
|
|
|
79
106
|
name: "shodan-mcp",
|
|
80
107
|
version: "1.0.0",
|
|
81
108
|
},
|
|
82
|
-
instructions: "This server provides tools for querying Shodan, including IP lookups, searches, and vulnerabilities.",
|
|
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).",
|
|
83
110
|
};
|
|
84
111
|
});
|
|
85
112
|
// Register Tools
|
|
@@ -97,7 +124,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
97
124
|
},
|
|
98
125
|
{
|
|
99
126
|
name: "vulnerabilities",
|
|
100
|
-
description: "Retrieve vulnerability information for a CVE.",
|
|
127
|
+
description: "Retrieve vulnerability information for a CVE. Use format: CVE-YYYY-NNNNN (e.g., CVE-2021-44228)",
|
|
101
128
|
inputSchema: zodToJsonSchema(VulnerabilitiesArgsSchema),
|
|
102
129
|
},
|
|
103
130
|
{
|
|
@@ -151,17 +178,45 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
151
178
|
case "vulnerabilities": {
|
|
152
179
|
const parsedVulnArgs = VulnerabilitiesArgsSchema.safeParse(args);
|
|
153
180
|
if (!parsedVulnArgs.success) {
|
|
154
|
-
throw new Error("Invalid
|
|
181
|
+
throw new Error("Invalid CVE format. Please use format: CVE-YYYY-NNNNN (e.g., CVE-2021-44228)");
|
|
182
|
+
}
|
|
183
|
+
const cveId = parsedVulnArgs.data.cve.toUpperCase();
|
|
184
|
+
logToFile(`Looking up CVE: ${cveId}`);
|
|
185
|
+
try {
|
|
186
|
+
const result = await queryCVEDB(cveId);
|
|
187
|
+
return {
|
|
188
|
+
content: [
|
|
189
|
+
{
|
|
190
|
+
type: "text",
|
|
191
|
+
text: JSON.stringify({
|
|
192
|
+
cve_id: result.cve_id,
|
|
193
|
+
summary: result.summary,
|
|
194
|
+
cvss_v3: result.cvss_v3,
|
|
195
|
+
cvss_v2: result.cvss_v2,
|
|
196
|
+
epss: result.epss,
|
|
197
|
+
ranking_epss: result.ranking_epss,
|
|
198
|
+
kev: result.kev,
|
|
199
|
+
propose_action: result.propose_action,
|
|
200
|
+
ransomware_campaign: result.ransomware_campaign,
|
|
201
|
+
published: result.published_time,
|
|
202
|
+
references: result.references,
|
|
203
|
+
affected_products: result.cpes
|
|
204
|
+
}, null, 2),
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
return {
|
|
211
|
+
content: [
|
|
212
|
+
{
|
|
213
|
+
type: "text",
|
|
214
|
+
text: error.message,
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
isError: true,
|
|
218
|
+
};
|
|
155
219
|
}
|
|
156
|
-
const result = await queryShodan(`/shodan/cve/${parsedVulnArgs.data.cve}`, {});
|
|
157
|
-
return {
|
|
158
|
-
content: [
|
|
159
|
-
{
|
|
160
|
-
type: "text",
|
|
161
|
-
text: JSON.stringify(result, null, 2),
|
|
162
|
-
},
|
|
163
|
-
],
|
|
164
|
-
};
|
|
165
220
|
}
|
|
166
221
|
case "dns_lookup": {
|
|
167
222
|
const parsedDnsArgs = DnsLookupArgsSchema.safeParse(args);
|