mcp-devutils 1.0.0 → 1.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.md +26 -0
- package/index.js +252 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -15,6 +15,10 @@ An MCP (Model Context Protocol) server with developer utilities. Use it directly
|
|
|
15
15
|
| `url_encode` | URL encode or decode strings |
|
|
16
16
|
| `json_format` | Pretty-print or minify JSON |
|
|
17
17
|
| `regex_test` | Test regex patterns against strings |
|
|
18
|
+
| `cron_explain` | Explain cron expressions in plain English + next 5 runs |
|
|
19
|
+
| `hmac` | Generate HMAC signatures (SHA-256, SHA-512, etc.) |
|
|
20
|
+
| `color_convert` | Convert colors between hex, RGB, and HSL |
|
|
21
|
+
| `semver_compare` | Compare two semantic versions |
|
|
18
22
|
|
|
19
23
|
## Installation
|
|
20
24
|
|
|
@@ -124,6 +128,28 @@ Test regex patterns:
|
|
|
124
128
|
- `text`: String to test against (required)
|
|
125
129
|
- `flags`: Regex flags like `g`, `i`, `gi` (optional)
|
|
126
130
|
|
|
131
|
+
### cron_explain
|
|
132
|
+
Explain a cron expression in plain English and show the next 5 scheduled runs:
|
|
133
|
+
- `expression`: 5-field cron expression (required), e.g. `*/15 9-17 * * 1-5`
|
|
134
|
+
|
|
135
|
+
### hmac
|
|
136
|
+
Generate an HMAC signature:
|
|
137
|
+
- `message`: Message to sign (required)
|
|
138
|
+
- `key`: Secret key (required)
|
|
139
|
+
- `algorithm`: `sha256`, `sha512`, `sha1`, or `md5` (default: sha256)
|
|
140
|
+
- `encoding`: `hex` or `base64` (default: hex)
|
|
141
|
+
|
|
142
|
+
### color_convert
|
|
143
|
+
Convert colors between formats:
|
|
144
|
+
- `color`: Color string (required) — accepts `#ff5733`, `rgb(255,87,51)`, or `hsl(11,100%,60%)`
|
|
145
|
+
- Returns all three formats
|
|
146
|
+
|
|
147
|
+
### semver_compare
|
|
148
|
+
Compare two semantic versions:
|
|
149
|
+
- `version1`: First version (required), e.g. `1.2.3`
|
|
150
|
+
- `version2`: Second version (required), e.g. `2.0.0`
|
|
151
|
+
- Returns comparison result and parsed components
|
|
152
|
+
|
|
127
153
|
## License
|
|
128
154
|
|
|
129
155
|
MIT — [Hong Teoh](https://github.com/hlteoh37)
|
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.1.0" },
|
|
9
9
|
{ capabilities: { tools: {} } }
|
|
10
10
|
);
|
|
11
11
|
|
|
@@ -141,6 +141,62 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
141
141
|
},
|
|
142
142
|
required: ["pattern", "text"]
|
|
143
143
|
}
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
name: "cron_explain",
|
|
147
|
+
description: "Explain a cron expression in plain English and show the next 5 run times",
|
|
148
|
+
inputSchema: {
|
|
149
|
+
type: "object",
|
|
150
|
+
properties: {
|
|
151
|
+
expression: { type: "string", description: "Cron expression (5 fields: minute hour day month weekday)" }
|
|
152
|
+
},
|
|
153
|
+
required: ["expression"]
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
name: "hmac",
|
|
158
|
+
description: "Generate an HMAC signature for a message",
|
|
159
|
+
inputSchema: {
|
|
160
|
+
type: "object",
|
|
161
|
+
properties: {
|
|
162
|
+
message: { type: "string", description: "Message to sign" },
|
|
163
|
+
key: { type: "string", description: "Secret key" },
|
|
164
|
+
algorithm: {
|
|
165
|
+
type: "string",
|
|
166
|
+
enum: ["sha256", "sha512", "sha1", "md5"],
|
|
167
|
+
description: "Hash algorithm (default: sha256)"
|
|
168
|
+
},
|
|
169
|
+
encoding: {
|
|
170
|
+
type: "string",
|
|
171
|
+
enum: ["hex", "base64"],
|
|
172
|
+
description: "Output encoding (default: hex)"
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
required: ["message", "key"]
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
name: "color_convert",
|
|
180
|
+
description: "Convert colors between hex, RGB, and HSL formats",
|
|
181
|
+
inputSchema: {
|
|
182
|
+
type: "object",
|
|
183
|
+
properties: {
|
|
184
|
+
color: { type: "string", description: "Color value (e.g. '#ff5733', 'rgb(255,87,51)', 'hsl(11,100%,60%)')" }
|
|
185
|
+
},
|
|
186
|
+
required: ["color"]
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
name: "semver_compare",
|
|
191
|
+
description: "Compare two semantic versions or check if a version satisfies a range",
|
|
192
|
+
inputSchema: {
|
|
193
|
+
type: "object",
|
|
194
|
+
properties: {
|
|
195
|
+
version1: { type: "string", description: "First version (e.g. '1.2.3')" },
|
|
196
|
+
version2: { type: "string", description: "Second version to compare against (e.g. '1.3.0')" }
|
|
197
|
+
},
|
|
198
|
+
required: ["version1", "version2"]
|
|
199
|
+
}
|
|
144
200
|
}
|
|
145
201
|
]
|
|
146
202
|
};
|
|
@@ -327,6 +383,201 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
327
383
|
};
|
|
328
384
|
}
|
|
329
385
|
|
|
386
|
+
case "cron_explain": {
|
|
387
|
+
const { expression } = args;
|
|
388
|
+
const parts = expression.trim().split(/\s+/);
|
|
389
|
+
if (parts.length !== 5) {
|
|
390
|
+
throw new Error("Cron expression must have 5 fields: minute hour day month weekday");
|
|
391
|
+
}
|
|
392
|
+
const [minute, hour, day, month, weekday] = parts;
|
|
393
|
+
const WEEKDAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
|
394
|
+
const MONTHS = ["", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
|
395
|
+
|
|
396
|
+
function describeField(val, fieldName) {
|
|
397
|
+
if (val === "*") return `every ${fieldName}`;
|
|
398
|
+
if (val.includes("/")) {
|
|
399
|
+
const [base, step] = val.split("/");
|
|
400
|
+
return base === "*" ? `every ${step} ${fieldName}s` : `every ${step} ${fieldName}s starting at ${base}`;
|
|
401
|
+
}
|
|
402
|
+
if (val.includes(",")) return `${fieldName}s ${val}`;
|
|
403
|
+
if (val.includes("-")) return `${fieldName}s ${val.split("-")[0]} through ${val.split("-")[1]}`;
|
|
404
|
+
return `${fieldName} ${val}`;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const explanation = [];
|
|
408
|
+
explanation.push(`Expression: ${expression}`);
|
|
409
|
+
explanation.push("");
|
|
410
|
+
explanation.push("Schedule:");
|
|
411
|
+
explanation.push(` Minute: ${describeField(minute, "minute")}`);
|
|
412
|
+
explanation.push(` Hour: ${describeField(hour, "hour")}`);
|
|
413
|
+
explanation.push(` Day: ${describeField(day, "day")}`);
|
|
414
|
+
explanation.push(` Month: ${describeField(month, "month")}`);
|
|
415
|
+
if (weekday !== "*") {
|
|
416
|
+
const wdNames = weekday.split(",").map(w => WEEKDAYS[parseInt(w)] || w).join(", ");
|
|
417
|
+
explanation.push(` Weekday: ${wdNames}`);
|
|
418
|
+
} else {
|
|
419
|
+
explanation.push(` Weekday: every day of the week`);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Compute next 5 run times
|
|
423
|
+
explanation.push("");
|
|
424
|
+
explanation.push("Next 5 runs:");
|
|
425
|
+
function matchesCron(date, parts) {
|
|
426
|
+
const [m, h, d, mo, wd] = parts;
|
|
427
|
+
function matches(val, actual, max) {
|
|
428
|
+
if (val === "*") return true;
|
|
429
|
+
if (val.includes("/")) {
|
|
430
|
+
const [base, step] = val.split("/");
|
|
431
|
+
const start = base === "*" ? 0 : parseInt(base);
|
|
432
|
+
return (actual - start) % parseInt(step) === 0 && actual >= start;
|
|
433
|
+
}
|
|
434
|
+
if (val.includes(",")) return val.split(",").map(Number).includes(actual);
|
|
435
|
+
if (val.includes("-")) {
|
|
436
|
+
const [lo, hi] = val.split("-").map(Number);
|
|
437
|
+
return actual >= lo && actual <= hi;
|
|
438
|
+
}
|
|
439
|
+
return parseInt(val) === actual;
|
|
440
|
+
}
|
|
441
|
+
return matches(m, date.getMinutes()) &&
|
|
442
|
+
matches(h, date.getHours()) &&
|
|
443
|
+
matches(d, date.getDate()) &&
|
|
444
|
+
matches(mo, date.getMonth() + 1) &&
|
|
445
|
+
matches(wd, date.getDay());
|
|
446
|
+
}
|
|
447
|
+
const now = new Date();
|
|
448
|
+
let cursor = new Date(now);
|
|
449
|
+
cursor.setSeconds(0, 0);
|
|
450
|
+
cursor.setMinutes(cursor.getMinutes() + 1);
|
|
451
|
+
let found = 0;
|
|
452
|
+
const limit = 525960; // max 1 year of minutes
|
|
453
|
+
for (let i = 0; i < limit && found < 5; i++) {
|
|
454
|
+
if (matchesCron(cursor, parts)) {
|
|
455
|
+
explanation.push(` ${cursor.toISOString()}`);
|
|
456
|
+
found++;
|
|
457
|
+
}
|
|
458
|
+
cursor.setMinutes(cursor.getMinutes() + 1);
|
|
459
|
+
}
|
|
460
|
+
if (found === 0) explanation.push(" (no runs found in next year)");
|
|
461
|
+
|
|
462
|
+
return {
|
|
463
|
+
content: [{ type: "text", text: explanation.join("\n") }]
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
case "hmac": {
|
|
468
|
+
const { message, key, algorithm = "sha256", encoding = "hex" } = args;
|
|
469
|
+
const hmac = crypto.createHmac(algorithm, key).update(message, "utf8").digest(encoding);
|
|
470
|
+
return {
|
|
471
|
+
content: [{ type: "text", text: `HMAC-${algorithm.toUpperCase()} (${encoding}): ${hmac}` }]
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
case "color_convert": {
|
|
476
|
+
const { color } = args;
|
|
477
|
+
let r, g, b;
|
|
478
|
+
const hexMatch = color.match(/^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/);
|
|
479
|
+
const rgbMatch = color.match(/^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i);
|
|
480
|
+
const hslMatch = color.match(/^hsl\s*\(\s*(\d+)\s*,\s*(\d+)%?\s*,\s*(\d+)%?\s*\)$/i);
|
|
481
|
+
|
|
482
|
+
if (hexMatch) {
|
|
483
|
+
let hex = hexMatch[1];
|
|
484
|
+
if (hex.length === 3) hex = hex.split("").map(c => c + c).join("");
|
|
485
|
+
r = parseInt(hex.slice(0, 2), 16);
|
|
486
|
+
g = parseInt(hex.slice(2, 4), 16);
|
|
487
|
+
b = parseInt(hex.slice(4, 6), 16);
|
|
488
|
+
} else if (rgbMatch) {
|
|
489
|
+
r = parseInt(rgbMatch[1]);
|
|
490
|
+
g = parseInt(rgbMatch[2]);
|
|
491
|
+
b = parseInt(rgbMatch[3]);
|
|
492
|
+
} else if (hslMatch) {
|
|
493
|
+
const h = parseInt(hslMatch[1]) / 360;
|
|
494
|
+
const s = parseInt(hslMatch[2]) / 100;
|
|
495
|
+
const l = parseInt(hslMatch[3]) / 100;
|
|
496
|
+
if (s === 0) {
|
|
497
|
+
r = g = b = Math.round(l * 255);
|
|
498
|
+
} else {
|
|
499
|
+
const hue2rgb = (p, q, t) => {
|
|
500
|
+
if (t < 0) t += 1;
|
|
501
|
+
if (t > 1) t -= 1;
|
|
502
|
+
if (t < 1/6) return p + (q - p) * 6 * t;
|
|
503
|
+
if (t < 1/2) return q;
|
|
504
|
+
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
|
|
505
|
+
return p;
|
|
506
|
+
};
|
|
507
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
508
|
+
const p = 2 * l - q;
|
|
509
|
+
r = Math.round(hue2rgb(p, q, h + 1/3) * 255);
|
|
510
|
+
g = Math.round(hue2rgb(p, q, h) * 255);
|
|
511
|
+
b = Math.round(hue2rgb(p, q, h - 1/3) * 255);
|
|
512
|
+
}
|
|
513
|
+
} else {
|
|
514
|
+
throw new Error("Unrecognized color format. Use hex (#ff5733), rgb(255,87,51), or hsl(11,100%,60%)");
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// RGB to HSL
|
|
518
|
+
const rn = r / 255, gn = g / 255, bn = b / 255;
|
|
519
|
+
const max = Math.max(rn, gn, bn), min = Math.min(rn, gn, bn);
|
|
520
|
+
let h, s, l = (max + min) / 2;
|
|
521
|
+
if (max === min) {
|
|
522
|
+
h = s = 0;
|
|
523
|
+
} else {
|
|
524
|
+
const d = max - min;
|
|
525
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
526
|
+
switch (max) {
|
|
527
|
+
case rn: h = ((gn - bn) / d + (gn < bn ? 6 : 0)) / 6; break;
|
|
528
|
+
case gn: h = ((bn - rn) / d + 2) / 6; break;
|
|
529
|
+
case bn: h = ((rn - gn) / d + 4) / 6; break;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const hex = `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
|
|
534
|
+
const output = [
|
|
535
|
+
`Input: ${color}`,
|
|
536
|
+
`HEX: ${hex}`,
|
|
537
|
+
`RGB: rgb(${r}, ${g}, ${b})`,
|
|
538
|
+
`HSL: hsl(${Math.round(h * 360)}, ${Math.round(s * 100)}%, ${Math.round(l * 100)}%)`
|
|
539
|
+
];
|
|
540
|
+
return {
|
|
541
|
+
content: [{ type: "text", text: output.join("\n") }]
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
case "semver_compare": {
|
|
546
|
+
const { version1, version2 } = args;
|
|
547
|
+
function parse(v) {
|
|
548
|
+
const match = v.match(/^v?(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/);
|
|
549
|
+
if (!match) throw new Error(`Invalid semver: ${v}`);
|
|
550
|
+
return { major: parseInt(match[1]), minor: parseInt(match[2]), patch: parseInt(match[3]), pre: match[4] || null };
|
|
551
|
+
}
|
|
552
|
+
const v1 = parse(version1);
|
|
553
|
+
const v2 = parse(version2);
|
|
554
|
+
|
|
555
|
+
function compare(a, b) {
|
|
556
|
+
if (a.major !== b.major) return a.major - b.major;
|
|
557
|
+
if (a.minor !== b.minor) return a.minor - b.minor;
|
|
558
|
+
if (a.patch !== b.patch) return a.patch - b.patch;
|
|
559
|
+
if (a.pre && !b.pre) return -1;
|
|
560
|
+
if (!a.pre && b.pre) return 1;
|
|
561
|
+
if (a.pre && b.pre) return a.pre < b.pre ? -1 : a.pre > b.pre ? 1 : 0;
|
|
562
|
+
return 0;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const cmp = compare(v1, v2);
|
|
566
|
+
const relation = cmp < 0 ? "less than" : cmp > 0 ? "greater than" : "equal to";
|
|
567
|
+
const symbol = cmp < 0 ? "<" : cmp > 0 ? ">" : "=";
|
|
568
|
+
|
|
569
|
+
const output = [
|
|
570
|
+
`${version1} ${symbol} ${version2}`,
|
|
571
|
+
`${version1} is ${relation} ${version2}`,
|
|
572
|
+
"",
|
|
573
|
+
`v1: major=${v1.major} minor=${v1.minor} patch=${v1.patch}${v1.pre ? ` pre=${v1.pre}` : ""}`,
|
|
574
|
+
`v2: major=${v2.major} minor=${v2.minor} patch=${v2.patch}${v2.pre ? ` pre=${v2.pre}` : ""}`
|
|
575
|
+
];
|
|
576
|
+
return {
|
|
577
|
+
content: [{ type: "text", text: output.join("\n") }]
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
|
|
330
581
|
default:
|
|
331
582
|
throw new Error(`Unknown tool: ${name}`);
|
|
332
583
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-devutils",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "MCP server with developer utilities - UUID, hash, base64, timestamps, JWT decode, random strings, URL encode/decode, JSON format, regex test",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "MCP server with 13 developer utilities - UUID, hash, HMAC, base64, timestamps, JWT decode, random strings, URL encode/decode, JSON format, regex test, cron explain, color convert, semver compare",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"bin": {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"scripts": {
|
|
11
11
|
"start": "node index.js"
|
|
12
12
|
},
|
|
13
|
-
"keywords": ["mcp", "model-context-protocol", "developer-tools", "utilities", "uuid", "hash", "base64", "jwt", "claude", "cursor"],
|
|
13
|
+
"keywords": ["mcp", "model-context-protocol", "developer-tools", "utilities", "uuid", "hash", "hmac", "base64", "jwt", "claude", "cursor", "cron", "color-convert", "semver"],
|
|
14
14
|
"author": "Hong Teoh",
|
|
15
15
|
"license": "MIT",
|
|
16
16
|
"funding": {
|