@rog0x/mcp-network-tools 1.0.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 +82 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +192 -0
- package/dist/tools/dns-lookup.d.ts +20 -0
- package/dist/tools/dns-lookup.js +132 -0
- package/dist/tools/ip-info.d.ts +20 -0
- package/dist/tools/ip-info.js +53 -0
- package/dist/tools/ping-tool.d.ts +19 -0
- package/dist/tools/ping-tool.js +80 -0
- package/dist/tools/ssl-checker.d.ts +23 -0
- package/dist/tools/ssl-checker.js +109 -0
- package/dist/tools/whois-lookup.d.ts +16 -0
- package/dist/tools/whois-lookup.js +156 -0
- package/package.json +20 -0
- package/src/index.ts +222 -0
- package/src/tools/dns-lookup.ts +131 -0
- package/src/tools/ip-info.ts +71 -0
- package/src/tools/ping-tool.ts +122 -0
- package/src/tools/ssl-checker.ts +112 -0
- package/src/tools/whois-lookup.ts +147 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.checkSsl = checkSsl;
|
|
37
|
+
const tls = __importStar(require("node:tls"));
|
|
38
|
+
function checkSsl(host, port = 443) {
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
const socket = tls.connect({
|
|
41
|
+
host,
|
|
42
|
+
port,
|
|
43
|
+
servername: host,
|
|
44
|
+
rejectUnauthorized: false,
|
|
45
|
+
timeout: 10000,
|
|
46
|
+
}, () => {
|
|
47
|
+
const cert = socket.getPeerCertificate(true);
|
|
48
|
+
const cipher = socket.getCipher();
|
|
49
|
+
const protocol = socket.getProtocol();
|
|
50
|
+
const authorized = socket.authorized;
|
|
51
|
+
if (!cert || !cert.subject) {
|
|
52
|
+
socket.destroy();
|
|
53
|
+
reject(new Error("No certificate returned by server"));
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const validTo = new Date(cert.valid_to);
|
|
57
|
+
const now = new Date();
|
|
58
|
+
const daysUntilExpiry = Math.floor((validTo.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
|
|
59
|
+
const altNames = cert.subjectaltname
|
|
60
|
+
? cert.subjectaltname.split(", ").map((s) => s.replace("DNS:", ""))
|
|
61
|
+
: [];
|
|
62
|
+
const chain = [];
|
|
63
|
+
let current = cert;
|
|
64
|
+
const seen = new Set();
|
|
65
|
+
while (current && current.issuerCertificate && !seen.has(current.fingerprint256)) {
|
|
66
|
+
seen.add(current.fingerprint256);
|
|
67
|
+
const issuerCert = current.issuerCertificate;
|
|
68
|
+
const subjectCN = issuerCert.subject?.CN;
|
|
69
|
+
const issuerCN = issuerCert.issuer?.CN;
|
|
70
|
+
chain.push({
|
|
71
|
+
subject: (Array.isArray(subjectCN) ? subjectCN[0] : subjectCN) || "Unknown",
|
|
72
|
+
issuer: (Array.isArray(issuerCN) ? issuerCN[0] : issuerCN) || "Unknown",
|
|
73
|
+
validTo: issuerCert.valid_to,
|
|
74
|
+
});
|
|
75
|
+
if (issuerCert.fingerprint256 === current.fingerprint256)
|
|
76
|
+
break;
|
|
77
|
+
current = issuerCert;
|
|
78
|
+
}
|
|
79
|
+
const result = {
|
|
80
|
+
host,
|
|
81
|
+
valid: authorized && daysUntilExpiry > 0,
|
|
82
|
+
issuer: cert.issuer,
|
|
83
|
+
subject: cert.subject,
|
|
84
|
+
validFrom: cert.valid_from,
|
|
85
|
+
validTo: cert.valid_to,
|
|
86
|
+
daysUntilExpiry,
|
|
87
|
+
serialNumber: cert.serialNumber,
|
|
88
|
+
fingerprint: cert.fingerprint,
|
|
89
|
+
fingerprint256: cert.fingerprint256,
|
|
90
|
+
subjectAltNames: altNames,
|
|
91
|
+
protocol: protocol || "unknown",
|
|
92
|
+
cipher: cipher?.name || "unknown",
|
|
93
|
+
chain,
|
|
94
|
+
timestamp: new Date().toISOString(),
|
|
95
|
+
};
|
|
96
|
+
socket.destroy();
|
|
97
|
+
resolve(result);
|
|
98
|
+
});
|
|
99
|
+
socket.on("error", (err) => {
|
|
100
|
+
socket.destroy();
|
|
101
|
+
reject(err);
|
|
102
|
+
});
|
|
103
|
+
socket.on("timeout", () => {
|
|
104
|
+
socket.destroy();
|
|
105
|
+
reject(new Error("Connection timed out"));
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=ssl-checker.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
interface WhoisResult {
|
|
2
|
+
domain: string;
|
|
3
|
+
raw: string;
|
|
4
|
+
parsed: {
|
|
5
|
+
registrar: string;
|
|
6
|
+
creationDate: string;
|
|
7
|
+
expiryDate: string;
|
|
8
|
+
updatedDate: string;
|
|
9
|
+
nameServers: string[];
|
|
10
|
+
status: string[];
|
|
11
|
+
registrant: string;
|
|
12
|
+
};
|
|
13
|
+
timestamp: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function whoisLookup(domain: string): Promise<WhoisResult>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.whoisLookup = whoisLookup;
|
|
37
|
+
const net = __importStar(require("node:net"));
|
|
38
|
+
function queryWhoisServer(server, query) {
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
const socket = new net.Socket();
|
|
41
|
+
let data = "";
|
|
42
|
+
socket.setTimeout(10000);
|
|
43
|
+
socket.connect(43, server, () => {
|
|
44
|
+
socket.write(query + "\r\n");
|
|
45
|
+
});
|
|
46
|
+
socket.on("data", (chunk) => {
|
|
47
|
+
data += chunk.toString();
|
|
48
|
+
});
|
|
49
|
+
socket.on("end", () => {
|
|
50
|
+
resolve(data);
|
|
51
|
+
});
|
|
52
|
+
socket.on("timeout", () => {
|
|
53
|
+
socket.destroy();
|
|
54
|
+
reject(new Error(`WHOIS query to ${server} timed out`));
|
|
55
|
+
});
|
|
56
|
+
socket.on("error", (err) => {
|
|
57
|
+
reject(err);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function extractField(raw, patterns) {
|
|
62
|
+
for (const pattern of patterns) {
|
|
63
|
+
const regex = new RegExp(`${pattern}:\\s*(.+)`, "im");
|
|
64
|
+
const match = raw.match(regex);
|
|
65
|
+
if (match)
|
|
66
|
+
return match[1].trim();
|
|
67
|
+
}
|
|
68
|
+
return "";
|
|
69
|
+
}
|
|
70
|
+
function extractMulti(raw, patterns) {
|
|
71
|
+
const results = [];
|
|
72
|
+
for (const pattern of patterns) {
|
|
73
|
+
const regex = new RegExp(`${pattern}:\\s*(.+)`, "gim");
|
|
74
|
+
let match;
|
|
75
|
+
while ((match = regex.exec(raw)) !== null) {
|
|
76
|
+
const value = match[1].trim();
|
|
77
|
+
if (value && !results.includes(value)) {
|
|
78
|
+
results.push(value);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return results;
|
|
83
|
+
}
|
|
84
|
+
function getWhoisServer(tld) {
|
|
85
|
+
const servers = {
|
|
86
|
+
com: "whois.verisign-grs.com",
|
|
87
|
+
net: "whois.verisign-grs.com",
|
|
88
|
+
org: "whois.pir.org",
|
|
89
|
+
info: "whois.afilias.net",
|
|
90
|
+
io: "whois.nic.io",
|
|
91
|
+
dev: "whois.nic.google",
|
|
92
|
+
app: "whois.nic.google",
|
|
93
|
+
co: "whois.nic.co",
|
|
94
|
+
me: "whois.nic.me",
|
|
95
|
+
ai: "whois.nic.ai",
|
|
96
|
+
xyz: "whois.nic.xyz",
|
|
97
|
+
tech: "whois.nic.tech",
|
|
98
|
+
};
|
|
99
|
+
return servers[tld] || "whois.iana.org";
|
|
100
|
+
}
|
|
101
|
+
async function whoisLookup(domain) {
|
|
102
|
+
const parts = domain.split(".");
|
|
103
|
+
const tld = parts[parts.length - 1].toLowerCase();
|
|
104
|
+
const server = getWhoisServer(tld);
|
|
105
|
+
let raw = await queryWhoisServer(server, domain);
|
|
106
|
+
// Check for referral to another WHOIS server
|
|
107
|
+
const referralMatch = raw.match(/Whois Server:\s*(.+)/im);
|
|
108
|
+
if (referralMatch) {
|
|
109
|
+
const referralServer = referralMatch[1].trim();
|
|
110
|
+
if (referralServer !== server) {
|
|
111
|
+
try {
|
|
112
|
+
const referralData = await queryWhoisServer(referralServer, domain);
|
|
113
|
+
if (referralData.length > raw.length) {
|
|
114
|
+
raw = referralData;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// Use original data if referral fails
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const parsed = {
|
|
123
|
+
registrar: extractField(raw, ["Registrar", "Sponsoring Registrar"]),
|
|
124
|
+
creationDate: extractField(raw, [
|
|
125
|
+
"Creation Date",
|
|
126
|
+
"Created Date",
|
|
127
|
+
"Created",
|
|
128
|
+
"Registration Date",
|
|
129
|
+
]),
|
|
130
|
+
expiryDate: extractField(raw, [
|
|
131
|
+
"Registry Expiry Date",
|
|
132
|
+
"Registrar Registration Expiration Date",
|
|
133
|
+
"Expiry Date",
|
|
134
|
+
"Expiration Date",
|
|
135
|
+
]),
|
|
136
|
+
updatedDate: extractField(raw, [
|
|
137
|
+
"Updated Date",
|
|
138
|
+
"Last Updated",
|
|
139
|
+
"Last Modified",
|
|
140
|
+
]),
|
|
141
|
+
nameServers: extractMulti(raw, ["Name Server", "Nameserver", "nserver"]),
|
|
142
|
+
status: extractMulti(raw, ["Domain Status", "Status"]),
|
|
143
|
+
registrant: extractField(raw, [
|
|
144
|
+
"Registrant Organization",
|
|
145
|
+
"Registrant Name",
|
|
146
|
+
"Registrant",
|
|
147
|
+
]),
|
|
148
|
+
};
|
|
149
|
+
return {
|
|
150
|
+
domain,
|
|
151
|
+
raw,
|
|
152
|
+
parsed,
|
|
153
|
+
timestamp: new Date().toISOString(),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=whois-lookup.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rog0x/mcp-network-tools",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Network diagnostics tools for AI agents via MCP",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"start": "node dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"keywords": ["mcp", "network", "diagnostics", "dns", "ssl", "whois", "ping"],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@modelcontextprotocol/sdk": "^1.12.1"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"typescript": "^5.7.0",
|
|
18
|
+
"@types/node": "^22.0.0"
|
|
19
|
+
}
|
|
20
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import {
|
|
6
|
+
CallToolRequestSchema,
|
|
7
|
+
ListToolsRequestSchema,
|
|
8
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
9
|
+
import { dnsLookup, reverseDns, checkPropagation } from "./tools/dns-lookup.js";
|
|
10
|
+
import { getIpInfo } from "./tools/ip-info.js";
|
|
11
|
+
import { checkSsl } from "./tools/ssl-checker.js";
|
|
12
|
+
import { whoisLookup } from "./tools/whois-lookup.js";
|
|
13
|
+
import { httpPing } from "./tools/ping-tool.js";
|
|
14
|
+
|
|
15
|
+
const server = new Server(
|
|
16
|
+
{
|
|
17
|
+
name: "mcp-network-tools",
|
|
18
|
+
version: "1.0.0",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
capabilities: {
|
|
22
|
+
tools: {},
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
28
|
+
tools: [
|
|
29
|
+
{
|
|
30
|
+
name: "dns_lookup",
|
|
31
|
+
description:
|
|
32
|
+
"Resolve a domain name to DNS records (A, AAAA, MX, TXT, NS, CNAME, SOA). Supports reverse DNS and propagation checking.",
|
|
33
|
+
inputSchema: {
|
|
34
|
+
type: "object" as const,
|
|
35
|
+
properties: {
|
|
36
|
+
domain: {
|
|
37
|
+
type: "string",
|
|
38
|
+
description: "Domain name to look up (e.g. example.com)",
|
|
39
|
+
},
|
|
40
|
+
record_types: {
|
|
41
|
+
type: "array",
|
|
42
|
+
items: { type: "string" },
|
|
43
|
+
description:
|
|
44
|
+
"Record types to query (default: all). Options: A, AAAA, MX, TXT, NS, CNAME, SOA",
|
|
45
|
+
},
|
|
46
|
+
mode: {
|
|
47
|
+
type: "string",
|
|
48
|
+
enum: ["resolve", "reverse", "propagation"],
|
|
49
|
+
description:
|
|
50
|
+
"Mode: 'resolve' (default) for forward DNS, 'reverse' for reverse DNS (pass IP in domain), 'propagation' to check across public DNS servers",
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
required: ["domain"],
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: "ip_info",
|
|
58
|
+
description:
|
|
59
|
+
"Get geolocation and network information for an IP address: country, city, ISP, ASN, coordinates. Supports IPv4 and IPv6.",
|
|
60
|
+
inputSchema: {
|
|
61
|
+
type: "object" as const,
|
|
62
|
+
properties: {
|
|
63
|
+
ip: {
|
|
64
|
+
type: "string",
|
|
65
|
+
description: "IPv4 or IPv6 address to look up",
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
required: ["ip"],
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: "ssl_check",
|
|
73
|
+
description:
|
|
74
|
+
"Check the SSL/TLS certificate of a host: validity, issuer, expiry date, days until expiry, subject alternative names, certificate chain, cipher, and protocol.",
|
|
75
|
+
inputSchema: {
|
|
76
|
+
type: "object" as const,
|
|
77
|
+
properties: {
|
|
78
|
+
host: {
|
|
79
|
+
type: "string",
|
|
80
|
+
description: "Hostname to check (e.g. example.com)",
|
|
81
|
+
},
|
|
82
|
+
port: {
|
|
83
|
+
type: "number",
|
|
84
|
+
description: "Port number (default: 443)",
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
required: ["host"],
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: "whois_lookup",
|
|
92
|
+
description:
|
|
93
|
+
"Look up WHOIS information for a domain: registrar, creation date, expiry date, nameservers, and domain status.",
|
|
94
|
+
inputSchema: {
|
|
95
|
+
type: "object" as const,
|
|
96
|
+
properties: {
|
|
97
|
+
domain: {
|
|
98
|
+
type: "string",
|
|
99
|
+
description: "Domain name to look up (e.g. example.com)",
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
required: ["domain"],
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: "http_ping",
|
|
107
|
+
description:
|
|
108
|
+
"Measure HTTP response time to a URL over multiple requests. Returns per-request latency and statistics: average, min, max, p95, and success rate.",
|
|
109
|
+
inputSchema: {
|
|
110
|
+
type: "object" as const,
|
|
111
|
+
properties: {
|
|
112
|
+
url: {
|
|
113
|
+
type: "string",
|
|
114
|
+
description: "URL to ping (e.g. https://example.com)",
|
|
115
|
+
},
|
|
116
|
+
count: {
|
|
117
|
+
type: "number",
|
|
118
|
+
description: "Number of requests to send (default: 5, max: 20)",
|
|
119
|
+
},
|
|
120
|
+
timeout_ms: {
|
|
121
|
+
type: "number",
|
|
122
|
+
description: "Timeout per request in milliseconds (default: 10000)",
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
required: ["url"],
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
}));
|
|
130
|
+
|
|
131
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
132
|
+
const { name, arguments: args } = request.params;
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
switch (name) {
|
|
136
|
+
case "dns_lookup": {
|
|
137
|
+
const domain = args?.domain as string;
|
|
138
|
+
const mode = (args?.mode as string) || "resolve";
|
|
139
|
+
const recordTypes = args?.record_types as string[] | undefined;
|
|
140
|
+
|
|
141
|
+
let result: unknown;
|
|
142
|
+
switch (mode) {
|
|
143
|
+
case "reverse":
|
|
144
|
+
result = await reverseDns(domain);
|
|
145
|
+
break;
|
|
146
|
+
case "propagation":
|
|
147
|
+
result = await checkPropagation(
|
|
148
|
+
domain,
|
|
149
|
+
recordTypes?.[0] || "A"
|
|
150
|
+
);
|
|
151
|
+
break;
|
|
152
|
+
default:
|
|
153
|
+
result = await dnsLookup(domain, recordTypes);
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
case "ip_info": {
|
|
163
|
+
const result = await getIpInfo(args?.ip as string);
|
|
164
|
+
return {
|
|
165
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
case "ssl_check": {
|
|
170
|
+
const port = (args?.port as number) || 443;
|
|
171
|
+
const result = await checkSsl(args?.host as string, port);
|
|
172
|
+
return {
|
|
173
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
case "whois_lookup": {
|
|
178
|
+
const result = await whoisLookup(args?.domain as string);
|
|
179
|
+
// Return parsed data without the full raw WHOIS by default to save tokens
|
|
180
|
+
const { raw, ...summary } = result;
|
|
181
|
+
return {
|
|
182
|
+
content: [
|
|
183
|
+
{
|
|
184
|
+
type: "text",
|
|
185
|
+
text: JSON.stringify(
|
|
186
|
+
{ ...summary, rawLength: raw.length },
|
|
187
|
+
null,
|
|
188
|
+
2
|
|
189
|
+
),
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
case "http_ping": {
|
|
196
|
+
const count = (args?.count as number) || 5;
|
|
197
|
+
const timeoutMs = (args?.timeout_ms as number) || 10000;
|
|
198
|
+
const result = await httpPing(args?.url as string, count, timeoutMs);
|
|
199
|
+
return {
|
|
200
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
default:
|
|
205
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
206
|
+
}
|
|
207
|
+
} catch (error: unknown) {
|
|
208
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
209
|
+
return {
|
|
210
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
211
|
+
isError: true,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
async function main() {
|
|
217
|
+
const transport = new StdioServerTransport();
|
|
218
|
+
await server.connect(transport);
|
|
219
|
+
console.error("MCP Network Tools server running on stdio");
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import * as dns from "node:dns";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
|
|
4
|
+
const resolve4 = promisify(dns.resolve4);
|
|
5
|
+
const resolve6 = promisify(dns.resolve6);
|
|
6
|
+
const resolveMx = promisify(dns.resolveMx);
|
|
7
|
+
const resolveTxt = promisify(dns.resolveTxt);
|
|
8
|
+
const resolveNs = promisify(dns.resolveNs);
|
|
9
|
+
const resolveCname = promisify(dns.resolveCname);
|
|
10
|
+
const resolveSoa = promisify(dns.resolveSoa);
|
|
11
|
+
const reverseLookup = promisify(dns.reverse);
|
|
12
|
+
|
|
13
|
+
interface DnsLookupResult {
|
|
14
|
+
domain: string;
|
|
15
|
+
records: Record<string, unknown>;
|
|
16
|
+
timestamp: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface ReverseDnsResult {
|
|
20
|
+
ip: string;
|
|
21
|
+
hostnames: string[];
|
|
22
|
+
timestamp: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function safeResolve<T>(fn: () => Promise<T>): Promise<T | null> {
|
|
26
|
+
try {
|
|
27
|
+
return await fn();
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function dnsLookup(
|
|
34
|
+
domain: string,
|
|
35
|
+
recordTypes?: string[]
|
|
36
|
+
): Promise<DnsLookupResult> {
|
|
37
|
+
const types = recordTypes?.map((t) => t.toUpperCase()) ?? [
|
|
38
|
+
"A",
|
|
39
|
+
"AAAA",
|
|
40
|
+
"MX",
|
|
41
|
+
"TXT",
|
|
42
|
+
"NS",
|
|
43
|
+
"CNAME",
|
|
44
|
+
"SOA",
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
const records: Record<string, unknown> = {};
|
|
48
|
+
|
|
49
|
+
const resolvers: Record<string, () => Promise<unknown>> = {
|
|
50
|
+
A: () => resolve4(domain),
|
|
51
|
+
AAAA: () => resolve6(domain),
|
|
52
|
+
MX: () => resolveMx(domain),
|
|
53
|
+
TXT: () => resolveTxt(domain),
|
|
54
|
+
NS: () => resolveNs(domain),
|
|
55
|
+
CNAME: () => resolveCname(domain),
|
|
56
|
+
SOA: () => resolveSoa(domain),
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
await Promise.all(
|
|
60
|
+
types.map(async (type) => {
|
|
61
|
+
const resolver = resolvers[type];
|
|
62
|
+
if (resolver) {
|
|
63
|
+
const result = await safeResolve(resolver);
|
|
64
|
+
if (result !== null) {
|
|
65
|
+
records[type] = result;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
domain,
|
|
73
|
+
records,
|
|
74
|
+
timestamp: new Date().toISOString(),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function reverseDns(ip: string): Promise<ReverseDnsResult> {
|
|
79
|
+
const hostnames = await reverseLookup(ip);
|
|
80
|
+
return {
|
|
81
|
+
ip,
|
|
82
|
+
hostnames,
|
|
83
|
+
timestamp: new Date().toISOString(),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function checkPropagation(
|
|
88
|
+
domain: string,
|
|
89
|
+
recordType: string = "A"
|
|
90
|
+
): Promise<{
|
|
91
|
+
domain: string;
|
|
92
|
+
recordType: string;
|
|
93
|
+
servers: Record<string, unknown>;
|
|
94
|
+
consistent: boolean;
|
|
95
|
+
timestamp: string;
|
|
96
|
+
}> {
|
|
97
|
+
const dnsServers: Record<string, string> = {
|
|
98
|
+
"Google": "8.8.8.8",
|
|
99
|
+
"Cloudflare": "1.1.1.1",
|
|
100
|
+
"OpenDNS": "208.67.222.222",
|
|
101
|
+
"Quad9": "9.9.9.9",
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const resolver = new dns.Resolver();
|
|
105
|
+
const resolveWithServer = promisify(resolver.resolve.bind(resolver));
|
|
106
|
+
|
|
107
|
+
const results: Record<string, unknown> = {};
|
|
108
|
+
const allRecords: string[] = [];
|
|
109
|
+
|
|
110
|
+
for (const [name, server] of Object.entries(dnsServers)) {
|
|
111
|
+
try {
|
|
112
|
+
resolver.setServers([server]);
|
|
113
|
+
const records = await resolveWithServer(domain, recordType);
|
|
114
|
+
results[name] = { server, records };
|
|
115
|
+
allRecords.push(JSON.stringify(records));
|
|
116
|
+
} catch (err: unknown) {
|
|
117
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
118
|
+
results[name] = { server, error: message };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const uniqueResults = new Set(allRecords);
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
domain,
|
|
126
|
+
recordType,
|
|
127
|
+
servers: results,
|
|
128
|
+
consistent: uniqueResults.size <= 1,
|
|
129
|
+
timestamp: new Date().toISOString(),
|
|
130
|
+
};
|
|
131
|
+
}
|