it-tools-mcp 3.0.23 → 3.1.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.dockerhub.md +30 -17
- package/README.md +81 -33
- package/build/index.js +47 -15
- package/build/tools/ansible/ansible-inventory-generator/index.js +212 -0
- package/build/tools/ansible/ansible-playbook-validator/index.js +128 -0
- package/build/tools/ansible/ansible-reference/index.js +393 -0
- package/build/tools/ansible/ansible-vault-decrypt/index.js +137 -0
- package/build/tools/ansible/ansible-vault-encrypt/index.js +79 -0
- package/build/tools/color/color-hex-to-rgb/index.js +29 -0
- package/build/tools/{color.js → color/color-rgb-to-hex/index.js} +1 -27
- package/build/tools/crypto/basic-auth-generator/index.js +45 -0
- package/build/tools/crypto/bcrypt-hash/index.js +67 -0
- package/build/tools/crypto/bip39-generate/index.js +53 -0
- package/build/tools/crypto/hash-md5/index.js +19 -0
- package/build/tools/crypto/hash-sha1/index.js +19 -0
- package/build/tools/crypto/hash-sha256/index.js +19 -0
- package/build/tools/crypto/hash-sha512/index.js +19 -0
- package/build/tools/crypto/hmac-generator/index.js +37 -0
- package/build/tools/crypto/jwt-decode/index.js +41 -0
- package/build/tools/crypto/otp-code-generator/index.js +67 -0
- package/build/tools/crypto/password-generate/index.js +54 -0
- package/build/tools/crypto/token-generator/index.js +75 -0
- package/build/tools/dataFormat/html-to-markdown/index.js +34 -0
- package/build/tools/dataFormat/json-diff/index.js +94 -0
- package/build/tools/dataFormat/json-format/index.js +100 -0
- package/build/tools/dataFormat/json-minify/index.js +29 -0
- package/build/tools/dataFormat/json-to-csv/index.js +34 -0
- package/build/tools/dataFormat/json-to-toml/index.js +30 -0
- package/build/tools/dataFormat/markdown-to-html/index.js +32 -0
- package/build/tools/dataFormat/phone-format/index.js +35 -0
- package/build/tools/dataFormat/sql-format/index.js +37 -0
- package/build/tools/dataFormat/toml-to-json/index.js +29 -0
- package/build/tools/dataFormat/xml-format/index.js +44 -0
- package/build/tools/dataFormat/yaml-format/index.js +58 -0
- package/build/tools/{development.js → development/crontab-generate/index.js} +1 -129
- package/build/tools/development/html-prettifier/index.js +47 -0
- package/build/tools/development/javascript-prettifier/index.js +74 -0
- package/build/tools/development/list-converter/index.js +62 -0
- package/build/tools/development/markdown-toc-generator/index.js +53 -0
- package/build/tools/development/regex-tester/index.js +69 -0
- package/build/tools/docker/docker-compose-to-docker-run/index.js +138 -0
- package/build/tools/docker/docker-compose-validator/index.js +125 -0
- package/build/tools/docker/docker-reference/index.js +188 -0
- package/build/tools/docker/docker-run-to-docker-compose/index.js +117 -0
- package/build/tools/docker/traefik-compose-generator/index.js +98 -0
- package/build/tools/encoding/base64-decode/index.js +28 -0
- package/build/tools/encoding/base64-encode/index.js +16 -0
- package/build/tools/encoding/html-decode/index.js +21 -0
- package/build/tools/encoding/html-encode/index.js +21 -0
- package/build/tools/encoding/html-entities-extended/index.js +72 -0
- package/build/tools/encoding/text-to-binary/index.js +51 -0
- package/build/tools/encoding/url-decode/index.js +28 -0
- package/build/tools/encoding/url-encode/index.js +16 -0
- package/build/tools/forensic/file-type-identifier/index.js +90 -0
- package/build/tools/forensic/safelink-decoder/index.js +54 -0
- package/build/tools/forensic/url-fanger/index.js +52 -0
- package/build/tools/idGenerators/qr-generate/index.js +76 -0
- package/build/tools/idGenerators/svg-placeholder-generator/index.js +59 -0
- package/build/tools/idGenerators/ulid-generate/index.js +34 -0
- package/build/tools/idGenerators/uuid-generate/index.js +14 -0
- package/build/tools/math/math-evaluate/index.js +33 -0
- package/build/tools/math/number-base-converter/index.js +46 -0
- package/build/tools/math/percentage-calculator/index.js +50 -0
- package/build/tools/math/roman-numeral-converter/index.js +76 -0
- package/build/tools/math/temperature-converter/index.js +59 -0
- package/build/tools/math/unix-timestamp-converter/index.js +55 -0
- package/build/tools/network/cat/index.js +15 -0
- package/build/tools/network/cidr-to-ip-range/index.js +108 -0
- package/build/tools/network/curl/index.js +35 -0
- package/build/tools/network/dig/index.js +19 -0
- package/build/tools/network/grep/index.js +18 -0
- package/build/tools/network/head/index.js +17 -0
- package/build/tools/network/iban-validate/index.js +83 -0
- package/build/tools/network/ip-range-to-cidr/index.js +88 -0
- package/build/tools/network/ip-subnet-calculator/index.js +102 -0
- package/build/tools/network/ipv4-subnet-calc/index.js +112 -0
- package/build/tools/network/ipv6-subnet-calculator/index.js +104 -0
- package/build/tools/network/ipv6-ula-generator/index.js +65 -0
- package/build/tools/network/mac-address-generate/index.js +68 -0
- package/build/tools/network/nslookup/index.js +18 -0
- package/build/tools/network/ping/index.js +20 -0
- package/build/tools/network/ps/index.js +22 -0
- package/build/tools/network/random-port/index.js +53 -0
- package/build/tools/network/scp/index.js +134 -0
- package/build/tools/network/ssh/index.js +83 -0
- package/build/tools/network/tail/index.js +16 -0
- package/build/tools/network/telnet/index.js +45 -0
- package/build/tools/network/top/index.js +14 -0
- package/build/tools/network/url-parse/index.js +52 -0
- package/build/tools/physics/angle-converter/index.js +73 -0
- package/build/tools/physics/energy-converter/index.js +72 -0
- package/build/tools/physics/power-converter/index.js +71 -0
- package/build/tools/text/ascii-art-text/index.js +112 -0
- package/build/tools/text/distinct-words/index.js +30 -0
- package/build/tools/text/emoji-search/index.js +76 -0
- package/build/tools/text/lorem-ipsum-generator/index.js +87 -0
- package/build/tools/text/numeronym-generator/index.js +37 -0
- package/build/tools/text/slugify-string/index.js +44 -0
- package/build/tools/text/string-obfuscator/index.js +49 -0
- package/build/tools/text/text-camelcase/index.js +20 -0
- package/build/tools/text/text-capitalize/index.js +16 -0
- package/build/tools/text/text-diff/index.js +72 -0
- package/build/tools/text/text-kebabcase/index.js +20 -0
- package/build/tools/text/text-lowercase/index.js +15 -0
- package/build/tools/text/text-pascalcase/index.js +18 -0
- package/build/tools/text/text-snakecase/index.js +20 -0
- package/build/tools/text/text-stats/index.js +29 -0
- package/build/tools/text/text-to-nato-alphabet/index.js +57 -0
- package/build/tools/text/text-to-unicode/index.js +50 -0
- package/build/tools/text/text-to-unicode-names/index.js +34 -0
- package/build/tools/text/text-uppercase/index.js +15 -0
- package/build/tools/utility/css-prettifier/index.js +70 -0
- package/build/tools/utility/device-info/index.js +44 -0
- package/build/tools/utility/email-normalizer/index.js +73 -0
- package/build/tools/utility/http-status-codes/index.js +173 -0
- package/build/tools/utility/mime-types/index.js +121 -0
- package/build/tools/utility/port-numbers/index.js +106 -0
- package/build/tools/utility/rem-px-converter/index.js +63 -0
- package/package.json +3 -3
- package/build/tools/crypto.js +0 -445
- package/build/tools/dataFormat.js +0 -535
- package/build/tools/encoding.js +0 -240
- package/build/tools/idGenerators.js +0 -180
- package/build/tools/math.js +0 -310
- package/build/tools/network.js +0 -931
- package/build/tools/text.js +0 -678
- package/build/tools/utility.js +0 -407
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function registerUrlParse(server) {
|
|
3
|
+
server.tool("url-parse", "Parse URL into components", {
|
|
4
|
+
url: z.string().describe("URL to parse"),
|
|
5
|
+
}, async ({ url }) => {
|
|
6
|
+
try {
|
|
7
|
+
const urlObj = new URL(url);
|
|
8
|
+
// Parse query parameters
|
|
9
|
+
const params = {};
|
|
10
|
+
urlObj.searchParams.forEach((value, key) => {
|
|
11
|
+
params[key] = value;
|
|
12
|
+
});
|
|
13
|
+
return {
|
|
14
|
+
content: [
|
|
15
|
+
{
|
|
16
|
+
type: "text",
|
|
17
|
+
text: `URL Components:
|
|
18
|
+
|
|
19
|
+
Original URL: ${url}
|
|
20
|
+
|
|
21
|
+
Protocol: ${urlObj.protocol}
|
|
22
|
+
Host: ${urlObj.host}
|
|
23
|
+
Hostname: ${urlObj.hostname}
|
|
24
|
+
Port: ${urlObj.port || 'default'}
|
|
25
|
+
Pathname: ${urlObj.pathname}
|
|
26
|
+
Search: ${urlObj.search}
|
|
27
|
+
Hash: ${urlObj.hash}
|
|
28
|
+
Origin: ${urlObj.origin}
|
|
29
|
+
|
|
30
|
+
Query Parameters:
|
|
31
|
+
${Object.keys(params).length > 0
|
|
32
|
+
? Object.entries(params).map(([key, value]) => ` ${key}: ${value}`).join('\n')
|
|
33
|
+
: ' (none)'}
|
|
34
|
+
|
|
35
|
+
Path Segments:
|
|
36
|
+
${urlObj.pathname.split('/').filter(segment => segment).map((segment, i) => ` ${i + 1}. ${segment}`).join('\n') || ' (none)'}`,
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
return {
|
|
43
|
+
content: [
|
|
44
|
+
{
|
|
45
|
+
type: "text",
|
|
46
|
+
text: `Error parsing URL: ${error instanceof Error ? error.message : 'Invalid URL format'}`,
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function registerAngleConverter(server) {
|
|
3
|
+
server.tool("angle-converter", "Convert between different angle units", {
|
|
4
|
+
value: z.number().describe("Angle value to convert"),
|
|
5
|
+
fromUnit: z.enum([
|
|
6
|
+
"degree", "radian", "gradian", "turn", "arcminute", "arcsecond"
|
|
7
|
+
]).describe("Source angle unit"),
|
|
8
|
+
toUnit: z.enum([
|
|
9
|
+
"degree", "radian", "gradian", "turn", "arcminute", "arcsecond"
|
|
10
|
+
]).describe("Target angle unit")
|
|
11
|
+
}, async ({ value, fromUnit, toUnit }) => {
|
|
12
|
+
try {
|
|
13
|
+
// Conversion factors to degrees
|
|
14
|
+
const toDegrees = {
|
|
15
|
+
"degree": 1,
|
|
16
|
+
"radian": 180 / Math.PI,
|
|
17
|
+
"gradian": 0.9, // 1 gradian = 0.9 degrees
|
|
18
|
+
"turn": 360, // 1 turn = 360 degrees
|
|
19
|
+
"arcminute": 1 / 60, // 1 arcminute = 1/60 degree
|
|
20
|
+
"arcsecond": 1 / 3600 // 1 arcsecond = 1/3600 degree
|
|
21
|
+
};
|
|
22
|
+
// Convert to degrees first
|
|
23
|
+
const degrees = value * toDegrees[fromUnit];
|
|
24
|
+
// Convert from degrees to target unit
|
|
25
|
+
const result = degrees / toDegrees[toUnit];
|
|
26
|
+
const units = {
|
|
27
|
+
"degree": "°",
|
|
28
|
+
"radian": "rad",
|
|
29
|
+
"gradian": "gon",
|
|
30
|
+
"turn": "tr",
|
|
31
|
+
"arcminute": "'",
|
|
32
|
+
"arcsecond": "\""
|
|
33
|
+
};
|
|
34
|
+
// Additional conversions for context
|
|
35
|
+
const inDegrees = degrees;
|
|
36
|
+
const inRadians = degrees * Math.PI / 180;
|
|
37
|
+
const inGradians = degrees / 0.9;
|
|
38
|
+
return {
|
|
39
|
+
content: [{
|
|
40
|
+
type: "text",
|
|
41
|
+
text: `Angle Conversion Results:
|
|
42
|
+
|
|
43
|
+
${value} ${units[fromUnit]} = ${result} ${units[toUnit]}
|
|
44
|
+
|
|
45
|
+
All Conversions:
|
|
46
|
+
• Degrees: ${inDegrees.toFixed(6)}°
|
|
47
|
+
• Radians: ${inRadians.toFixed(6)} rad
|
|
48
|
+
• Gradians: ${inGradians.toFixed(6)} gon
|
|
49
|
+
• Turns: ${(inDegrees / 360).toFixed(6)} tr
|
|
50
|
+
• Arcminutes: ${(inDegrees * 60).toFixed(2)}'
|
|
51
|
+
• Arcseconds: ${(inDegrees * 3600).toFixed(2)}"
|
|
52
|
+
|
|
53
|
+
Trigonometric Values:
|
|
54
|
+
• sin: ${Math.sin(inRadians).toFixed(6)}
|
|
55
|
+
• cos: ${Math.cos(inRadians).toFixed(6)}
|
|
56
|
+
• tan: ${Math.tan(inRadians).toFixed(6)}
|
|
57
|
+
|
|
58
|
+
Reference:
|
|
59
|
+
• π rad = 180° = 200 gon = 0.5 tr
|
|
60
|
+
• 1° = 60' = 3600"`
|
|
61
|
+
}]
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
return {
|
|
66
|
+
content: [{
|
|
67
|
+
type: "text",
|
|
68
|
+
text: `Error converting angle: ${error instanceof Error ? error.message : 'Unknown error'}`
|
|
69
|
+
}]
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function registerEnergyConverter(server) {
|
|
3
|
+
server.tool("energy-converter", "Convert between different energy units", {
|
|
4
|
+
value: z.number().describe("Energy value to convert"),
|
|
5
|
+
fromUnit: z.enum([
|
|
6
|
+
"joule", "kilojoule", "calorie", "kilocalorie", "btu",
|
|
7
|
+
"watt-hour", "kilowatt-hour", "electronvolt", "foot-pound"
|
|
8
|
+
]).describe("Source energy unit"),
|
|
9
|
+
toUnit: z.enum([
|
|
10
|
+
"joule", "kilojoule", "calorie", "kilocalorie", "btu",
|
|
11
|
+
"watt-hour", "kilowatt-hour", "electronvolt", "foot-pound"
|
|
12
|
+
]).describe("Target energy unit")
|
|
13
|
+
}, async ({ value, fromUnit, toUnit }) => {
|
|
14
|
+
try {
|
|
15
|
+
// Conversion factors to joules
|
|
16
|
+
const toJoules = {
|
|
17
|
+
"joule": 1,
|
|
18
|
+
"kilojoule": 1000,
|
|
19
|
+
"calorie": 4.184,
|
|
20
|
+
"kilocalorie": 4184,
|
|
21
|
+
"btu": 1055.06,
|
|
22
|
+
"watt-hour": 3600,
|
|
23
|
+
"kilowatt-hour": 3600000,
|
|
24
|
+
"electronvolt": 1.602176634e-19,
|
|
25
|
+
"foot-pound": 1.355818
|
|
26
|
+
};
|
|
27
|
+
// Convert to joules first
|
|
28
|
+
const joules = value * toJoules[fromUnit];
|
|
29
|
+
// Convert from joules to target unit
|
|
30
|
+
const result = joules / toJoules[toUnit];
|
|
31
|
+
const units = {
|
|
32
|
+
"joule": "J",
|
|
33
|
+
"kilojoule": "kJ",
|
|
34
|
+
"calorie": "cal",
|
|
35
|
+
"kilocalorie": "kcal",
|
|
36
|
+
"btu": "BTU",
|
|
37
|
+
"watt-hour": "Wh",
|
|
38
|
+
"kilowatt-hour": "kWh",
|
|
39
|
+
"electronvolt": "eV",
|
|
40
|
+
"foot-pound": "ft·lb"
|
|
41
|
+
};
|
|
42
|
+
return {
|
|
43
|
+
content: [{
|
|
44
|
+
type: "text",
|
|
45
|
+
text: `Energy Conversion Results:
|
|
46
|
+
|
|
47
|
+
${value} ${units[fromUnit]} = ${result.toExponential(6)} ${units[toUnit]}
|
|
48
|
+
|
|
49
|
+
Formatted Results:
|
|
50
|
+
• Scientific: ${result.toExponential(6)} ${units[toUnit]}
|
|
51
|
+
• Fixed: ${result.toFixed(6)} ${units[toUnit]}
|
|
52
|
+
• Compact: ${result.toPrecision(6)} ${units[toUnit]}
|
|
53
|
+
|
|
54
|
+
Intermediate (Joules): ${joules.toExponential(6)} J
|
|
55
|
+
|
|
56
|
+
Common Energy Equivalents:
|
|
57
|
+
• 1 kWh = 3.6 MJ = 860 kcal
|
|
58
|
+
• 1 BTU = 1055 J = 252 cal
|
|
59
|
+
• 1 eV = 1.602 × 10⁻¹⁹ J`
|
|
60
|
+
}]
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
return {
|
|
65
|
+
content: [{
|
|
66
|
+
type: "text",
|
|
67
|
+
text: `Error converting energy: ${error instanceof Error ? error.message : 'Unknown error'}`
|
|
68
|
+
}]
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function registerPowerConverter(server) {
|
|
3
|
+
server.tool("power-converter", "Convert between different power units", {
|
|
4
|
+
value: z.number().describe("Power value to convert"),
|
|
5
|
+
fromUnit: z.enum([
|
|
6
|
+
"watt", "kilowatt", "megawatt", "horsepower", "metric-horsepower",
|
|
7
|
+
"btu-per-hour", "calorie-per-second", "foot-pound-per-second"
|
|
8
|
+
]).describe("Source power unit"),
|
|
9
|
+
toUnit: z.enum([
|
|
10
|
+
"watt", "kilowatt", "megawatt", "horsepower", "metric-horsepower",
|
|
11
|
+
"btu-per-hour", "calorie-per-second", "foot-pound-per-second"
|
|
12
|
+
]).describe("Target power unit")
|
|
13
|
+
}, async ({ value, fromUnit, toUnit }) => {
|
|
14
|
+
try {
|
|
15
|
+
// Conversion factors to watts
|
|
16
|
+
const toWatts = {
|
|
17
|
+
"watt": 1,
|
|
18
|
+
"kilowatt": 1000,
|
|
19
|
+
"megawatt": 1000000,
|
|
20
|
+
"horsepower": 745.7, // Mechanical horsepower
|
|
21
|
+
"metric-horsepower": 735.5,
|
|
22
|
+
"btu-per-hour": 0.293071,
|
|
23
|
+
"calorie-per-second": 4.184,
|
|
24
|
+
"foot-pound-per-second": 1.355818
|
|
25
|
+
};
|
|
26
|
+
// Convert to watts first
|
|
27
|
+
const watts = value * toWatts[fromUnit];
|
|
28
|
+
// Convert from watts to target unit
|
|
29
|
+
const result = watts / toWatts[toUnit];
|
|
30
|
+
const units = {
|
|
31
|
+
"watt": "W",
|
|
32
|
+
"kilowatt": "kW",
|
|
33
|
+
"megawatt": "MW",
|
|
34
|
+
"horsepower": "hp",
|
|
35
|
+
"metric-horsepower": "PS",
|
|
36
|
+
"btu-per-hour": "BTU/h",
|
|
37
|
+
"calorie-per-second": "cal/s",
|
|
38
|
+
"foot-pound-per-second": "ft·lb/s"
|
|
39
|
+
};
|
|
40
|
+
return {
|
|
41
|
+
content: [{
|
|
42
|
+
type: "text",
|
|
43
|
+
text: `Power Conversion Results:
|
|
44
|
+
|
|
45
|
+
${value} ${units[fromUnit]} = ${result.toFixed(6)} ${units[toUnit]}
|
|
46
|
+
|
|
47
|
+
Common Conversions:
|
|
48
|
+
• Watts: ${watts.toFixed(3)} W
|
|
49
|
+
• Kilowatts: ${(watts / 1000).toFixed(6)} kW
|
|
50
|
+
• Horsepower: ${(watts / 745.7).toFixed(6)} hp
|
|
51
|
+
• Metric HP: ${(watts / 735.5).toFixed(6)} PS
|
|
52
|
+
• BTU/hour: ${(watts / 0.293071).toFixed(3)} BTU/h
|
|
53
|
+
|
|
54
|
+
Energy Relationships:
|
|
55
|
+
• Power × Time = Energy
|
|
56
|
+
• 1 kW for 1 hour = 1 kWh = 3.6 MJ
|
|
57
|
+
• 1 hp ≈ 746 W (mechanical)
|
|
58
|
+
• 1 PS ≈ 736 W (metric)`
|
|
59
|
+
}]
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
return {
|
|
64
|
+
content: [{
|
|
65
|
+
type: "text",
|
|
66
|
+
text: `Error converting power: ${error instanceof Error ? error.message : 'Unknown error'}`
|
|
67
|
+
}]
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function registerAsciiArtText(server) {
|
|
3
|
+
server.tool("ascii-art-text", "Generate ASCII art text", {
|
|
4
|
+
text: z.string().describe("Text to convert to ASCII art, or use 'LIST_FONTS' to get all available font names"),
|
|
5
|
+
font: z.string().describe("ASCII art font style. Supports all 295+ figlet fonts. Use 'standard' if unsure.").optional(),
|
|
6
|
+
}, async ({ text, font = "standard" }) => {
|
|
7
|
+
try {
|
|
8
|
+
// Generate ASCII art using figlet
|
|
9
|
+
const figlet = await import('figlet');
|
|
10
|
+
// Get list of available fonts
|
|
11
|
+
const availableFonts = figlet.default.fontsSync();
|
|
12
|
+
// Check if user wants to list all fonts
|
|
13
|
+
if (text.toUpperCase() === 'LIST_FONTS') {
|
|
14
|
+
const sortedFonts = availableFonts.sort();
|
|
15
|
+
const popularFonts = [
|
|
16
|
+
"Standard", "Big", "Small", "Slant", "3-D", "Banner", "Block", "Shadow",
|
|
17
|
+
"Larry 3D", "Doom", "Star Wars", "Gothic", "Graffiti", "Bubble", "Digital"
|
|
18
|
+
];
|
|
19
|
+
// Filter popular fonts that are actually available
|
|
20
|
+
const availablePopularFonts = popularFonts.filter(f => sortedFonts.some(availableFont => availableFont === f));
|
|
21
|
+
return {
|
|
22
|
+
content: [
|
|
23
|
+
{
|
|
24
|
+
type: "text",
|
|
25
|
+
text: `Available ASCII Art Fonts (${sortedFonts.length} total):
|
|
26
|
+
|
|
27
|
+
🌟 POPULAR FONTS:
|
|
28
|
+
${availablePopularFonts.join(', ')}
|
|
29
|
+
|
|
30
|
+
📝 ALL AVAILABLE FONTS:
|
|
31
|
+
${sortedFonts.join(', ')}
|
|
32
|
+
|
|
33
|
+
💡 Usage: Use any font name above as the 'font' parameter.
|
|
34
|
+
Examples: 'Standard', '3-D', 'Larry 3D', 'Banner', 'Block', etc.`,
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
// Find the exact font match (case insensitive and flexible matching)
|
|
40
|
+
let targetFont = "Standard"; // Default fallback
|
|
41
|
+
const inputFont = font.toLowerCase();
|
|
42
|
+
// Direct match
|
|
43
|
+
const exactMatch = availableFonts.find(f => f.toLowerCase() === inputFont);
|
|
44
|
+
if (exactMatch) {
|
|
45
|
+
targetFont = exactMatch;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// Fuzzy match - look for fonts that contain the input as substring
|
|
49
|
+
const partialMatch = availableFonts.find(f => f.toLowerCase().includes(inputFont) ||
|
|
50
|
+
inputFont.includes(f.toLowerCase()));
|
|
51
|
+
if (partialMatch) {
|
|
52
|
+
targetFont = partialMatch;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Generate ASCII art
|
|
56
|
+
const asciiArt = figlet.default.textSync(text, {
|
|
57
|
+
font: targetFont,
|
|
58
|
+
horizontalLayout: 'default',
|
|
59
|
+
verticalLayout: 'default'
|
|
60
|
+
});
|
|
61
|
+
const fontUsed = targetFont === font ? font : `${font} → ${targetFont}`;
|
|
62
|
+
return {
|
|
63
|
+
content: [
|
|
64
|
+
{
|
|
65
|
+
type: "text",
|
|
66
|
+
text: `ASCII Art (${fontUsed}):\n\n${asciiArt}`,
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
// Get available fonts for error message
|
|
73
|
+
try {
|
|
74
|
+
const figlet = await import('figlet');
|
|
75
|
+
const availableFonts = figlet.default.fontsSync();
|
|
76
|
+
const popularFonts = [
|
|
77
|
+
"Standard", "Big", "Small", "Slant", "3-D", "Banner", "Block", "Shadow",
|
|
78
|
+
"Larry 3D", "Doom", "Star Wars", "Gothic", "Graffiti", "Bubble", "Digital"
|
|
79
|
+
];
|
|
80
|
+
return {
|
|
81
|
+
content: [
|
|
82
|
+
{
|
|
83
|
+
type: "text",
|
|
84
|
+
text: `Error generating ASCII art: ${error instanceof Error ? error.message : 'Unknown error'}
|
|
85
|
+
|
|
86
|
+
Font '${font}' not found or invalid.
|
|
87
|
+
|
|
88
|
+
Popular fonts to try: ${popularFonts.join(', ')}
|
|
89
|
+
|
|
90
|
+
Total available fonts: ${availableFonts.length}
|
|
91
|
+
Some examples: ${availableFonts.slice(0, 10).join(', ')}...
|
|
92
|
+
|
|
93
|
+
Note: ASCII art generation works best with short text (1-10 characters).`,
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return {
|
|
100
|
+
content: [
|
|
101
|
+
{
|
|
102
|
+
type: "text",
|
|
103
|
+
text: `Error generating ASCII art: ${error instanceof Error ? error.message : 'Unknown error'}
|
|
104
|
+
|
|
105
|
+
Note: ASCII art generation works best with short text (1-10 characters).`,
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|