@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.
@@ -0,0 +1,71 @@
1
+ import http from "node:http";
2
+
3
+ interface IpInfoResult {
4
+ ip: string;
5
+ country: string;
6
+ countryCode: string;
7
+ region: string;
8
+ regionName: string;
9
+ city: string;
10
+ zip: string;
11
+ lat: number;
12
+ lon: number;
13
+ timezone: string;
14
+ isp: string;
15
+ org: string;
16
+ as: string;
17
+ asn: string;
18
+ query: string;
19
+ timestamp: string;
20
+ }
21
+
22
+ function httpGet(url: string): Promise<string> {
23
+ return new Promise((resolve, reject) => {
24
+ const req = http.get(url, { timeout: 10000 }, (res) => {
25
+ let data = "";
26
+ res.on("data", (chunk: Buffer) => {
27
+ data += chunk.toString();
28
+ });
29
+ res.on("end", () => resolve(data));
30
+ res.on("error", reject);
31
+ });
32
+ req.on("error", reject);
33
+ req.on("timeout", () => {
34
+ req.destroy();
35
+ reject(new Error("Request timed out"));
36
+ });
37
+ });
38
+ }
39
+
40
+ export async function getIpInfo(ip: string): Promise<IpInfoResult> {
41
+ const url = `http://ip-api.com/json/${encodeURIComponent(ip)}?fields=status,message,country,countryCode,region,regionName,city,zip,lat,lon,timezone,isp,org,as,query`;
42
+
43
+ const raw = await httpGet(url);
44
+ const data = JSON.parse(raw);
45
+
46
+ if (data.status === "fail") {
47
+ throw new Error(`IP lookup failed: ${data.message}`);
48
+ }
49
+
50
+ const asString = (data.as as string) || "";
51
+ const asnMatch = asString.match(/^AS(\d+)/);
52
+
53
+ return {
54
+ ip,
55
+ country: data.country ?? "",
56
+ countryCode: data.countryCode ?? "",
57
+ region: data.region ?? "",
58
+ regionName: data.regionName ?? "",
59
+ city: data.city ?? "",
60
+ zip: data.zip ?? "",
61
+ lat: data.lat ?? 0,
62
+ lon: data.lon ?? 0,
63
+ timezone: data.timezone ?? "",
64
+ isp: data.isp ?? "",
65
+ org: data.org ?? "",
66
+ as: asString,
67
+ asn: asnMatch ? `AS${asnMatch[1]}` : "",
68
+ query: data.query ?? ip,
69
+ timestamp: new Date().toISOString(),
70
+ };
71
+ }
@@ -0,0 +1,122 @@
1
+ import https from "node:https";
2
+ import http from "node:http";
3
+ import { URL } from "node:url";
4
+
5
+ interface PingResult {
6
+ url: string;
7
+ requests: number;
8
+ results: Array<{
9
+ attempt: number;
10
+ statusCode: number;
11
+ latencyMs: number;
12
+ }>;
13
+ stats: {
14
+ avgMs: number;
15
+ minMs: number;
16
+ maxMs: number;
17
+ p95Ms: number;
18
+ successRate: number;
19
+ };
20
+ timestamp: string;
21
+ }
22
+
23
+ function singlePing(
24
+ url: string,
25
+ timeoutMs: number
26
+ ): Promise<{ statusCode: number; latencyMs: number }> {
27
+ return new Promise((resolve, reject) => {
28
+ const parsed = new URL(url);
29
+ const client = parsed.protocol === "https:" ? https : http;
30
+ const start = performance.now();
31
+
32
+ const req = client.get(
33
+ url,
34
+ {
35
+ timeout: timeoutMs,
36
+ headers: {
37
+ "User-Agent": "mcp-network-tools/1.0",
38
+ },
39
+ },
40
+ (res) => {
41
+ const latencyMs = Math.round((performance.now() - start) * 100) / 100;
42
+ // Consume response data to free memory
43
+ res.resume();
44
+ res.on("end", () => {
45
+ resolve({
46
+ statusCode: res.statusCode || 0,
47
+ latencyMs,
48
+ });
49
+ });
50
+ }
51
+ );
52
+
53
+ req.on("error", (err) => {
54
+ reject(err);
55
+ });
56
+
57
+ req.on("timeout", () => {
58
+ req.destroy();
59
+ reject(new Error("Request timed out"));
60
+ });
61
+ });
62
+ }
63
+
64
+ function percentile(sorted: number[], p: number): number {
65
+ const index = Math.ceil((p / 100) * sorted.length) - 1;
66
+ return sorted[Math.max(0, index)];
67
+ }
68
+
69
+ export async function httpPing(
70
+ url: string,
71
+ count: number = 5,
72
+ timeoutMs: number = 10000
73
+ ): Promise<PingResult> {
74
+ const safeCount = Math.min(Math.max(1, count), 20);
75
+
76
+ const results: Array<{
77
+ attempt: number;
78
+ statusCode: number;
79
+ latencyMs: number;
80
+ }> = [];
81
+
82
+ for (let i = 0; i < safeCount; i++) {
83
+ try {
84
+ const { statusCode, latencyMs } = await singlePing(url, timeoutMs);
85
+ results.push({ attempt: i + 1, statusCode, latencyMs });
86
+ } catch (err: unknown) {
87
+ const message = err instanceof Error ? err.message : String(err);
88
+ results.push({ attempt: i + 1, statusCode: 0, latencyMs: -1 });
89
+ }
90
+ }
91
+
92
+ const successfulLatencies = results
93
+ .filter((r) => r.latencyMs >= 0)
94
+ .map((r) => r.latencyMs)
95
+ .sort((a, b) => a - b);
96
+
97
+ const successCount = successfulLatencies.length;
98
+
99
+ const stats = {
100
+ avgMs:
101
+ successCount > 0
102
+ ? Math.round(
103
+ (successfulLatencies.reduce((a, b) => a + b, 0) / successCount) *
104
+ 100
105
+ ) / 100
106
+ : -1,
107
+ minMs: successCount > 0 ? successfulLatencies[0] : -1,
108
+ maxMs:
109
+ successCount > 0 ? successfulLatencies[successCount - 1] : -1,
110
+ p95Ms: successCount > 0 ? percentile(successfulLatencies, 95) : -1,
111
+ successRate:
112
+ Math.round((successCount / safeCount) * 10000) / 100,
113
+ };
114
+
115
+ return {
116
+ url,
117
+ requests: safeCount,
118
+ results,
119
+ stats,
120
+ timestamp: new Date().toISOString(),
121
+ };
122
+ }
@@ -0,0 +1,112 @@
1
+ import * as tls from "node:tls";
2
+ import * as net from "node:net";
3
+
4
+ interface CertificateInfo {
5
+ host: string;
6
+ valid: boolean;
7
+ issuer: Record<string, string>;
8
+ subject: Record<string, string>;
9
+ validFrom: string;
10
+ validTo: string;
11
+ daysUntilExpiry: number;
12
+ serialNumber: string;
13
+ fingerprint: string;
14
+ fingerprint256: string;
15
+ subjectAltNames: string[];
16
+ protocol: string;
17
+ cipher: string;
18
+ chain: Array<{
19
+ subject: string;
20
+ issuer: string;
21
+ validTo: string;
22
+ }>;
23
+ timestamp: string;
24
+ }
25
+
26
+ export function checkSsl(
27
+ host: string,
28
+ port: number = 443
29
+ ): Promise<CertificateInfo> {
30
+ return new Promise((resolve, reject) => {
31
+ const socket = tls.connect(
32
+ {
33
+ host,
34
+ port,
35
+ servername: host,
36
+ rejectUnauthorized: false,
37
+ timeout: 10000,
38
+ },
39
+ () => {
40
+ const cert = socket.getPeerCertificate(true);
41
+ const cipher = socket.getCipher();
42
+ const protocol = socket.getProtocol();
43
+ const authorized = socket.authorized;
44
+
45
+ if (!cert || !cert.subject) {
46
+ socket.destroy();
47
+ reject(new Error("No certificate returned by server"));
48
+ return;
49
+ }
50
+
51
+ const validTo = new Date(cert.valid_to);
52
+ const now = new Date();
53
+ const daysUntilExpiry = Math.floor(
54
+ (validTo.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)
55
+ );
56
+
57
+ const altNames = cert.subjectaltname
58
+ ? cert.subjectaltname.split(", ").map((s: string) => s.replace("DNS:", ""))
59
+ : [];
60
+
61
+ const chain: Array<{ subject: string; issuer: string; validTo: string }> =
62
+ [];
63
+ let current = cert;
64
+ const seen = new Set<string>();
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) break;
76
+ current = issuerCert;
77
+ }
78
+
79
+ const result: CertificateInfo = {
80
+ host,
81
+ valid: authorized && daysUntilExpiry > 0,
82
+ issuer: cert.issuer as unknown as Record<string, string>,
83
+ subject: cert.subject as unknown as Record<string, string>,
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
+
97
+ socket.destroy();
98
+ resolve(result);
99
+ }
100
+ );
101
+
102
+ socket.on("error", (err) => {
103
+ socket.destroy();
104
+ reject(err);
105
+ });
106
+
107
+ socket.on("timeout", () => {
108
+ socket.destroy();
109
+ reject(new Error("Connection timed out"));
110
+ });
111
+ });
112
+ }
@@ -0,0 +1,147 @@
1
+ import * as net from "node:net";
2
+
3
+ interface WhoisResult {
4
+ domain: string;
5
+ raw: string;
6
+ parsed: {
7
+ registrar: string;
8
+ creationDate: string;
9
+ expiryDate: string;
10
+ updatedDate: string;
11
+ nameServers: string[];
12
+ status: string[];
13
+ registrant: string;
14
+ };
15
+ timestamp: string;
16
+ }
17
+
18
+ function queryWhoisServer(server: string, query: string): Promise<string> {
19
+ return new Promise((resolve, reject) => {
20
+ const socket = new net.Socket();
21
+ let data = "";
22
+
23
+ socket.setTimeout(10000);
24
+
25
+ socket.connect(43, server, () => {
26
+ socket.write(query + "\r\n");
27
+ });
28
+
29
+ socket.on("data", (chunk: Buffer) => {
30
+ data += chunk.toString();
31
+ });
32
+
33
+ socket.on("end", () => {
34
+ resolve(data);
35
+ });
36
+
37
+ socket.on("timeout", () => {
38
+ socket.destroy();
39
+ reject(new Error(`WHOIS query to ${server} timed out`));
40
+ });
41
+
42
+ socket.on("error", (err) => {
43
+ reject(err);
44
+ });
45
+ });
46
+ }
47
+
48
+ function extractField(raw: string, patterns: string[]): string {
49
+ for (const pattern of patterns) {
50
+ const regex = new RegExp(`${pattern}:\\s*(.+)`, "im");
51
+ const match = raw.match(regex);
52
+ if (match) return match[1].trim();
53
+ }
54
+ return "";
55
+ }
56
+
57
+ function extractMulti(raw: string, patterns: string[]): string[] {
58
+ const results: string[] = [];
59
+ for (const pattern of patterns) {
60
+ const regex = new RegExp(`${pattern}:\\s*(.+)`, "gim");
61
+ let match: RegExpExecArray | null;
62
+ while ((match = regex.exec(raw)) !== null) {
63
+ const value = match[1].trim();
64
+ if (value && !results.includes(value)) {
65
+ results.push(value);
66
+ }
67
+ }
68
+ }
69
+ return results;
70
+ }
71
+
72
+ function getWhoisServer(tld: string): string {
73
+ const servers: Record<string, string> = {
74
+ com: "whois.verisign-grs.com",
75
+ net: "whois.verisign-grs.com",
76
+ org: "whois.pir.org",
77
+ info: "whois.afilias.net",
78
+ io: "whois.nic.io",
79
+ dev: "whois.nic.google",
80
+ app: "whois.nic.google",
81
+ co: "whois.nic.co",
82
+ me: "whois.nic.me",
83
+ ai: "whois.nic.ai",
84
+ xyz: "whois.nic.xyz",
85
+ tech: "whois.nic.tech",
86
+ };
87
+ return servers[tld] || "whois.iana.org";
88
+ }
89
+
90
+ export async function whoisLookup(domain: string): Promise<WhoisResult> {
91
+ const parts = domain.split(".");
92
+ const tld = parts[parts.length - 1].toLowerCase();
93
+
94
+ const server = getWhoisServer(tld);
95
+ let raw = await queryWhoisServer(server, domain);
96
+
97
+ // Check for referral to another WHOIS server
98
+ const referralMatch = raw.match(/Whois Server:\s*(.+)/im);
99
+ if (referralMatch) {
100
+ const referralServer = referralMatch[1].trim();
101
+ if (referralServer !== server) {
102
+ try {
103
+ const referralData = await queryWhoisServer(referralServer, domain);
104
+ if (referralData.length > raw.length) {
105
+ raw = referralData;
106
+ }
107
+ } catch {
108
+ // Use original data if referral fails
109
+ }
110
+ }
111
+ }
112
+
113
+ const parsed = {
114
+ registrar: extractField(raw, ["Registrar", "Sponsoring Registrar"]),
115
+ creationDate: extractField(raw, [
116
+ "Creation Date",
117
+ "Created Date",
118
+ "Created",
119
+ "Registration Date",
120
+ ]),
121
+ expiryDate: extractField(raw, [
122
+ "Registry Expiry Date",
123
+ "Registrar Registration Expiration Date",
124
+ "Expiry Date",
125
+ "Expiration Date",
126
+ ]),
127
+ updatedDate: extractField(raw, [
128
+ "Updated Date",
129
+ "Last Updated",
130
+ "Last Modified",
131
+ ]),
132
+ nameServers: extractMulti(raw, ["Name Server", "Nameserver", "nserver"]),
133
+ status: extractMulti(raw, ["Domain Status", "Status"]),
134
+ registrant: extractField(raw, [
135
+ "Registrant Organization",
136
+ "Registrant Name",
137
+ "Registrant",
138
+ ]),
139
+ };
140
+
141
+ return {
142
+ domain,
143
+ raw,
144
+ parsed,
145
+ timestamp: new Date().toISOString(),
146
+ };
147
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "Node16",
5
+ "moduleResolution": "Node16",
6
+ "outDir": "dist",
7
+ "rootDir": "src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "declaration": true,
12
+ "sourceMap": true
13
+ },
14
+ "include": ["src/**/*"]
15
+ }