it-tools-mcp 3.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,578 @@
1
+ import { isValidPhoneNumber, parsePhoneNumber } from "libphonenumber-js";
2
+ import IBAN from "iban";
3
+ import { z } from "zod";
4
+ export function registerNetworkTools(server) {
5
+ // IP address tools
6
+ server.tool("ip-subnet-calculator", "Calculate subnet information for IPv4", {
7
+ ip: z.string().describe("IPv4 address (e.g., 192.168.1.1)"),
8
+ cidr: z.number().describe("CIDR notation (e.g., 24)"),
9
+ }, async ({ ip, cidr }) => {
10
+ try {
11
+ if (cidr < 1 || cidr > 32) {
12
+ return {
13
+ content: [
14
+ {
15
+ type: "text",
16
+ text: "CIDR must be between 1 and 32.",
17
+ },
18
+ ],
19
+ };
20
+ }
21
+ const ipParts = ip.split('.').map(part => {
22
+ const num = parseInt(part);
23
+ if (isNaN(num) || num < 0 || num > 255) {
24
+ throw new Error(`Invalid IP address part: ${part}`);
25
+ }
26
+ return num;
27
+ });
28
+ if (ipParts.length !== 4) {
29
+ throw new Error("Invalid IP address format");
30
+ }
31
+ // Calculate subnet mask
32
+ const mask = (0xFFFFFFFF << (32 - cidr)) >>> 0;
33
+ const maskParts = [
34
+ (mask >>> 24) & 0xFF,
35
+ (mask >>> 16) & 0xFF,
36
+ (mask >>> 8) & 0xFF,
37
+ mask & 0xFF
38
+ ];
39
+ // Calculate network address
40
+ const ipNum = (ipParts[0] << 24) | (ipParts[1] << 16) | (ipParts[2] << 8) | ipParts[3];
41
+ const networkNum = (ipNum & mask) >>> 0;
42
+ const networkParts = [
43
+ (networkNum >>> 24) & 0xFF,
44
+ (networkNum >>> 16) & 0xFF,
45
+ (networkNum >>> 8) & 0xFF,
46
+ networkNum & 0xFF
47
+ ];
48
+ // Calculate broadcast address
49
+ const broadcastNum = (networkNum | (0xFFFFFFFF >>> cidr)) >>> 0;
50
+ const broadcastParts = [
51
+ (broadcastNum >>> 24) & 0xFF,
52
+ (broadcastNum >>> 16) & 0xFF,
53
+ (broadcastNum >>> 8) & 0xFF,
54
+ broadcastNum & 0xFF
55
+ ];
56
+ // Calculate first and last usable addresses
57
+ const firstUsableNum = networkNum + 1;
58
+ const lastUsableNum = broadcastNum - 1;
59
+ const firstUsableParts = [
60
+ (firstUsableNum >>> 24) & 0xFF,
61
+ (firstUsableNum >>> 16) & 0xFF,
62
+ (firstUsableNum >>> 8) & 0xFF,
63
+ firstUsableNum & 0xFF
64
+ ];
65
+ const lastUsableParts = [
66
+ (lastUsableNum >>> 24) & 0xFF,
67
+ (lastUsableNum >>> 16) & 0xFF,
68
+ (lastUsableNum >>> 8) & 0xFF,
69
+ lastUsableNum & 0xFF
70
+ ];
71
+ const totalHosts = Math.pow(2, 32 - cidr);
72
+ const usableHosts = totalHosts - 2;
73
+ return {
74
+ content: [
75
+ {
76
+ type: "text",
77
+ text: `IPv4 Subnet Information:
78
+
79
+ Input: ${ip}/${cidr}
80
+
81
+ Network Address: ${networkParts.join('.')}
82
+ Subnet Mask: ${maskParts.join('.')}
83
+ Broadcast Address: ${broadcastParts.join('.')}
84
+ First Usable: ${firstUsableParts.join('.')}
85
+ Last Usable: ${lastUsableParts.join('.')}
86
+
87
+ Total Addresses: ${totalHosts}
88
+ Usable Addresses: ${usableHosts}
89
+ CIDR: /${cidr}`,
90
+ },
91
+ ],
92
+ };
93
+ }
94
+ catch (error) {
95
+ return {
96
+ content: [
97
+ {
98
+ type: "text",
99
+ text: `Error calculating subnet: ${error instanceof Error ? error.message : 'Unknown error'}`,
100
+ },
101
+ ],
102
+ };
103
+ }
104
+ });
105
+ // Enhanced IPv4 subnet calculator
106
+ server.tool("ipv4-subnet-calc", "Calculate IPv4 subnet information", {
107
+ cidr: z.string().describe("IPv4 CIDR notation (e.g., 192.168.1.0/24)"),
108
+ }, async ({ cidr }) => {
109
+ try {
110
+ const [ip, prefixLength] = cidr.split('/');
111
+ const prefix = parseInt(prefixLength);
112
+ if (isNaN(prefix) || prefix < 0 || prefix > 32) {
113
+ throw new Error("Invalid CIDR prefix length");
114
+ }
115
+ const ipParts = ip.split('.').map(part => {
116
+ const num = parseInt(part);
117
+ if (isNaN(num) || num < 0 || num > 255) {
118
+ throw new Error(`Invalid IP address part: ${part}`);
119
+ }
120
+ return num;
121
+ });
122
+ if (ipParts.length !== 4) {
123
+ throw new Error("Invalid IP address format");
124
+ }
125
+ // Calculate all subnet information
126
+ const mask = (0xFFFFFFFF << (32 - prefix)) >>> 0;
127
+ const inverseMask = (0xFFFFFFFF >>> prefix);
128
+ const maskOctets = [
129
+ (mask >>> 24) & 0xFF,
130
+ (mask >>> 16) & 0xFF,
131
+ (mask >>> 8) & 0xFF,
132
+ mask & 0xFF
133
+ ];
134
+ const wildcardOctets = [
135
+ inverseMask >>> 24,
136
+ (inverseMask >>> 16) & 0xFF,
137
+ (inverseMask >>> 8) & 0xFF,
138
+ inverseMask & 0xFF
139
+ ];
140
+ const ipNum = (ipParts[0] << 24) | (ipParts[1] << 16) | (ipParts[2] << 8) | ipParts[3];
141
+ const networkNum = (ipNum & mask) >>> 0;
142
+ const broadcastNum = (networkNum | inverseMask) >>> 0;
143
+ const networkOctets = [
144
+ (networkNum >>> 24) & 0xFF,
145
+ (networkNum >>> 16) & 0xFF,
146
+ (networkNum >>> 8) & 0xFF,
147
+ networkNum & 0xFF
148
+ ];
149
+ const broadcastOctets = [
150
+ (broadcastNum >>> 24) & 0xFF,
151
+ (broadcastNum >>> 16) & 0xFF,
152
+ (broadcastNum >>> 8) & 0xFF,
153
+ broadcastNum & 0xFF
154
+ ];
155
+ const totalAddresses = Math.pow(2, 32 - prefix);
156
+ const usableAddresses = Math.max(0, totalAddresses - 2);
157
+ // Determine network class
158
+ let networkClass = 'Unknown';
159
+ const firstOctet = networkOctets[0];
160
+ if (firstOctet >= 1 && firstOctet <= 126)
161
+ networkClass = 'A';
162
+ else if (firstOctet >= 128 && firstOctet <= 191)
163
+ networkClass = 'B';
164
+ else if (firstOctet >= 192 && firstOctet <= 223)
165
+ networkClass = 'C';
166
+ else if (firstOctet >= 224 && firstOctet <= 239)
167
+ networkClass = 'D (Multicast)';
168
+ else if (firstOctet >= 240 && firstOctet <= 255)
169
+ networkClass = 'E (Reserved)';
170
+ // Check if private
171
+ const isPrivate = (firstOctet === 10) ||
172
+ (firstOctet === 172 && networkOctets[1] >= 16 && networkOctets[1] <= 31) ||
173
+ (firstOctet === 192 && networkOctets[1] === 168);
174
+ return {
175
+ content: [
176
+ {
177
+ type: "text",
178
+ text: `Enhanced IPv4 Subnet Calculation:
179
+
180
+ Input CIDR: ${cidr}
181
+
182
+ Network Information:
183
+ Network Address: ${networkOctets.join('.')}
184
+ Broadcast Address: ${broadcastOctets.join('.')}
185
+ Subnet Mask: ${maskOctets.join('.')}
186
+ Wildcard Mask: ${wildcardOctets.join('.')}
187
+
188
+ Address Range:
189
+ First Host: ${networkOctets[0]}.${networkOctets[1]}.${networkOctets[2]}.${networkOctets[3] + 1}
190
+ Last Host: ${broadcastOctets[0]}.${broadcastOctets[1]}.${broadcastOctets[2]}.${broadcastOctets[3] - 1}
191
+
192
+ Capacity:
193
+ Total Addresses: ${totalAddresses.toLocaleString()}
194
+ Usable Host Addresses: ${usableAddresses.toLocaleString()}
195
+
196
+ Network Properties:
197
+ Class: ${networkClass}
198
+ Type: ${isPrivate ? 'Private' : 'Public'}
199
+ CIDR Prefix: /${prefix}`,
200
+ },
201
+ ],
202
+ };
203
+ }
204
+ catch (error) {
205
+ return {
206
+ content: [
207
+ {
208
+ type: "text",
209
+ text: `Error calculating IPv4 subnet: ${error instanceof Error ? error.message : 'Unknown error'}`,
210
+ },
211
+ ],
212
+ };
213
+ }
214
+ });
215
+ // IPv6 ULA generator
216
+ server.tool("ipv6-ula-generator", "Generate IPv6 Unique Local Address (ULA) prefix", {
217
+ globalId: z.string().optional().describe("Global ID (40 bits in hex, auto-generated if not provided)"),
218
+ }, async ({ globalId }) => {
219
+ try {
220
+ // Generate random 40-bit Global ID if not provided
221
+ let gid = globalId;
222
+ if (!gid) {
223
+ const randomBytes = [];
224
+ for (let i = 0; i < 5; i++) {
225
+ randomBytes.push(Math.floor(Math.random() * 256).toString(16).padStart(2, '0'));
226
+ }
227
+ gid = randomBytes.join('');
228
+ }
229
+ // Validate Global ID
230
+ if (!/^[0-9a-fA-F]{10}$/.test(gid)) {
231
+ throw new Error("Global ID must be exactly 10 hexadecimal characters (40 bits)");
232
+ }
233
+ // Format the ULA prefix
234
+ const prefix = `fd${gid.substring(0, 2)}:${gid.substring(2, 6)}:${gid.substring(6, 10)}`;
235
+ const fullPrefix = `${prefix}::/48`;
236
+ // Generate some example subnets
237
+ const subnets = [];
238
+ for (let i = 0; i < 5; i++) {
239
+ const subnetId = Math.floor(Math.random() * 65536).toString(16).padStart(4, '0');
240
+ subnets.push(`${prefix}:${subnetId}::/64`);
241
+ }
242
+ return {
243
+ content: [
244
+ {
245
+ type: "text",
246
+ text: `IPv6 ULA (Unique Local Address) Generated:
247
+
248
+ ULA Prefix: ${fullPrefix}
249
+ Global ID: ${gid}
250
+
251
+ Example Subnets:
252
+ ${subnets.map((subnet, i) => `${i + 1}. ${subnet}`).join('\n')}
253
+
254
+ Properties:
255
+ - Scope: Local (not routed on the internet)
256
+ - Prefix: fd00::/8 (ULA)
257
+ - Global ID: ${gid} (40 bits)
258
+ - Subnet ID: 16 bits available
259
+ - Interface ID: 64 bits available
260
+
261
+ Note: ULAs are designed for local communications within a site.
262
+ They are not expected to be routable on the global Internet.`,
263
+ },
264
+ ],
265
+ };
266
+ }
267
+ catch (error) {
268
+ return {
269
+ content: [
270
+ {
271
+ type: "text",
272
+ text: `Error generating IPv6 ULA: ${error instanceof Error ? error.message : 'Unknown error'}`,
273
+ },
274
+ ],
275
+ };
276
+ }
277
+ });
278
+ // URL parser
279
+ server.tool("url-parse", "Parse URL into components", {
280
+ url: z.string().describe("URL to parse"),
281
+ }, async ({ url }) => {
282
+ try {
283
+ const urlObj = new URL(url);
284
+ // Parse query parameters
285
+ const params = {};
286
+ urlObj.searchParams.forEach((value, key) => {
287
+ params[key] = value;
288
+ });
289
+ return {
290
+ content: [
291
+ {
292
+ type: "text",
293
+ text: `URL Components:
294
+
295
+ Original URL: ${url}
296
+
297
+ Protocol: ${urlObj.protocol}
298
+ Host: ${urlObj.host}
299
+ Hostname: ${urlObj.hostname}
300
+ Port: ${urlObj.port || 'default'}
301
+ Pathname: ${urlObj.pathname}
302
+ Search: ${urlObj.search}
303
+ Hash: ${urlObj.hash}
304
+ Origin: ${urlObj.origin}
305
+
306
+ Query Parameters:
307
+ ${Object.keys(params).length > 0
308
+ ? Object.entries(params).map(([key, value]) => ` ${key}: ${value}`).join('\n')
309
+ : ' (none)'}
310
+
311
+ Path Segments:
312
+ ${urlObj.pathname.split('/').filter(segment => segment).map((segment, i) => ` ${i + 1}. ${segment}`).join('\n') || ' (none)'}`,
313
+ },
314
+ ],
315
+ };
316
+ }
317
+ catch (error) {
318
+ return {
319
+ content: [
320
+ {
321
+ type: "text",
322
+ text: `Error parsing URL: ${error instanceof Error ? error.message : 'Invalid URL format'}`,
323
+ },
324
+ ],
325
+ };
326
+ }
327
+ });
328
+ // Random port generator
329
+ server.tool("random-port", "Generate random port numbers", {
330
+ count: z.number().describe("Number of ports to generate").optional(),
331
+ min: z.number().describe("Minimum port number").optional(),
332
+ max: z.number().describe("Maximum port number").optional(),
333
+ exclude: z.array(z.number()).optional().describe("Ports to exclude"),
334
+ }, async ({ count = 1, min = 1024, max = 65535, exclude = [] }) => {
335
+ try {
336
+ const ports = [];
337
+ const excludeSet = new Set(exclude);
338
+ // Well-known ports to avoid by default
339
+ const wellKnownPorts = [22, 23, 25, 53, 80, 110, 143, 443, 993, 995];
340
+ wellKnownPorts.forEach(port => excludeSet.add(port));
341
+ for (let i = 0; i < count; i++) {
342
+ let port;
343
+ let attempts = 0;
344
+ do {
345
+ port = Math.floor(Math.random() * (max - min + 1)) + min;
346
+ attempts++;
347
+ if (attempts > 1000) {
348
+ throw new Error("Could not generate unique port after 1000 attempts");
349
+ }
350
+ } while (excludeSet.has(port) || ports.includes(port));
351
+ ports.push(port);
352
+ }
353
+ return {
354
+ content: [
355
+ {
356
+ type: "text",
357
+ text: `Random Ports Generated:
358
+
359
+ ${ports.map((port, i) => `${i + 1}. ${port}`).join('\n')}
360
+
361
+ Range: ${min} - ${max}
362
+ Excluded well-known ports: ${wellKnownPorts.join(', ')}
363
+ ${exclude.length > 0 ? `Custom excluded: ${exclude.join(', ')}` : ''}`,
364
+ },
365
+ ],
366
+ };
367
+ }
368
+ catch (error) {
369
+ return {
370
+ content: [
371
+ {
372
+ type: "text",
373
+ text: `Error generating random ports: ${error instanceof Error ? error.message : 'Unknown error'}`,
374
+ },
375
+ ],
376
+ };
377
+ }
378
+ });
379
+ // MAC address generator
380
+ server.tool("mac-address-generate", "Generate random MAC address", {
381
+ prefix: z.string().optional().describe("MAC address prefix (e.g., '00:1B:44')"),
382
+ separator: z.enum([":", "-"]).describe("Separator character").optional(),
383
+ }, async ({ prefix, separator = ":" }) => {
384
+ try {
385
+ let macParts = [];
386
+ if (prefix) {
387
+ // Validate and use provided prefix
388
+ const prefixParts = prefix.split(/[:-]/);
389
+ if (prefixParts.length > 6) {
390
+ throw new Error("Prefix cannot have more than 6 parts");
391
+ }
392
+ for (const part of prefixParts) {
393
+ if (!/^[0-9A-Fa-f]{2}$/.test(part)) {
394
+ throw new Error(`Invalid MAC address part: ${part}`);
395
+ }
396
+ macParts.push(part.toUpperCase());
397
+ }
398
+ }
399
+ // Generate remaining parts
400
+ while (macParts.length < 6) {
401
+ const randomByte = Math.floor(Math.random() * 256).toString(16).padStart(2, '0').toUpperCase();
402
+ macParts.push(randomByte);
403
+ }
404
+ // Ensure first octet indicates locally administered unicast
405
+ if (!prefix) {
406
+ const firstOctet = parseInt(macParts[0], 16);
407
+ // Set locally administered bit (bit 1) and clear multicast bit (bit 0)
408
+ macParts[0] = ((firstOctet | 0x02) & 0xFE).toString(16).padStart(2, '0').toUpperCase();
409
+ }
410
+ const macAddress = macParts.join(separator);
411
+ // Analyze the MAC address
412
+ const firstOctet = parseInt(macParts[0], 16);
413
+ const isMulticast = (firstOctet & 0x01) !== 0;
414
+ const isLocallyAdministered = (firstOctet & 0x02) !== 0;
415
+ return {
416
+ content: [
417
+ {
418
+ type: "text",
419
+ text: `Generated MAC Address: ${macAddress}
420
+
421
+ Properties:
422
+ Type: ${isMulticast ? 'Multicast' : 'Unicast'}
423
+ Administration: ${isLocallyAdministered ? 'Locally Administered' : 'Universally Administered'}
424
+ Format: ${separator === ':' ? 'Colon notation' : 'Hyphen notation'}
425
+
426
+ Binary representation:
427
+ ${macParts.map(part => parseInt(part, 16).toString(2).padStart(8, '0')).join(' ')}
428
+
429
+ ${prefix ? `Used prefix: ${prefix}` : 'Randomly generated'}`,
430
+ },
431
+ ],
432
+ };
433
+ }
434
+ catch (error) {
435
+ return {
436
+ content: [
437
+ {
438
+ type: "text",
439
+ text: `Error generating MAC address: ${error instanceof Error ? error.message : 'Unknown error'}`,
440
+ },
441
+ ],
442
+ };
443
+ }
444
+ });
445
+ // Phone number formatter
446
+ server.tool("phone-format", "Parse and format phone numbers", {
447
+ phoneNumber: z.string().describe("Phone number to parse and format"),
448
+ countryCode: z.string().optional().describe("Country code (e.g., 'US', 'GB', 'FR')"),
449
+ }, async ({ phoneNumber, countryCode }) => {
450
+ try {
451
+ // First check if it's a valid phone number
452
+ if (!isValidPhoneNumber(phoneNumber, countryCode)) {
453
+ throw new Error("Invalid phone number format");
454
+ }
455
+ // Parse the phone number
456
+ const parsedNumber = parsePhoneNumber(phoneNumber, countryCode);
457
+ return {
458
+ content: [
459
+ {
460
+ type: "text",
461
+ text: `Phone Number Formatting:
462
+
463
+ Original: ${phoneNumber}
464
+ Country: ${parsedNumber.country || 'Unknown'}
465
+ National: ${parsedNumber.formatNational()}
466
+ International: ${parsedNumber.formatInternational()}
467
+ E.164: ${parsedNumber.format('E.164')}
468
+ URI: ${parsedNumber.getURI()}
469
+
470
+ Details:
471
+ Type: ${parsedNumber.getType() || 'Unknown'}
472
+ Country Code: +${parsedNumber.countryCallingCode}
473
+ National Number: ${parsedNumber.nationalNumber}
474
+ Valid: ${parsedNumber.isValid()}
475
+
476
+ ✅ Formatted using libphonenumber-js library for accuracy.`,
477
+ },
478
+ ],
479
+ };
480
+ }
481
+ catch (error) {
482
+ return {
483
+ content: [
484
+ {
485
+ type: "text",
486
+ text: `Error formatting phone number: ${error instanceof Error ? error.message : 'Unknown error'}
487
+
488
+ 💡 Tips:
489
+ • Include country code (e.g., +1 555-123-4567)
490
+ • Use standard formats (e.g., (555) 123-4567)
491
+ • Specify country code parameter if needed
492
+ • Examples: "+1-555-123-4567", "555-123-4567" with countryCode="US"`,
493
+ },
494
+ ],
495
+ };
496
+ }
497
+ });
498
+ // IBAN validator (using iban library)
499
+ server.tool("iban-validate", "Validate and parse IBAN (International Bank Account Number)", {
500
+ iban: z.string().describe("IBAN to validate and parse"),
501
+ }, async ({ iban }) => {
502
+ try {
503
+ // Clean the input
504
+ const cleanIban = iban.replace(/\s/g, '').toUpperCase();
505
+ // Validate using the IBAN library
506
+ const isValid = IBAN.isValid(cleanIban);
507
+ if (isValid) {
508
+ // Extract components
509
+ const countryCode = cleanIban.slice(0, 2);
510
+ const checkDigits = cleanIban.slice(2, 4);
511
+ const bban = cleanIban.slice(4); // Basic Bank Account Number
512
+ // Get country information if available (simplified approach)
513
+ const countryInfo = IBAN.countries[countryCode];
514
+ const countryName = countryInfo ? 'Available' : 'Unknown';
515
+ return {
516
+ content: [
517
+ {
518
+ type: "text",
519
+ text: `IBAN Validation Result: ✅ VALID
520
+
521
+ IBAN: ${iban}
522
+ Formatted: ${IBAN.printFormat(cleanIban)}
523
+
524
+ Components:
525
+ Country Code: ${countryCode} (${countryName})
526
+ Check Digits: ${checkDigits}
527
+ BBAN: ${bban}
528
+ Length: ${cleanIban.length} characters
529
+
530
+ Validation:
531
+ Format: ✅ Valid
532
+ Length: ✅ Correct for ${countryCode}
533
+ Checksum: ✅ Valid (MOD-97)
534
+
535
+ Electronic Format: ${cleanIban}
536
+ Print Format: ${IBAN.printFormat(cleanIban)}`,
537
+ },
538
+ ],
539
+ };
540
+ }
541
+ else {
542
+ return {
543
+ content: [
544
+ {
545
+ type: "text",
546
+ text: `IBAN Validation Result: ❌ INVALID
547
+
548
+ IBAN: ${iban}
549
+ Cleaned: ${cleanIban}
550
+
551
+ The provided IBAN is not valid. Please check:
552
+ - Country code (first 2 characters)
553
+ - Check digits (characters 3-4)
554
+ - Bank account number format
555
+ - Overall length for the country
556
+
557
+ Common issues:
558
+ - Incorrect country code
559
+ - Invalid check digits
560
+ - Wrong length for the country
561
+ - Invalid characters in BBAN`,
562
+ },
563
+ ],
564
+ };
565
+ }
566
+ }
567
+ catch (error) {
568
+ return {
569
+ content: [
570
+ {
571
+ type: "text",
572
+ text: `Error validating IBAN: ${error instanceof Error ? error.message : 'Unknown error'}`,
573
+ },
574
+ ],
575
+ };
576
+ }
577
+ });
578
+ }