it-tools-mcp 3.0.24 → 3.1.1

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 (127) hide show
  1. package/README.dockerhub.md +23 -18
  2. package/README.md +77 -34
  3. package/build/index.js +51 -24
  4. package/build/tools/ansible/ansible-inventory-generator/index.js +212 -0
  5. package/build/tools/ansible/ansible-playbook-validator/index.js +128 -0
  6. package/build/tools/ansible/ansible-reference/index.js +393 -0
  7. package/build/tools/ansible/ansible-vault-decrypt/index.js +137 -0
  8. package/build/tools/ansible/ansible-vault-encrypt/index.js +79 -0
  9. package/build/tools/color/color-hex-to-rgb/index.js +29 -0
  10. package/build/tools/{color.js → color/color-rgb-to-hex/index.js} +1 -27
  11. package/build/tools/crypto/basic-auth-generator/index.js +45 -0
  12. package/build/tools/crypto/bcrypt-hash/index.js +67 -0
  13. package/build/tools/crypto/bip39-generate/index.js +53 -0
  14. package/build/tools/crypto/hash-md5/index.js +19 -0
  15. package/build/tools/crypto/hash-sha1/index.js +19 -0
  16. package/build/tools/crypto/hash-sha256/index.js +19 -0
  17. package/build/tools/crypto/hash-sha512/index.js +19 -0
  18. package/build/tools/crypto/hmac-generator/index.js +37 -0
  19. package/build/tools/crypto/jwt-decode/index.js +41 -0
  20. package/build/tools/crypto/otp-code-generator/index.js +67 -0
  21. package/build/tools/crypto/password-generate/index.js +54 -0
  22. package/build/tools/crypto/token-generator/index.js +75 -0
  23. package/build/tools/dataFormat/html-to-markdown/index.js +34 -0
  24. package/build/tools/dataFormat/json-diff/index.js +94 -0
  25. package/build/tools/dataFormat/json-format/index.js +100 -0
  26. package/build/tools/dataFormat/json-minify/index.js +29 -0
  27. package/build/tools/dataFormat/json-to-csv/index.js +34 -0
  28. package/build/tools/dataFormat/json-to-toml/index.js +30 -0
  29. package/build/tools/dataFormat/markdown-to-html/index.js +32 -0
  30. package/build/tools/dataFormat/phone-format/index.js +35 -0
  31. package/build/tools/dataFormat/sql-format/index.js +37 -0
  32. package/build/tools/dataFormat/toml-to-json/index.js +29 -0
  33. package/build/tools/dataFormat/xml-format/index.js +44 -0
  34. package/build/tools/dataFormat/yaml-format/index.js +58 -0
  35. package/build/tools/{development.js → development/crontab-generate/index.js} +1 -129
  36. package/build/tools/development/html-prettifier/index.js +47 -0
  37. package/build/tools/development/javascript-prettifier/index.js +74 -0
  38. package/build/tools/development/list-converter/index.js +62 -0
  39. package/build/tools/development/markdown-toc-generator/index.js +53 -0
  40. package/build/tools/development/regex-tester/index.js +69 -0
  41. package/build/tools/docker/docker-compose-to-docker-run/index.js +138 -0
  42. package/build/tools/docker/docker-compose-validator/index.js +125 -0
  43. package/build/tools/docker/docker-reference/index.js +188 -0
  44. package/build/tools/docker/docker-run-to-docker-compose/index.js +117 -0
  45. package/build/tools/docker/traefik-compose-generator/index.js +98 -0
  46. package/build/tools/encoding/base64-decode/index.js +28 -0
  47. package/build/tools/encoding/base64-encode/index.js +16 -0
  48. package/build/tools/encoding/html-decode/index.js +21 -0
  49. package/build/tools/encoding/html-encode/index.js +21 -0
  50. package/build/tools/encoding/html-entities-extended/index.js +72 -0
  51. package/build/tools/encoding/text-to-binary/index.js +51 -0
  52. package/build/tools/encoding/url-decode/index.js +28 -0
  53. package/build/tools/encoding/url-encode/index.js +16 -0
  54. package/build/tools/forensic/file-type-identifier/index.js +90 -0
  55. package/build/tools/forensic/safelink-decoder/index.js +54 -0
  56. package/build/tools/forensic/url-fanger/index.js +52 -0
  57. package/build/tools/idGenerators/qr-generate/index.js +76 -0
  58. package/build/tools/idGenerators/svg-placeholder-generator/index.js +59 -0
  59. package/build/tools/idGenerators/ulid-generate/index.js +34 -0
  60. package/build/tools/idGenerators/uuid-generate/index.js +14 -0
  61. package/build/tools/math/math-evaluate/index.js +33 -0
  62. package/build/tools/math/number-base-converter/index.js +46 -0
  63. package/build/tools/math/percentage-calculator/index.js +50 -0
  64. package/build/tools/math/roman-numeral-converter/index.js +76 -0
  65. package/build/tools/math/temperature-converter/index.js +59 -0
  66. package/build/tools/math/unix-timestamp-converter/index.js +55 -0
  67. package/build/tools/network/cat/index.js +15 -0
  68. package/build/tools/network/cidr-to-ip-range/index.js +108 -0
  69. package/build/tools/network/curl/index.js +35 -0
  70. package/build/tools/network/dig/index.js +19 -0
  71. package/build/tools/network/grep/index.js +18 -0
  72. package/build/tools/network/head/index.js +17 -0
  73. package/build/tools/network/iban-validate/index.js +83 -0
  74. package/build/tools/network/ip-range-to-cidr/index.js +88 -0
  75. package/build/tools/network/ip-subnet-calculator/index.js +102 -0
  76. package/build/tools/network/ipv4-subnet-calc/index.js +112 -0
  77. package/build/tools/network/ipv6-subnet-calculator/index.js +104 -0
  78. package/build/tools/network/ipv6-ula-generator/index.js +65 -0
  79. package/build/tools/network/mac-address-generate/index.js +68 -0
  80. package/build/tools/network/nslookup/index.js +18 -0
  81. package/build/tools/network/ping/index.js +20 -0
  82. package/build/tools/network/ps/index.js +22 -0
  83. package/build/tools/network/random-port/index.js +53 -0
  84. package/build/tools/network/scp/index.js +134 -0
  85. package/build/tools/network/ssh/index.js +83 -0
  86. package/build/tools/network/tail/index.js +16 -0
  87. package/build/tools/network/telnet/index.js +45 -0
  88. package/build/tools/network/top/index.js +14 -0
  89. package/build/tools/network/url-parse/index.js +52 -0
  90. package/build/tools/physics/angle-converter/index.js +73 -0
  91. package/build/tools/physics/energy-converter/index.js +72 -0
  92. package/build/tools/physics/power-converter/index.js +71 -0
  93. package/build/tools/text/ascii-art-text/index.js +112 -0
  94. package/build/tools/text/distinct-words/index.js +30 -0
  95. package/build/tools/text/emoji-search/index.js +76 -0
  96. package/build/tools/text/lorem-ipsum-generator/index.js +87 -0
  97. package/build/tools/text/numeronym-generator/index.js +37 -0
  98. package/build/tools/text/slugify-string/index.js +44 -0
  99. package/build/tools/text/string-obfuscator/index.js +49 -0
  100. package/build/tools/text/text-camelcase/index.js +20 -0
  101. package/build/tools/text/text-capitalize/index.js +16 -0
  102. package/build/tools/text/text-diff/index.js +72 -0
  103. package/build/tools/text/text-kebabcase/index.js +20 -0
  104. package/build/tools/text/text-lowercase/index.js +15 -0
  105. package/build/tools/text/text-pascalcase/index.js +18 -0
  106. package/build/tools/text/text-snakecase/index.js +20 -0
  107. package/build/tools/text/text-stats/index.js +29 -0
  108. package/build/tools/text/text-to-nato-alphabet/index.js +57 -0
  109. package/build/tools/text/text-to-unicode/index.js +50 -0
  110. package/build/tools/text/text-to-unicode-names/index.js +34 -0
  111. package/build/tools/text/text-uppercase/index.js +15 -0
  112. package/build/tools/utility/css-prettifier/index.js +70 -0
  113. package/build/tools/utility/device-info/index.js +44 -0
  114. package/build/tools/utility/email-normalizer/index.js +73 -0
  115. package/build/tools/utility/http-status-codes/index.js +173 -0
  116. package/build/tools/utility/mime-types/index.js +121 -0
  117. package/build/tools/utility/port-numbers/index.js +106 -0
  118. package/build/tools/utility/rem-px-converter/index.js +63 -0
  119. package/package.json +3 -3
  120. package/build/tools/crypto.js +0 -445
  121. package/build/tools/dataFormat.js +0 -535
  122. package/build/tools/encoding.js +0 -240
  123. package/build/tools/idGenerators.js +0 -180
  124. package/build/tools/math.js +0 -310
  125. package/build/tools/network.js +0 -939
  126. package/build/tools/text.js +0 -678
  127. package/build/tools/utility.js +0 -407
@@ -0,0 +1,104 @@
1
+ import { z } from "zod";
2
+ export function registerIpv6SubnetCalculator(server) {
3
+ server.tool("ipv6-subnet-calculator", "Calculate IPv6 subnet information", {
4
+ ipv6: z.string().describe("IPv6 address and prefix (e.g., 2001:db8::/32)"),
5
+ newPrefix: z.number().optional().describe("New prefix length for subnetting")
6
+ }, async ({ ipv6, newPrefix }) => {
7
+ try {
8
+ const [address, prefixStr] = ipv6.split('/');
9
+ if (!address || !prefixStr) {
10
+ throw new Error("Invalid IPv6 format. Use format: address/prefix");
11
+ }
12
+ const prefix = parseInt(prefixStr);
13
+ if (prefix < 0 || prefix > 128) {
14
+ throw new Error("Prefix length must be between 0 and 128");
15
+ }
16
+ // Expand IPv6 address to full form
17
+ const expandIPv6 = (addr) => {
18
+ // Handle :: notation
19
+ const parts = addr.split('::');
20
+ if (parts.length === 2) {
21
+ const left = parts[0] ? parts[0].split(':') : [];
22
+ const right = parts[1] ? parts[1].split(':') : [];
23
+ const missing = 8 - left.length - right.length;
24
+ const middle = Array(missing).fill('0000');
25
+ return [...left, ...middle, ...right].map(part => part.padStart(4, '0')).join(':');
26
+ }
27
+ else {
28
+ return addr.split(':').map(part => part.padStart(4, '0')).join(':');
29
+ }
30
+ };
31
+ const fullAddress = expandIPv6(address);
32
+ const parts = fullAddress.split(':');
33
+ if (parts.length !== 8) {
34
+ throw new Error("Invalid IPv6 address format");
35
+ }
36
+ // Calculate network portion
37
+ const networkBits = prefix;
38
+ const hostBits = 128 - prefix;
39
+ // Create network address
40
+ const networkParts = [...parts];
41
+ const bytesToClear = Math.floor((128 - prefix) / 16);
42
+ const partialBits = (128 - prefix) % 16;
43
+ // Clear host portion
44
+ for (let i = 8 - bytesToClear; i < 8; i++) {
45
+ networkParts[i] = '0000';
46
+ }
47
+ if (partialBits > 0) {
48
+ const partIndex = 8 - bytesToClear - 1;
49
+ const mask = (0xFFFF << partialBits) & 0xFFFF;
50
+ const value = parseInt(networkParts[partIndex], 16) & mask;
51
+ networkParts[partIndex] = value.toString(16).padStart(4, '0');
52
+ }
53
+ const networkAddr = networkParts.join(':');
54
+ // Calculate some common subnet sizes if newPrefix is provided
55
+ let subnetInfo = '';
56
+ if (newPrefix && newPrefix > prefix) {
57
+ const subnetBits = newPrefix - prefix;
58
+ const numSubnets = Math.pow(2, subnetBits);
59
+ const hostsPerSubnet = Math.pow(2, 128 - newPrefix);
60
+ subnetInfo = `\nSubnetting to /${newPrefix}:
61
+ • Subnet bits borrowed: ${subnetBits}
62
+ • Number of subnets: ${numSubnets.toLocaleString()}
63
+ • Hosts per subnet: ${hostsPerSubnet.toExponential(2)}`;
64
+ }
65
+ const totalHosts = Math.pow(2, hostBits);
66
+ return {
67
+ content: [{
68
+ type: "text",
69
+ text: `IPv6 Subnet Calculator Results:
70
+
71
+ Input: ${ipv6}
72
+ Full Address: ${fullAddress}
73
+
74
+ Network Information:
75
+ • Network Address: ${networkAddr}/${prefix}
76
+ • Prefix Length: /${prefix}
77
+ • Network Bits: ${networkBits}
78
+ • Host Bits: ${hostBits}
79
+ • Total Addresses: 2^${hostBits} (${totalHosts.toExponential(2)})
80
+
81
+ Address Format:
82
+ • Compressed: ${networkAddr.replace(/(:0000)+/g, '::').replace(/^0+|:0+/g, ':').replace(/^:/, '').replace(/:$/, '') || '::'}/${prefix}
83
+ • Full: ${networkAddr}/${prefix}
84
+
85
+ ${subnetInfo}
86
+
87
+ IPv6 Address Types:
88
+ • Global Unicast: 2000::/3
89
+ • Link-Local: fe80::/10
90
+ • Unique Local: fc00::/7
91
+ • Multicast: ff00::/8`
92
+ }]
93
+ };
94
+ }
95
+ catch (error) {
96
+ return {
97
+ content: [{
98
+ type: "text",
99
+ text: `Error calculating IPv6 subnet: ${error instanceof Error ? error.message : 'Unknown error'}`
100
+ }]
101
+ };
102
+ }
103
+ });
104
+ }
@@ -0,0 +1,65 @@
1
+ import { z } from "zod";
2
+ export function registerIpv6UlaGenerator(server) {
3
+ server.tool("ipv6-ula-generator", "Generate IPv6 Unique Local Address (ULA) prefix", {
4
+ globalId: z.string().optional().describe("Global ID (40 bits in hex, auto-generated if not provided)"),
5
+ }, async ({ globalId }) => {
6
+ try {
7
+ // Generate random 40-bit Global ID if not provided
8
+ let gid = globalId;
9
+ if (!gid) {
10
+ const randomBytes = [];
11
+ for (let i = 0; i < 5; i++) {
12
+ randomBytes.push(Math.floor(Math.random() * 256).toString(16).padStart(2, '0'));
13
+ }
14
+ gid = randomBytes.join('');
15
+ }
16
+ // Validate Global ID
17
+ if (!/^[0-9a-fA-F]{10}$/.test(gid)) {
18
+ throw new Error("Global ID must be exactly 10 hexadecimal characters (40 bits)");
19
+ }
20
+ // Format the ULA prefix
21
+ const prefix = `fd${gid.substring(0, 2)}:${gid.substring(2, 6)}:${gid.substring(6, 10)}`;
22
+ const fullPrefix = `${prefix}::/48`;
23
+ // Generate some example subnets
24
+ const subnets = [];
25
+ for (let i = 0; i < 5; i++) {
26
+ const subnetId = Math.floor(Math.random() * 65536).toString(16).padStart(4, '0');
27
+ subnets.push(`${prefix}:${subnetId}::/64`);
28
+ }
29
+ return {
30
+ content: [
31
+ {
32
+ type: "text",
33
+ text: `IPv6 ULA (Unique Local Address) Generated:
34
+
35
+ ULA Prefix: ${fullPrefix}
36
+ Global ID: ${gid}
37
+
38
+ Example Subnets:
39
+ ${subnets.map((subnet, i) => `${i + 1}. ${subnet}`).join('\n')}
40
+
41
+ Properties:
42
+ - Scope: Local (not routed on the internet)
43
+ - Prefix: fd00::/8 (ULA)
44
+ - Global ID: ${gid} (40 bits)
45
+ - Subnet ID: 16 bits available
46
+ - Interface ID: 64 bits available
47
+
48
+ Note: ULAs are designed for local communications within a site.
49
+ They are not expected to be routable on the global Internet.`,
50
+ },
51
+ ],
52
+ };
53
+ }
54
+ catch (error) {
55
+ return {
56
+ content: [
57
+ {
58
+ type: "text",
59
+ text: `Error generating IPv6 ULA: ${error instanceof Error ? error.message : 'Unknown error'}`,
60
+ },
61
+ ],
62
+ };
63
+ }
64
+ });
65
+ }
@@ -0,0 +1,68 @@
1
+ import { z } from "zod";
2
+ export function registerMacAddressGenerate(server) {
3
+ server.tool("mac-address-generate", "Generate random MAC address", {
4
+ prefix: z.string().optional().describe("MAC address prefix (e.g., '00:1B:44')"),
5
+ separator: z.enum([":", "-"]).describe("Separator character").optional(),
6
+ }, async ({ prefix, separator = ":" }) => {
7
+ try {
8
+ let macParts = [];
9
+ if (prefix) {
10
+ // Validate and use provided prefix
11
+ const prefixParts = prefix.split(/[:-]/);
12
+ if (prefixParts.length > 6) {
13
+ throw new Error("Prefix cannot have more than 6 parts");
14
+ }
15
+ for (const part of prefixParts) {
16
+ if (!/^[0-9-A-Fa-f]{2}$/.test(part)) {
17
+ throw new Error(`Invalid MAC address part: ${part}`);
18
+ }
19
+ macParts.push(part.toUpperCase());
20
+ }
21
+ }
22
+ // Generate remaining parts
23
+ while (macParts.length < 6) {
24
+ const randomByte = Math.floor(Math.random() * 256).toString(16).padStart(2, '0').toUpperCase();
25
+ macParts.push(randomByte);
26
+ }
27
+ // Ensure first octet indicates locally administered unicast
28
+ if (!prefix) {
29
+ const firstOctet = parseInt(macParts[0], 16);
30
+ // Set locally administered bit (bit 1) and clear multicast bit (bit 0)
31
+ macParts[0] = ((firstOctet | 0x02) & 0xFE).toString(16).padStart(2, '0').toUpperCase();
32
+ }
33
+ const macAddress = macParts.join(separator);
34
+ // Analyze the MAC address
35
+ const firstOctet = parseInt(macParts[0], 16);
36
+ const isMulticast = (firstOctet & 0x01) !== 0;
37
+ const isLocallyAdministered = (firstOctet & 0x02) !== 0;
38
+ return {
39
+ content: [
40
+ {
41
+ type: "text",
42
+ text: `Generated MAC Address: ${macAddress}
43
+
44
+ Properties:
45
+ Type: ${isMulticast ? 'Multicast' : 'Unicast'}
46
+ Administration: ${isLocallyAdministered ? 'Locally Administered' : 'Universally Administered'}
47
+ Format: ${separator === ':' ? 'Colon notation' : 'Hyphen notation'}
48
+
49
+ Binary representation:
50
+ ${macParts.map(part => parseInt(part, 16).toString(2).padStart(8, '0')).join(' ')}
51
+
52
+ ${prefix ? `Used prefix: ${prefix}` : 'Randomly generated'}`,
53
+ },
54
+ ],
55
+ };
56
+ }
57
+ catch (error) {
58
+ return {
59
+ content: [
60
+ {
61
+ type: "text",
62
+ text: `Error generating MAC address: ${error instanceof Error ? error.message : 'Unknown error'}`,
63
+ },
64
+ ],
65
+ };
66
+ }
67
+ });
68
+ }
@@ -0,0 +1,18 @@
1
+ import { z } from "zod";
2
+ import dns from "dns";
3
+ export function registerNslookup(server) {
4
+ server.tool("nslookup", "Perform DNS lookup on a hostname or IP address", {
5
+ target: z.string().describe("Hostname or IP address")
6
+ }, async ({ target }) => {
7
+ return new Promise((resolve) => {
8
+ dns.lookup(target, (err, address, family) => {
9
+ if (err) {
10
+ resolve({ content: [{ type: "text", text: `nslookup failed: ${err.message}` }] });
11
+ }
12
+ else {
13
+ resolve({ content: [{ type: "text", text: `Address: ${address}\nFamily: IPv${family}` }] });
14
+ }
15
+ });
16
+ });
17
+ });
18
+ }
@@ -0,0 +1,20 @@
1
+ import { z } from "zod";
2
+ import ping from "ping";
3
+ export function registerPing(server) {
4
+ server.tool("ping", "Ping a host to check connectivity", {
5
+ target: z.string().describe("Host to ping"),
6
+ count: z.number().default(4).describe("Number of ping attempts")
7
+ }, async ({ target, count }) => {
8
+ try {
9
+ const res = await ping.promise.probe(target, { min_reply: count });
10
+ return {
11
+ content: [
12
+ { type: "text", text: `Ping to ${target}:\nAlive: ${res.alive}\nTime: ${res.time} ms\nOutput: ${res.output}` }
13
+ ]
14
+ };
15
+ }
16
+ catch (error) {
17
+ return { content: [{ type: "text", text: `Ping failed: ${error instanceof Error ? error.message : error}` }] };
18
+ }
19
+ });
20
+ }
@@ -0,0 +1,22 @@
1
+ import psList from "ps-list";
2
+ export function registerPs(server) {
3
+ server.tool("ps", "List running processes", {}, async () => {
4
+ try {
5
+ const processes = await psList();
6
+ // Defensive: handle missing properties and filter out bad entries
7
+ const output = processes
8
+ .map(p => {
9
+ const pid = p.pid ?? 'N/A';
10
+ const name = p.name ?? 'N/A';
11
+ return `${pid}\t${name}`;
12
+ })
13
+ .join("\n");
14
+ return { content: [{ type: "text", text: output || 'No processes found.' }] };
15
+ }
16
+ catch (error) {
17
+ // Log error for debugging
18
+ console.error('ps error:', error);
19
+ return { content: [{ type: "text", text: `ps failed: ${error instanceof Error ? error.message : error}` }] };
20
+ }
21
+ });
22
+ }
@@ -0,0 +1,53 @@
1
+ import { z } from "zod";
2
+ export function registerRandomPort(server) {
3
+ server.tool("random-port", "Generate random port numbers", {
4
+ count: z.number().describe("Number of ports to generate").optional(),
5
+ min: z.number().describe("Minimum port number").optional(),
6
+ max: z.number().describe("Maximum port number").optional(),
7
+ exclude: z.array(z.number()).optional().describe("Ports to exclude"),
8
+ }, async ({ count = 1, min = 1024, max = 65535, exclude = [] }) => {
9
+ try {
10
+ const ports = [];
11
+ const excludeSet = new Set(exclude);
12
+ // Well-known ports to avoid by default
13
+ const wellKnownPorts = [22, 23, 25, 53, 80, 110, 143, 443, 993, 995];
14
+ wellKnownPorts.forEach(port => excludeSet.add(port));
15
+ for (let i = 0; i < count; i++) {
16
+ let port;
17
+ let attempts = 0;
18
+ do {
19
+ port = Math.floor(Math.random() * (max - min + 1)) + min;
20
+ attempts++;
21
+ if (attempts > 1000) {
22
+ throw new Error("Could not generate unique port after 1000 attempts");
23
+ }
24
+ } while (excludeSet.has(port) || ports.includes(port));
25
+ ports.push(port);
26
+ }
27
+ return {
28
+ content: [
29
+ {
30
+ type: "text",
31
+ text: `Random Ports Generated:
32
+
33
+ ${ports.map((port, i) => `${i + 1}. ${port}`).join('\n')}
34
+
35
+ Range: ${min} - ${max}
36
+ Excluded well-known ports: ${wellKnownPorts.join(', ')}
37
+ ${exclude.length > 0 ? `Custom excluded: ${exclude.join(', ')}` : ''}`,
38
+ },
39
+ ],
40
+ };
41
+ }
42
+ catch (error) {
43
+ return {
44
+ content: [
45
+ {
46
+ type: "text",
47
+ text: `Error generating random ports: ${error instanceof Error ? error.message : 'Unknown error'}`,
48
+ },
49
+ ],
50
+ };
51
+ }
52
+ });
53
+ }
@@ -0,0 +1,134 @@
1
+ import { z } from "zod";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ import os from "os";
5
+ function resolvePrivateKey(privateKeyArg) {
6
+ // If not provided, try default keys
7
+ if (!privateKeyArg) {
8
+ const home = os.homedir();
9
+ const defaultKeys = [
10
+ path.join(home, '.ssh', 'id_rsa'),
11
+ path.join(home, '.ssh', 'id_ed25519'),
12
+ ];
13
+ for (const keyPath of defaultKeys) {
14
+ if (fs.existsSync(keyPath)) {
15
+ return fs.readFileSync(keyPath, 'utf8');
16
+ }
17
+ }
18
+ return undefined;
19
+ }
20
+ // If it looks like a path, try to read it
21
+ if (privateKeyArg.startsWith('/') ||
22
+ privateKeyArg.startsWith('~') ||
23
+ privateKeyArg.endsWith('.pem') ||
24
+ privateKeyArg.endsWith('.key')) {
25
+ let keyPath = privateKeyArg;
26
+ if (keyPath.startsWith('~')) {
27
+ keyPath = path.join(os.homedir(), keyPath.slice(1));
28
+ }
29
+ if (fs.existsSync(keyPath)) {
30
+ return fs.readFileSync(keyPath, 'utf8');
31
+ }
32
+ else {
33
+ throw new Error('Private key file not found: ' + keyPath);
34
+ }
35
+ }
36
+ // Otherwise, assume it's the key content
37
+ return privateKeyArg;
38
+ }
39
+ export function registerScp(server) {
40
+ server.tool("scp", "Copy files to or from a remote host using SFTP (SCP-like)", {
41
+ target: z.string().describe("Target host"),
42
+ user: z.string().describe("Username"),
43
+ direction: z.enum(["upload", "download"]).describe("Direction: upload (local to remote) or download (remote to local)"),
44
+ localPath: z.string().describe("Local file path (source for upload, destination for download)"),
45
+ remotePath: z.string().describe("Remote file path (destination for upload, source for download)"),
46
+ privateKey: z.string().optional().describe("Private key for authentication (PEM format, optional, or path to key file)")
47
+ }, async ({ target, user, direction, localPath, remotePath, privateKey }) => {
48
+ try {
49
+ const { Client } = await import("ssh2");
50
+ const fs = await import("fs");
51
+ let resolvedKey;
52
+ try {
53
+ resolvedKey = resolvePrivateKey(privateKey);
54
+ }
55
+ catch (err) {
56
+ return { content: [{ type: "text", text: `SCP key error: ${err.message}` }] };
57
+ }
58
+ return await new Promise((resolve) => {
59
+ const conn = new Client();
60
+ let finished = false;
61
+ const finish = (msg) => {
62
+ if (!finished) {
63
+ finished = true;
64
+ try {
65
+ conn.end();
66
+ }
67
+ catch { }
68
+ resolve({ content: [{ type: "text", text: msg }] });
69
+ }
70
+ };
71
+ // Connection timeout (20s)
72
+ const timeout = setTimeout(() => {
73
+ finish(`SCP connection timed out after 20 seconds`);
74
+ }, 20000);
75
+ conn.on("ready", () => {
76
+ clearTimeout(timeout);
77
+ conn.sftp((err, sftp) => {
78
+ if (err) {
79
+ finish(`SFTP error: ${err.message}`);
80
+ return;
81
+ }
82
+ if (direction === "upload") {
83
+ let readStream, writeStream;
84
+ try {
85
+ readStream = fs.createReadStream(localPath);
86
+ writeStream = sftp.createWriteStream(remotePath);
87
+ }
88
+ catch (streamErr) {
89
+ finish(`Upload failed: ${streamErr.message}`);
90
+ return;
91
+ }
92
+ writeStream.on("close", () => finish(`Upload complete: ${localPath} → ${user}@${target}:${remotePath}`));
93
+ writeStream.on("error", (err) => finish(`Upload failed: ${err.message}`));
94
+ readStream.on("error", (err) => finish(`Upload failed: ${err.message}`));
95
+ readStream.pipe(writeStream);
96
+ }
97
+ else {
98
+ let readStream, writeStream;
99
+ try {
100
+ readStream = sftp.createReadStream(remotePath);
101
+ writeStream = fs.createWriteStream(localPath);
102
+ }
103
+ catch (streamErr) {
104
+ finish(`Download failed: ${streamErr.message}`);
105
+ return;
106
+ }
107
+ writeStream.on("close", () => finish(`Download complete: ${user}@${target}:${remotePath} → ${localPath}`));
108
+ writeStream.on("error", (err) => finish(`Download failed: ${err.message}`));
109
+ readStream.on("error", (err) => finish(`Download failed: ${err.message}`));
110
+ readStream.pipe(writeStream);
111
+ }
112
+ });
113
+ }).on("error", (err) => {
114
+ clearTimeout(timeout);
115
+ finish(`SCP connection error: ${err.message}`);
116
+ });
117
+ try {
118
+ conn.connect({
119
+ host: target,
120
+ username: user,
121
+ ...(resolvedKey ? { privateKey: resolvedKey } : {})
122
+ });
123
+ }
124
+ catch (err) {
125
+ clearTimeout(timeout);
126
+ finish(`SCP connect threw: ${err.message}`);
127
+ }
128
+ });
129
+ }
130
+ catch (fatalErr) {
131
+ return { content: [{ type: "text", text: `SCP fatal error: ${fatalErr.message || fatalErr}` }] };
132
+ }
133
+ });
134
+ }
@@ -0,0 +1,83 @@
1
+ import { z } from "zod";
2
+ import { Client as SSHClient } from "ssh2";
3
+ import fs from "fs";
4
+ import path from "path";
5
+ import os from "os";
6
+ function resolvePrivateKey(privateKeyArg) {
7
+ // If not provided, try default keys
8
+ if (!privateKeyArg) {
9
+ const home = os.homedir();
10
+ const defaultKeys = [
11
+ path.join(home, '.ssh', 'id_rsa'),
12
+ path.join(home, '.ssh', 'id_ed25519'),
13
+ ];
14
+ for (const keyPath of defaultKeys) {
15
+ if (fs.existsSync(keyPath)) {
16
+ return fs.readFileSync(keyPath, 'utf8');
17
+ }
18
+ }
19
+ return undefined;
20
+ }
21
+ // If it looks like a path, try to read it
22
+ if (privateKeyArg.startsWith('/') ||
23
+ privateKeyArg.startsWith('~') ||
24
+ privateKeyArg.endsWith('.pem') ||
25
+ privateKeyArg.endsWith('.key')) {
26
+ let keyPath = privateKeyArg;
27
+ if (keyPath.startsWith('~')) {
28
+ keyPath = path.join(os.homedir(), keyPath.slice(1));
29
+ }
30
+ if (fs.existsSync(keyPath)) {
31
+ return fs.readFileSync(keyPath, 'utf8');
32
+ }
33
+ else {
34
+ throw new Error('Private key file not found: ' + keyPath);
35
+ }
36
+ }
37
+ // Otherwise, assume it's the key content
38
+ return privateKeyArg;
39
+ }
40
+ export function registerSsh(server) {
41
+ server.tool("ssh", "Connect to a target via SSH", {
42
+ target: z.string().describe("Target host"),
43
+ user: z.string().describe("Username"),
44
+ command: z.string().describe("Command to run on remote host"),
45
+ privateKey: z.string().optional().describe("Private key for authentication (PEM format, optional, or path to key file)")
46
+ }, async ({ target, user, command, privateKey }) => {
47
+ return new Promise((resolve) => {
48
+ let resolvedKey;
49
+ try {
50
+ resolvedKey = resolvePrivateKey(privateKey);
51
+ }
52
+ catch (err) {
53
+ resolve({ content: [{ type: "text", text: `SSH key error: ${err.message}` }] });
54
+ return;
55
+ }
56
+ const conn = new SSHClient();
57
+ let output = "";
58
+ conn.on("ready", () => {
59
+ conn.exec(command, (err, stream) => {
60
+ if (err) {
61
+ resolve({ content: [{ type: "text", text: `SSH error: ${err.message}` }] });
62
+ conn.end();
63
+ return;
64
+ }
65
+ stream.on("close", () => {
66
+ conn.end();
67
+ resolve({ content: [{ type: "text", text: output }] });
68
+ }).on("data", (data) => {
69
+ output += data.toString();
70
+ }).stderr.on("data", (data) => {
71
+ output += data.toString();
72
+ });
73
+ });
74
+ }).on("error", (err) => {
75
+ resolve({ content: [{ type: "text", text: `SSH connection error: ${err.message}` }] });
76
+ }).connect({
77
+ host: target,
78
+ username: user,
79
+ ...(resolvedKey ? { privateKey: resolvedKey } : {})
80
+ });
81
+ });
82
+ });
83
+ }
@@ -0,0 +1,16 @@
1
+ import { z } from "zod";
2
+ import readLastLines from "read-last-lines";
3
+ export function registerTail(server) {
4
+ server.tool("tail", "Display the end of a file", {
5
+ file: z.string().describe("File path"),
6
+ lines: z.number().default(10).describe("Number of lines")
7
+ }, async ({ file, lines }) => {
8
+ try {
9
+ const out = await readLastLines.read(file, lines);
10
+ return { content: [{ type: "text", text: out }] };
11
+ }
12
+ catch (error) {
13
+ return { content: [{ type: "text", text: `tail failed: ${error instanceof Error ? error.message : error}` }] };
14
+ }
15
+ });
16
+ }
@@ -0,0 +1,45 @@
1
+ import { z } from "zod";
2
+ export function registerTelnet(server) {
3
+ server.tool("telnet", "Test TCP connectivity to a host and port", {
4
+ target: z.string().describe("Host to connect to"),
5
+ port: z.number().describe("Port number")
6
+ }, async ({ target, port }) => {
7
+ return new Promise(async (resolve) => {
8
+ try {
9
+ const net = (await import('net')).default;
10
+ const socket = new net.Socket();
11
+ let connected = false;
12
+ let banner = '';
13
+ socket.setTimeout(2000);
14
+ socket.connect(port, target, () => {
15
+ connected = true;
16
+ });
17
+ socket.on('data', (data) => {
18
+ banner += data.toString();
19
+ // If we get a banner, close immediately
20
+ socket.end();
21
+ });
22
+ socket.on('timeout', () => {
23
+ socket.destroy();
24
+ if (!connected) {
25
+ resolve({ content: [{ type: "text", text: `Telnet failed: Connection timed out` }] });
26
+ }
27
+ else {
28
+ resolve({ content: [{ type: "text", text: `Telnet to ${target}:${port} succeeded.${banner ? '\nBanner: ' + banner.trim() : ''}` }] });
29
+ }
30
+ });
31
+ socket.on('error', (err) => {
32
+ resolve({ content: [{ type: "text", text: `Telnet failed: ${err.message}` }] });
33
+ });
34
+ socket.on('close', (hadError) => {
35
+ if (connected) {
36
+ resolve({ content: [{ type: "text", text: `Telnet to ${target}:${port} succeeded.${banner ? '\nBanner: ' + banner.trim() : ''}` }] });
37
+ }
38
+ });
39
+ }
40
+ catch (error) {
41
+ resolve({ content: [{ type: "text", text: `Telnet failed: ${error instanceof Error ? error.message : error}` }] });
42
+ }
43
+ });
44
+ });
45
+ }
@@ -0,0 +1,14 @@
1
+ import psList from "ps-list";
2
+ export function registerTop(server) {
3
+ server.tool("top", "Display system processes (snapshot)", {}, async () => {
4
+ try {
5
+ const processes = await psList();
6
+ const sorted = processes.sort((a, b) => (b.cpu || 0) - (a.cpu || 0)).slice(0, 10);
7
+ const output = sorted.map(p => `${p.pid}\t${p.name}\tCPU: ${p.cpu || 0}%\tMEM: ${p.memory || 0}`).join("\n");
8
+ return { content: [{ type: "text", text: output }] };
9
+ }
10
+ catch (error) {
11
+ return { content: [{ type: "text", text: `top failed: ${error instanceof Error ? error.message : error}` }] };
12
+ }
13
+ });
14
+ }