mcp-devutils 1.3.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -0
- package/index.js +418 -1
- package/package.json +13 -3
package/README.md
CHANGED
|
@@ -1,4 +1,62 @@
|
|
|
1
|
+
# mcp-devutils
|
|
1
2
|
|
|
3
|
+
MCP server with **35 developer utilities** for Claude Desktop, Cursor, and any MCP-compatible AI assistant.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{
|
|
9
|
+
"mcpServers": {
|
|
10
|
+
"devutils": {
|
|
11
|
+
"command": "npx",
|
|
12
|
+
"args": ["-y", "mcp-devutils"]
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Tools (35)
|
|
19
|
+
|
|
20
|
+
| Tool | Description |
|
|
21
|
+
|------|-------------|
|
|
22
|
+
| `uuid` | Generate UUID v4 (batch support) |
|
|
23
|
+
| `hash` | Hash text (md5, sha1, sha256) |
|
|
24
|
+
| `base64` | Encode/decode base64 |
|
|
25
|
+
| `timestamp` | Convert Unix ↔ ISO 8601 dates |
|
|
26
|
+
| `jwt_decode` | Decode JWT tokens (header + payload) |
|
|
27
|
+
| `random_string` | Generate random strings/passwords |
|
|
28
|
+
| `url_encode` | URL encode/decode |
|
|
29
|
+
| `json_format` | Pretty-print or minify JSON |
|
|
30
|
+
| `regex_test` | Test regex patterns with match details |
|
|
31
|
+
| `cron_explain` | Explain cron expressions + next run times |
|
|
32
|
+
| `hmac` | Generate HMAC signatures |
|
|
33
|
+
| `color_convert` | Convert hex ↔ RGB ↔ HSL colors |
|
|
34
|
+
| `semver_compare` | Compare semantic versions |
|
|
35
|
+
| `http_status` | Look up HTTP status code meanings |
|
|
36
|
+
| `slug` | Generate URL-safe slugs |
|
|
37
|
+
| `escape_html` | Escape/unescape HTML entities |
|
|
38
|
+
| `chmod_calc` | Convert numeric ↔ symbolic permissions |
|
|
39
|
+
| `diff` | Compare two text strings |
|
|
40
|
+
| `number_base` | Convert decimal/hex/octal/binary |
|
|
41
|
+
| `lorem_ipsum` | Generate placeholder text |
|
|
42
|
+
| `word_count` | Count chars, words, lines, bytes |
|
|
43
|
+
| `cidr` | Parse CIDR notation (network, broadcast, hosts) |
|
|
44
|
+
| `case_convert` | Convert camelCase/snake_case/PascalCase/kebab-case |
|
|
45
|
+
| `markdown_toc` | Generate table of contents from markdown |
|
|
46
|
+
| `env_parse` | Parse and validate .env files |
|
|
47
|
+
| `ip_info` | Analyze IP addresses (type, class, private/public) |
|
|
48
|
+
| `password_strength` | Analyze password entropy and strength |
|
|
49
|
+
| `data_size` | Convert between bytes/KB/MB/GB/TB (SI + IEC) |
|
|
50
|
+
| `string_escape` | Escape strings for JSON/CSV/regex/SQL/shell |
|
|
51
|
+
| `nanoid` | Generate compact, URL-safe unique IDs |
|
|
52
|
+
| `csv_json` | Convert between CSV and JSON |
|
|
53
|
+
| `hex_encode` | Hex encode/decode text |
|
|
54
|
+
| `char_info` | Unicode character info (codepoint, UTF-8 bytes, HTML entity) |
|
|
55
|
+
| `byte_count` | Count string bytes in UTF-8/UTF-16/ASCII |
|
|
56
|
+
|
|
57
|
+
## Zero dependencies
|
|
58
|
+
|
|
59
|
+
Only requires `@modelcontextprotocol/sdk`. All tools use Node.js built-ins.
|
|
2
60
|
|
|
3
61
|
## Support
|
|
4
62
|
|
|
@@ -6,3 +64,7 @@ If this tool saves you time, consider supporting development:
|
|
|
6
64
|
|
|
7
65
|
- [Buy me a coffee](https://buymeacoffee.com/gl89tu25lp)
|
|
8
66
|
- [Tip via Stripe ($3)](https://buy.stripe.com/dRm8wP8R295Z9VyeN59Zm00)
|
|
67
|
+
|
|
68
|
+
## License
|
|
69
|
+
|
|
70
|
+
MIT
|
package/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprot
|
|
|
5
5
|
import crypto from "crypto";
|
|
6
6
|
|
|
7
7
|
const server = new Server(
|
|
8
|
-
{ name: "mcp-devutils", version: "1.
|
|
8
|
+
{ name: "mcp-devutils", version: "1.5.0" },
|
|
9
9
|
{ capabilities: { tools: {} } }
|
|
10
10
|
);
|
|
11
11
|
|
|
@@ -335,6 +335,135 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
335
335
|
},
|
|
336
336
|
required: ["markdown"]
|
|
337
337
|
}
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
name: "env_parse",
|
|
341
|
+
description: "Parse and validate .env file contents — shows keys, detects issues like missing values, duplicate keys, or invalid lines",
|
|
342
|
+
inputSchema: {
|
|
343
|
+
type: "object",
|
|
344
|
+
properties: {
|
|
345
|
+
content: { type: "string", description: ".env file content to parse and validate" }
|
|
346
|
+
},
|
|
347
|
+
required: ["content"]
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
name: "ip_info",
|
|
352
|
+
description: "Parse and analyze an IP address — shows type (IPv4/IPv6), class, whether private/public/loopback/link-local",
|
|
353
|
+
inputSchema: {
|
|
354
|
+
type: "object",
|
|
355
|
+
properties: {
|
|
356
|
+
ip: { type: "string", description: "IP address to analyze (e.g. '192.168.1.1' or '::1')" }
|
|
357
|
+
},
|
|
358
|
+
required: ["ip"]
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
name: "password_strength",
|
|
363
|
+
description: "Analyze password strength — calculates entropy, checks length, character diversity, and common patterns",
|
|
364
|
+
inputSchema: {
|
|
365
|
+
type: "object",
|
|
366
|
+
properties: {
|
|
367
|
+
password: { type: "string", description: "Password to analyze" }
|
|
368
|
+
},
|
|
369
|
+
required: ["password"]
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
name: "data_size",
|
|
374
|
+
description: "Convert between data size units (bytes, KB, MB, GB, TB, PB) with both decimal (SI) and binary (IEC) representations",
|
|
375
|
+
inputSchema: {
|
|
376
|
+
type: "object",
|
|
377
|
+
properties: {
|
|
378
|
+
value: { type: "number", description: "Numeric value to convert" },
|
|
379
|
+
unit: {
|
|
380
|
+
type: "string",
|
|
381
|
+
enum: ["B", "KB", "MB", "GB", "TB", "PB", "KiB", "MiB", "GiB", "TiB", "PiB"],
|
|
382
|
+
description: "Source unit (default: B for bytes)"
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
required: ["value"]
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
name: "string_escape",
|
|
390
|
+
description: "Escape or unescape strings for various contexts: JSON, CSV, regex, SQL, or shell",
|
|
391
|
+
inputSchema: {
|
|
392
|
+
type: "object",
|
|
393
|
+
properties: {
|
|
394
|
+
text: { type: "string", description: "Text to escape or unescape" },
|
|
395
|
+
format: {
|
|
396
|
+
type: "string",
|
|
397
|
+
enum: ["json", "csv", "regex", "sql", "shell"],
|
|
398
|
+
description: "Target format to escape for"
|
|
399
|
+
},
|
|
400
|
+
action: {
|
|
401
|
+
type: "string",
|
|
402
|
+
enum: ["escape", "unescape"],
|
|
403
|
+
description: "Action: escape or unescape (default: escape)"
|
|
404
|
+
}
|
|
405
|
+
},
|
|
406
|
+
required: ["text", "format"]
|
|
407
|
+
}
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
name: "nanoid",
|
|
411
|
+
description: "Generate compact, URL-safe unique IDs (like UUID but shorter). Customizable length and alphabet.",
|
|
412
|
+
inputSchema: {
|
|
413
|
+
type: "object",
|
|
414
|
+
properties: {
|
|
415
|
+
length: { type: "number", description: "ID length (default: 21)" },
|
|
416
|
+
alphabet: { type: "string", description: "Custom alphabet (default: A-Za-z0-9_-)" },
|
|
417
|
+
count: { type: "number", description: "Number of IDs to generate (default: 1, max: 10)" }
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
name: "csv_json",
|
|
423
|
+
description: "Convert between CSV and JSON. CSV→JSON parses CSV text into an array of objects. JSON→CSV converts an array of objects to CSV.",
|
|
424
|
+
inputSchema: {
|
|
425
|
+
type: "object",
|
|
426
|
+
properties: {
|
|
427
|
+
input: { type: "string", description: "CSV text or JSON string to convert" },
|
|
428
|
+
direction: { type: "string", enum: ["csv_to_json", "json_to_csv"], description: "Conversion direction" },
|
|
429
|
+
delimiter: { type: "string", description: "CSV delimiter (default: comma)" }
|
|
430
|
+
},
|
|
431
|
+
required: ["input", "direction"]
|
|
432
|
+
}
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
name: "hex_encode",
|
|
436
|
+
description: "Encode text to hexadecimal or decode hex back to text",
|
|
437
|
+
inputSchema: {
|
|
438
|
+
type: "object",
|
|
439
|
+
properties: {
|
|
440
|
+
text: { type: "string", description: "Text to encode or hex string to decode" },
|
|
441
|
+
action: { type: "string", enum: ["encode", "decode"], description: "Action (default: encode)" }
|
|
442
|
+
},
|
|
443
|
+
required: ["text"]
|
|
444
|
+
}
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
name: "char_info",
|
|
448
|
+
description: "Get Unicode character info — codepoint, name category, UTF-8 bytes, HTML entity for each character in the input",
|
|
449
|
+
inputSchema: {
|
|
450
|
+
type: "object",
|
|
451
|
+
properties: {
|
|
452
|
+
text: { type: "string", description: "Characters to analyze (1-20 chars)" }
|
|
453
|
+
},
|
|
454
|
+
required: ["text"]
|
|
455
|
+
}
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
name: "byte_count",
|
|
459
|
+
description: "Count the byte length of a string in UTF-8, UTF-16, and ASCII. Useful for checking API payload sizes and database field limits.",
|
|
460
|
+
inputSchema: {
|
|
461
|
+
type: "object",
|
|
462
|
+
properties: {
|
|
463
|
+
text: { type: "string", description: "Text to measure" }
|
|
464
|
+
},
|
|
465
|
+
required: ["text"]
|
|
466
|
+
}
|
|
338
467
|
}
|
|
339
468
|
]
|
|
340
469
|
};
|
|
@@ -1032,6 +1161,294 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1032
1161
|
return { content: [{ type: "text", text: toc.join("\n") }] };
|
|
1033
1162
|
}
|
|
1034
1163
|
|
|
1164
|
+
case "env_parse": {
|
|
1165
|
+
const { content } = args;
|
|
1166
|
+
const lines = content.split("\n");
|
|
1167
|
+
const keys = [];
|
|
1168
|
+
const issues = [];
|
|
1169
|
+
const seen = new Set();
|
|
1170
|
+
lines.forEach((line, i) => {
|
|
1171
|
+
const num = i + 1;
|
|
1172
|
+
const trimmed = line.trim();
|
|
1173
|
+
if (!trimmed || trimmed.startsWith("#")) return;
|
|
1174
|
+
const eqIdx = trimmed.indexOf("=");
|
|
1175
|
+
if (eqIdx === -1) {
|
|
1176
|
+
issues.push(`Line ${num}: Invalid format (no '=' found): ${trimmed}`);
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
const key = trimmed.substring(0, eqIdx).trim();
|
|
1180
|
+
const val = trimmed.substring(eqIdx + 1).trim();
|
|
1181
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
|
|
1182
|
+
issues.push(`Line ${num}: Invalid key name '${key}'`);
|
|
1183
|
+
}
|
|
1184
|
+
if (seen.has(key)) {
|
|
1185
|
+
issues.push(`Line ${num}: Duplicate key '${key}'`);
|
|
1186
|
+
}
|
|
1187
|
+
seen.add(key);
|
|
1188
|
+
if (!val) {
|
|
1189
|
+
issues.push(`Line ${num}: Empty value for '${key}'`);
|
|
1190
|
+
}
|
|
1191
|
+
keys.push({ key, value: val.length > 50 ? val.substring(0, 50) + "..." : val, line: num });
|
|
1192
|
+
});
|
|
1193
|
+
const output = [`Parsed ${keys.length} key(s) from ${lines.length} line(s)`];
|
|
1194
|
+
if (keys.length > 0) {
|
|
1195
|
+
output.push("\nKeys:");
|
|
1196
|
+
keys.forEach(k => output.push(` ${k.key} = ${k.value} (line ${k.line})`));
|
|
1197
|
+
}
|
|
1198
|
+
if (issues.length > 0) {
|
|
1199
|
+
output.push(`\n⚠ ${issues.length} issue(s):`);
|
|
1200
|
+
issues.forEach(i => output.push(` ${i}`));
|
|
1201
|
+
} else {
|
|
1202
|
+
output.push("\n✓ No issues found");
|
|
1203
|
+
}
|
|
1204
|
+
return { content: [{ type: "text", text: output.join("\n") }] };
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
case "ip_info": {
|
|
1208
|
+
const { ip } = args;
|
|
1209
|
+
const trimmed = ip.trim();
|
|
1210
|
+
const output = [];
|
|
1211
|
+
if (trimmed.includes(":")) {
|
|
1212
|
+
output.push(`IP: ${trimmed}`);
|
|
1213
|
+
output.push(`Version: IPv6`);
|
|
1214
|
+
if (trimmed === "::1") output.push("Type: Loopback");
|
|
1215
|
+
else if (trimmed.startsWith("fe80:")) output.push("Type: Link-local");
|
|
1216
|
+
else if (trimmed.startsWith("fc") || trimmed.startsWith("fd")) output.push("Type: Unique local (private)");
|
|
1217
|
+
else if (trimmed.startsWith("ff")) output.push("Type: Multicast");
|
|
1218
|
+
else if (trimmed === "::") output.push("Type: Unspecified");
|
|
1219
|
+
else output.push("Type: Global unicast (public)");
|
|
1220
|
+
} else {
|
|
1221
|
+
const parts = trimmed.split(".");
|
|
1222
|
+
if (parts.length !== 4 || parts.some(p => isNaN(p) || +p < 0 || +p > 255)) {
|
|
1223
|
+
throw new Error(`Invalid IPv4 address: ${trimmed}`);
|
|
1224
|
+
}
|
|
1225
|
+
const octets = parts.map(Number);
|
|
1226
|
+
output.push(`IP: ${trimmed}`);
|
|
1227
|
+
output.push(`Version: IPv4`);
|
|
1228
|
+
output.push(`Binary: ${octets.map(o => o.toString(2).padStart(8, "0")).join(".")}`);
|
|
1229
|
+
if (octets[0] === 127) output.push("Type: Loopback");
|
|
1230
|
+
else if (octets[0] === 10) output.push("Type: Private (10.0.0.0/8, Class A)");
|
|
1231
|
+
else if (octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31) output.push("Type: Private (172.16.0.0/12, Class B)");
|
|
1232
|
+
else if (octets[0] === 192 && octets[1] === 168) output.push("Type: Private (192.168.0.0/16, Class C)");
|
|
1233
|
+
else if (octets[0] === 169 && octets[1] === 254) output.push("Type: Link-local (APIPA)");
|
|
1234
|
+
else if (octets[0] >= 224 && octets[0] <= 239) output.push("Type: Multicast");
|
|
1235
|
+
else if (octets[0] >= 240) output.push("Type: Reserved");
|
|
1236
|
+
else output.push("Type: Public");
|
|
1237
|
+
if (octets[0] < 128) output.push("Class: A");
|
|
1238
|
+
else if (octets[0] < 192) output.push("Class: B");
|
|
1239
|
+
else if (octets[0] < 224) output.push("Class: C");
|
|
1240
|
+
else if (octets[0] < 240) output.push("Class: D (Multicast)");
|
|
1241
|
+
else output.push("Class: E (Reserved)");
|
|
1242
|
+
}
|
|
1243
|
+
return { content: [{ type: "text", text: output.join("\n") }] };
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
case "password_strength": {
|
|
1247
|
+
const { password } = args;
|
|
1248
|
+
const len = password.length;
|
|
1249
|
+
let charsetSize = 0;
|
|
1250
|
+
if (/[a-z]/.test(password)) charsetSize += 26;
|
|
1251
|
+
if (/[A-Z]/.test(password)) charsetSize += 26;
|
|
1252
|
+
if (/[0-9]/.test(password)) charsetSize += 10;
|
|
1253
|
+
if (/[^a-zA-Z0-9]/.test(password)) charsetSize += 32;
|
|
1254
|
+
const entropy = Math.round(len * Math.log2(charsetSize || 1) * 100) / 100;
|
|
1255
|
+
const issues = [];
|
|
1256
|
+
if (len < 8) issues.push("Too short (< 8 characters)");
|
|
1257
|
+
if (!/[A-Z]/.test(password)) issues.push("No uppercase letters");
|
|
1258
|
+
if (!/[a-z]/.test(password)) issues.push("No lowercase letters");
|
|
1259
|
+
if (!/[0-9]/.test(password)) issues.push("No digits");
|
|
1260
|
+
if (!/[^a-zA-Z0-9]/.test(password)) issues.push("No special characters");
|
|
1261
|
+
if (/(.)\1{2,}/.test(password)) issues.push("Contains repeated characters (3+)");
|
|
1262
|
+
if (/^(123|abc|qwerty|password|admin|letmein)/i.test(password)) issues.push("Starts with common pattern");
|
|
1263
|
+
let strength;
|
|
1264
|
+
if (entropy < 28) strength = "Very Weak";
|
|
1265
|
+
else if (entropy < 36) strength = "Weak";
|
|
1266
|
+
else if (entropy < 60) strength = "Moderate";
|
|
1267
|
+
else if (entropy < 80) strength = "Strong";
|
|
1268
|
+
else strength = "Very Strong";
|
|
1269
|
+
const output = [
|
|
1270
|
+
`Password length: ${len}`,
|
|
1271
|
+
`Charset size: ${charsetSize}`,
|
|
1272
|
+
`Entropy: ${entropy} bits`,
|
|
1273
|
+
`Strength: ${strength}`,
|
|
1274
|
+
];
|
|
1275
|
+
if (issues.length > 0) {
|
|
1276
|
+
output.push(`\nIssues (${issues.length}):`);
|
|
1277
|
+
issues.forEach(i => output.push(` - ${i}`));
|
|
1278
|
+
} else {
|
|
1279
|
+
output.push("\n✓ No issues detected");
|
|
1280
|
+
}
|
|
1281
|
+
return { content: [{ type: "text", text: output.join("\n") }] };
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
case "data_size": {
|
|
1285
|
+
const { value, unit = "B" } = args;
|
|
1286
|
+
const units = {
|
|
1287
|
+
B: 1, KB: 1e3, MB: 1e6, GB: 1e9, TB: 1e12, PB: 1e15,
|
|
1288
|
+
KiB: 1024, MiB: 1048576, GiB: 1073741824, TiB: 1099511627776, PiB: 1125899906842624
|
|
1289
|
+
};
|
|
1290
|
+
if (!units[unit]) throw new Error(`Unknown unit: ${unit}. Use: ${Object.keys(units).join(", ")}`);
|
|
1291
|
+
const bytes = value * units[unit];
|
|
1292
|
+
const fmt = (n) => n < 0.01 ? n.toExponential(2) : (n % 1 === 0 ? n.toString() : n.toFixed(2));
|
|
1293
|
+
const output = [
|
|
1294
|
+
`Input: ${value} ${unit} = ${fmt(bytes)} bytes`,
|
|
1295
|
+
"",
|
|
1296
|
+
"Decimal (SI):",
|
|
1297
|
+
` ${fmt(bytes)} B`,
|
|
1298
|
+
` ${fmt(bytes / 1e3)} KB`,
|
|
1299
|
+
` ${fmt(bytes / 1e6)} MB`,
|
|
1300
|
+
` ${fmt(bytes / 1e9)} GB`,
|
|
1301
|
+
` ${fmt(bytes / 1e12)} TB`,
|
|
1302
|
+
` ${fmt(bytes / 1e15)} PB`,
|
|
1303
|
+
"",
|
|
1304
|
+
"Binary (IEC):",
|
|
1305
|
+
` ${fmt(bytes)} B`,
|
|
1306
|
+
` ${fmt(bytes / 1024)} KiB`,
|
|
1307
|
+
` ${fmt(bytes / 1048576)} MiB`,
|
|
1308
|
+
` ${fmt(bytes / 1073741824)} GiB`,
|
|
1309
|
+
` ${fmt(bytes / 1099511627776)} TiB`,
|
|
1310
|
+
` ${fmt(bytes / 1125899906842624)} PiB`,
|
|
1311
|
+
];
|
|
1312
|
+
return { content: [{ type: "text", text: output.join("\n") }] };
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
case "string_escape": {
|
|
1316
|
+
const { text, format, action = "escape" } = args;
|
|
1317
|
+
let result;
|
|
1318
|
+
if (action === "escape") {
|
|
1319
|
+
switch (format) {
|
|
1320
|
+
case "json":
|
|
1321
|
+
result = JSON.stringify(text).slice(1, -1);
|
|
1322
|
+
break;
|
|
1323
|
+
case "csv":
|
|
1324
|
+
result = text.includes(",") || text.includes('"') || text.includes("\n")
|
|
1325
|
+
? '"' + text.replace(/"/g, '""') + '"'
|
|
1326
|
+
: text;
|
|
1327
|
+
break;
|
|
1328
|
+
case "regex":
|
|
1329
|
+
result = text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1330
|
+
break;
|
|
1331
|
+
case "sql":
|
|
1332
|
+
result = text.replace(/'/g, "''");
|
|
1333
|
+
break;
|
|
1334
|
+
case "shell":
|
|
1335
|
+
result = "'" + text.replace(/'/g, "'\\''") + "'";
|
|
1336
|
+
break;
|
|
1337
|
+
default:
|
|
1338
|
+
throw new Error(`Unknown format: ${format}`);
|
|
1339
|
+
}
|
|
1340
|
+
} else {
|
|
1341
|
+
switch (format) {
|
|
1342
|
+
case "json":
|
|
1343
|
+
result = JSON.parse(`"${text}"`);
|
|
1344
|
+
break;
|
|
1345
|
+
case "csv":
|
|
1346
|
+
result = text.startsWith('"') && text.endsWith('"')
|
|
1347
|
+
? text.slice(1, -1).replace(/""/g, '"')
|
|
1348
|
+
: text;
|
|
1349
|
+
break;
|
|
1350
|
+
case "regex":
|
|
1351
|
+
result = text.replace(/\\([.*+?^${}()|[\]\\])/g, "$1");
|
|
1352
|
+
break;
|
|
1353
|
+
case "sql":
|
|
1354
|
+
result = text.replace(/''/g, "'");
|
|
1355
|
+
break;
|
|
1356
|
+
case "shell":
|
|
1357
|
+
result = text.startsWith("'") && text.endsWith("'")
|
|
1358
|
+
? text.slice(1, -1).replace(/'\\''/g, "'")
|
|
1359
|
+
: text;
|
|
1360
|
+
break;
|
|
1361
|
+
default:
|
|
1362
|
+
throw new Error(`Unknown format: ${format}`);
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
return { content: [{ type: "text", text: result }] };
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
case "nanoid": {
|
|
1369
|
+
const len = Math.min(Math.max(args.length || 21, 1), 128);
|
|
1370
|
+
const alphabet = args.alphabet || "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-";
|
|
1371
|
+
const count = Math.min(Math.max(args.count || 1, 1), 10);
|
|
1372
|
+
const ids = [];
|
|
1373
|
+
for (let i = 0; i < count; i++) {
|
|
1374
|
+
const bytes = crypto.randomBytes(len);
|
|
1375
|
+
let id = "";
|
|
1376
|
+
for (let j = 0; j < len; j++) {
|
|
1377
|
+
id += alphabet[bytes[j] % alphabet.length];
|
|
1378
|
+
}
|
|
1379
|
+
ids.push(id);
|
|
1380
|
+
}
|
|
1381
|
+
return { content: [{ type: "text", text: ids.join("\n") }] };
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
case "csv_json": {
|
|
1385
|
+
const { input, direction, delimiter = "," } = args;
|
|
1386
|
+
if (direction === "csv_to_json") {
|
|
1387
|
+
const lines = input.split("\n").filter(l => l.trim());
|
|
1388
|
+
if (lines.length < 1) throw new Error("CSV must have at least a header row");
|
|
1389
|
+
const headers = lines[0].split(delimiter).map(h => h.trim().replace(/^"|"$/g, ""));
|
|
1390
|
+
const rows = lines.slice(1).map(line => {
|
|
1391
|
+
const vals = line.split(delimiter).map(v => v.trim().replace(/^"|"$/g, ""));
|
|
1392
|
+
const obj = {};
|
|
1393
|
+
headers.forEach((h, i) => { obj[h] = vals[i] || ""; });
|
|
1394
|
+
return obj;
|
|
1395
|
+
});
|
|
1396
|
+
return { content: [{ type: "text", text: JSON.stringify(rows, null, 2) }] };
|
|
1397
|
+
} else {
|
|
1398
|
+
const arr = JSON.parse(input);
|
|
1399
|
+
if (!Array.isArray(arr) || arr.length === 0) throw new Error("Input must be a non-empty JSON array");
|
|
1400
|
+
const headers = Object.keys(arr[0]);
|
|
1401
|
+
const csvLines = [headers.join(delimiter)];
|
|
1402
|
+
for (const row of arr) {
|
|
1403
|
+
csvLines.push(headers.map(h => {
|
|
1404
|
+
const val = String(row[h] ?? "");
|
|
1405
|
+
return val.includes(delimiter) || val.includes('"') || val.includes("\n")
|
|
1406
|
+
? `"${val.replace(/"/g, '""')}"` : val;
|
|
1407
|
+
}).join(delimiter));
|
|
1408
|
+
}
|
|
1409
|
+
return { content: [{ type: "text", text: csvLines.join("\n") }] };
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
case "hex_encode": {
|
|
1414
|
+
const action = args.action || "encode";
|
|
1415
|
+
if (action === "encode") {
|
|
1416
|
+
return { content: [{ type: "text", text: Buffer.from(args.text, "utf-8").toString("hex") }] };
|
|
1417
|
+
} else {
|
|
1418
|
+
const hex = args.text.replace(/\s/g, "");
|
|
1419
|
+
return { content: [{ type: "text", text: Buffer.from(hex, "hex").toString("utf-8") }] };
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
case "char_info": {
|
|
1424
|
+
const chars = [...args.text].slice(0, 20);
|
|
1425
|
+
const info = chars.map(ch => {
|
|
1426
|
+
const cp = ch.codePointAt(0);
|
|
1427
|
+
const hex = cp.toString(16).toUpperCase().padStart(4, "0");
|
|
1428
|
+
const utf8Bytes = Buffer.from(ch, "utf-8");
|
|
1429
|
+
const htmlEntity = cp < 128 ? `&#${cp};` : `&#x${hex};`;
|
|
1430
|
+
return `'${ch}' U+${hex} decimal: ${cp} UTF-8: ${[...utf8Bytes].map(b => b.toString(16).padStart(2, "0")).join(" ")} HTML: ${htmlEntity}`;
|
|
1431
|
+
});
|
|
1432
|
+
return { content: [{ type: "text", text: info.join("\n") }] };
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
case "byte_count": {
|
|
1436
|
+
const text = args.text;
|
|
1437
|
+
const utf8 = Buffer.byteLength(text, "utf-8");
|
|
1438
|
+
const utf16 = Buffer.byteLength(text, "utf-16le");
|
|
1439
|
+
const ascii = text.length; // JS string length
|
|
1440
|
+
const chars = [...text].length; // actual character count (handles surrogate pairs)
|
|
1441
|
+
return {
|
|
1442
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
1443
|
+
characters: chars,
|
|
1444
|
+
js_length: text.length,
|
|
1445
|
+
utf8_bytes: utf8,
|
|
1446
|
+
utf16_bytes: utf16,
|
|
1447
|
+
ascii_bytes: ascii
|
|
1448
|
+
}, null, 2) }]
|
|
1449
|
+
};
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1035
1452
|
default:
|
|
1036
1453
|
throw new Error(`Unknown tool: ${name}`);
|
|
1037
1454
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-devutils",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "MCP server with
|
|
3
|
+
"version": "1.5.0",
|
|
4
|
+
"description": "MCP server with 35 developer utilities - UUID, nanoid, hash, HMAC, base64, hex encode, timestamps, JWT decode, random strings, URL encode/decode, JSON format, CSV/JSON convert, regex test, cron explain, color convert, semver compare, HTTP status codes, slugify, HTML escape, chmod calculator, text diff, number base converter, lorem ipsum, word count, byte count, CIDR calculator, case converter, markdown TOC, env parser, IP info, password strength, data size converter, string escape, char info",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"bin": {
|
|
@@ -36,7 +36,17 @@
|
|
|
36
36
|
"word-count",
|
|
37
37
|
"cidr",
|
|
38
38
|
"case-convert",
|
|
39
|
-
"markdown-toc"
|
|
39
|
+
"markdown-toc",
|
|
40
|
+
"env-parser",
|
|
41
|
+
"ip-address",
|
|
42
|
+
"password-strength",
|
|
43
|
+
"data-size",
|
|
44
|
+
"string-escape",
|
|
45
|
+
"nanoid",
|
|
46
|
+
"csv-json",
|
|
47
|
+
"hex-encode",
|
|
48
|
+
"unicode",
|
|
49
|
+
"byte-count"
|
|
40
50
|
],
|
|
41
51
|
"author": "Hong Teoh",
|
|
42
52
|
"license": "MIT",
|