mcp-devutils 1.2.0 → 1.3.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 +38 -0
- package/index.js +245 -1
- package/package.json +9 -3
package/README.md
CHANGED
|
@@ -24,6 +24,12 @@ An MCP (Model Context Protocol) server with developer utilities. Use it directly
|
|
|
24
24
|
| `escape_html` | Escape or unescape HTML entities |
|
|
25
25
|
| `chmod_calc` | Convert between numeric and symbolic Unix permissions |
|
|
26
26
|
| `diff` | Compare two texts and show differences line by line |
|
|
27
|
+
| `number_base` | Convert numbers between decimal, hex, octal, and binary |
|
|
28
|
+
| `lorem_ipsum` | Generate placeholder lorem ipsum text |
|
|
29
|
+
| `word_count` | Count characters, words, lines, and bytes in text |
|
|
30
|
+
| `cidr` | Parse CIDR notation — network, broadcast, host range |
|
|
31
|
+
| `case_convert` | Convert between camelCase, snake_case, PascalCase, kebab-case, CONSTANT_CASE, Title Case |
|
|
32
|
+
| `markdown_toc` | Generate table of contents from markdown headings |
|
|
27
33
|
|
|
28
34
|
## Installation
|
|
29
35
|
|
|
@@ -181,6 +187,38 @@ Compare two texts:
|
|
|
181
187
|
- `text2`: Modified text (required)
|
|
182
188
|
- Returns line-by-line diff with added/removed/unchanged summary
|
|
183
189
|
|
|
190
|
+
### number_base
|
|
191
|
+
Convert numbers between bases:
|
|
192
|
+
- `value`: Number string (required) — prefix `0x` for hex, `0o` for octal, `0b` for binary, or plain decimal
|
|
193
|
+
- Returns decimal, hex, octal, and binary representations
|
|
194
|
+
|
|
195
|
+
### lorem_ipsum
|
|
196
|
+
Generate placeholder text:
|
|
197
|
+
- `count`: Number of units (default: 1, max: 20)
|
|
198
|
+
- `unit`: `paragraphs`, `sentences`, or `words` (default: paragraphs)
|
|
199
|
+
|
|
200
|
+
### word_count
|
|
201
|
+
Analyze text:
|
|
202
|
+
- `text`: Input text (required)
|
|
203
|
+
- Returns character count, word count, line count, and byte size
|
|
204
|
+
|
|
205
|
+
### cidr
|
|
206
|
+
Parse CIDR notation:
|
|
207
|
+
- `notation`: CIDR string (required), e.g. `192.168.1.0/24`
|
|
208
|
+
- Returns network, netmask, broadcast, host range, total hosts
|
|
209
|
+
|
|
210
|
+
### case_convert
|
|
211
|
+
Convert between naming conventions:
|
|
212
|
+
- `text`: Input text (required), e.g. `myVariableName` or `my-variable-name`
|
|
213
|
+
- `to`: Target case (required) — `camel`, `snake`, `pascal`, `kebab`, `constant`, or `title`
|
|
214
|
+
- Returns converted text plus all format variants
|
|
215
|
+
|
|
216
|
+
### markdown_toc
|
|
217
|
+
Generate a table of contents:
|
|
218
|
+
- `markdown`: Markdown text (required)
|
|
219
|
+
- `max_depth`: Maximum heading level to include (default: 3)
|
|
220
|
+
- Returns formatted TOC with anchor links
|
|
221
|
+
|
|
184
222
|
## See Also
|
|
185
223
|
|
|
186
224
|
- [mcp-apitools](https://www.npmjs.com/package/mcp-apitools) — 8 API & web dev utilities: HTTP status codes, MIME types, JWT creation, mock data, CORS headers, cookie parsing
|
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.3.0" },
|
|
9
9
|
{ capabilities: { tools: {} } }
|
|
10
10
|
);
|
|
11
11
|
|
|
@@ -259,6 +259,82 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
259
259
|
},
|
|
260
260
|
required: ["text1", "text2"]
|
|
261
261
|
}
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
name: "number_base",
|
|
265
|
+
description: "Convert numbers between decimal, hexadecimal, octal, and binary",
|
|
266
|
+
inputSchema: {
|
|
267
|
+
type: "object",
|
|
268
|
+
properties: {
|
|
269
|
+
value: { type: "string", description: "Number to convert (prefix with 0x for hex, 0o for octal, 0b for binary, or plain decimal)" }
|
|
270
|
+
},
|
|
271
|
+
required: ["value"]
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
name: "lorem_ipsum",
|
|
276
|
+
description: "Generate placeholder lorem ipsum text",
|
|
277
|
+
inputSchema: {
|
|
278
|
+
type: "object",
|
|
279
|
+
properties: {
|
|
280
|
+
count: { type: "number", description: "Number of units to generate (default: 1)" },
|
|
281
|
+
unit: {
|
|
282
|
+
type: "string",
|
|
283
|
+
enum: ["paragraphs", "sentences", "words"],
|
|
284
|
+
description: "Unit type (default: paragraphs)"
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
name: "word_count",
|
|
291
|
+
description: "Count characters, words, lines, and bytes in text",
|
|
292
|
+
inputSchema: {
|
|
293
|
+
type: "object",
|
|
294
|
+
properties: {
|
|
295
|
+
text: { type: "string", description: "Text to analyze" }
|
|
296
|
+
},
|
|
297
|
+
required: ["text"]
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
name: "cidr",
|
|
302
|
+
description: "Parse CIDR notation and show network address, broadcast, host range, and number of hosts",
|
|
303
|
+
inputSchema: {
|
|
304
|
+
type: "object",
|
|
305
|
+
properties: {
|
|
306
|
+
notation: { type: "string", description: "CIDR notation (e.g. '192.168.1.0/24')" }
|
|
307
|
+
},
|
|
308
|
+
required: ["notation"]
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
name: "case_convert",
|
|
313
|
+
description: "Convert text between camelCase, snake_case, PascalCase, kebab-case, CONSTANT_CASE, and Title Case",
|
|
314
|
+
inputSchema: {
|
|
315
|
+
type: "object",
|
|
316
|
+
properties: {
|
|
317
|
+
text: { type: "string", description: "Text to convert (e.g. 'myVariableName' or 'my-variable-name')" },
|
|
318
|
+
to: {
|
|
319
|
+
type: "string",
|
|
320
|
+
enum: ["camel", "snake", "pascal", "kebab", "constant", "title"],
|
|
321
|
+
description: "Target case format"
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
required: ["text", "to"]
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
name: "markdown_toc",
|
|
329
|
+
description: "Generate a table of contents from markdown headings",
|
|
330
|
+
inputSchema: {
|
|
331
|
+
type: "object",
|
|
332
|
+
properties: {
|
|
333
|
+
markdown: { type: "string", description: "Markdown text to extract headings from" },
|
|
334
|
+
max_depth: { type: "number", description: "Maximum heading depth to include (default: 3)" }
|
|
335
|
+
},
|
|
336
|
+
required: ["markdown"]
|
|
337
|
+
}
|
|
262
338
|
}
|
|
263
339
|
]
|
|
264
340
|
};
|
|
@@ -788,6 +864,174 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
788
864
|
return { content: [{ type: "text", text: output.join("\n") + summary }] };
|
|
789
865
|
}
|
|
790
866
|
|
|
867
|
+
case "number_base": {
|
|
868
|
+
const { value } = args;
|
|
869
|
+
let num;
|
|
870
|
+
const v = value.trim();
|
|
871
|
+
if (v.startsWith("0x") || v.startsWith("0X")) num = parseInt(v, 16);
|
|
872
|
+
else if (v.startsWith("0o") || v.startsWith("0O")) num = parseInt(v.slice(2), 8);
|
|
873
|
+
else if (v.startsWith("0b") || v.startsWith("0B")) num = parseInt(v.slice(2), 2);
|
|
874
|
+
else num = parseInt(v, 10);
|
|
875
|
+
if (isNaN(num)) throw new Error(`Invalid number: ${value}`);
|
|
876
|
+
const output = [
|
|
877
|
+
`Decimal: ${num}`,
|
|
878
|
+
`Hexadecimal: 0x${num.toString(16).toUpperCase()}`,
|
|
879
|
+
`Octal: 0o${num.toString(8)}`,
|
|
880
|
+
`Binary: 0b${num.toString(2)}`
|
|
881
|
+
];
|
|
882
|
+
return { content: [{ type: "text", text: output.join("\n") }] };
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
case "lorem_ipsum": {
|
|
886
|
+
const { count = 1, unit = "paragraphs" } = args || {};
|
|
887
|
+
const sentences = [
|
|
888
|
+
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
|
|
889
|
+
"Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
|
|
890
|
+
"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.",
|
|
891
|
+
"Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore.",
|
|
892
|
+
"Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia.",
|
|
893
|
+
"Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit.",
|
|
894
|
+
"Neque porro quisquam est qui dolorem ipsum quia dolor sit amet.",
|
|
895
|
+
"Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit.",
|
|
896
|
+
"Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse.",
|
|
897
|
+
"At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis."
|
|
898
|
+
];
|
|
899
|
+
const n = Math.min(Math.max(1, count), 20);
|
|
900
|
+
let result;
|
|
901
|
+
if (unit === "words") {
|
|
902
|
+
const allWords = sentences.join(" ").split(/\s+/);
|
|
903
|
+
const words = [];
|
|
904
|
+
for (let i = 0; i < n; i++) words.push(allWords[i % allWords.length]);
|
|
905
|
+
result = words.join(" ");
|
|
906
|
+
} else if (unit === "sentences") {
|
|
907
|
+
const out = [];
|
|
908
|
+
for (let i = 0; i < n; i++) out.push(sentences[i % sentences.length]);
|
|
909
|
+
result = out.join(" ");
|
|
910
|
+
} else {
|
|
911
|
+
const paras = [];
|
|
912
|
+
for (let i = 0; i < n; i++) {
|
|
913
|
+
const start = (i * 3) % sentences.length;
|
|
914
|
+
const para = [];
|
|
915
|
+
for (let j = 0; j < 5; j++) para.push(sentences[(start + j) % sentences.length]);
|
|
916
|
+
paras.push(para.join(" "));
|
|
917
|
+
}
|
|
918
|
+
result = paras.join("\n\n");
|
|
919
|
+
}
|
|
920
|
+
return { content: [{ type: "text", text: result }] };
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
case "word_count": {
|
|
924
|
+
const { text } = args;
|
|
925
|
+
const chars = text.length;
|
|
926
|
+
const charsNoSpaces = text.replace(/\s/g, "").length;
|
|
927
|
+
const words = text.trim() === "" ? 0 : text.trim().split(/\s+/).length;
|
|
928
|
+
const lines = text.split("\n").length;
|
|
929
|
+
const bytes = Buffer.byteLength(text, "utf8");
|
|
930
|
+
const output = [
|
|
931
|
+
`Characters: ${chars}`,
|
|
932
|
+
`Characters (no spaces): ${charsNoSpaces}`,
|
|
933
|
+
`Words: ${words}`,
|
|
934
|
+
`Lines: ${lines}`,
|
|
935
|
+
`Bytes (UTF-8): ${bytes}`
|
|
936
|
+
];
|
|
937
|
+
return { content: [{ type: "text", text: output.join("\n") }] };
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
case "cidr": {
|
|
941
|
+
const { notation } = args;
|
|
942
|
+
const match = notation.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)\/(\d+)$/);
|
|
943
|
+
if (!match) throw new Error("Invalid CIDR notation. Use format: 192.168.1.0/24");
|
|
944
|
+
const octets = [parseInt(match[1]), parseInt(match[2]), parseInt(match[3]), parseInt(match[4])];
|
|
945
|
+
const prefix = parseInt(match[5]);
|
|
946
|
+
if (octets.some(o => o < 0 || o > 255) || prefix < 0 || prefix > 32) {
|
|
947
|
+
throw new Error("Invalid IP address or prefix length");
|
|
948
|
+
}
|
|
949
|
+
const ip = ((octets[0] << 24) | (octets[1] << 16) | (octets[2] << 8) | octets[3]) >>> 0;
|
|
950
|
+
const mask = prefix === 0 ? 0 : (~0 << (32 - prefix)) >>> 0;
|
|
951
|
+
const network = (ip & mask) >>> 0;
|
|
952
|
+
const broadcast = (network | ~mask) >>> 0;
|
|
953
|
+
const firstHost = prefix >= 31 ? network : (network + 1) >>> 0;
|
|
954
|
+
const lastHost = prefix >= 31 ? broadcast : (broadcast - 1) >>> 0;
|
|
955
|
+
const hostCount = prefix >= 31 ? (prefix === 32 ? 1 : 2) : Math.pow(2, 32 - prefix) - 2;
|
|
956
|
+
const toIP = (n) => `${(n >>> 24) & 255}.${(n >>> 16) & 255}.${(n >>> 8) & 255}.${n & 255}`;
|
|
957
|
+
const output = [
|
|
958
|
+
`CIDR: ${notation}`,
|
|
959
|
+
`Network: ${toIP(network)}`,
|
|
960
|
+
`Netmask: ${toIP(mask)}`,
|
|
961
|
+
`Broadcast: ${toIP(broadcast)}`,
|
|
962
|
+
`First host: ${toIP(firstHost)}`,
|
|
963
|
+
`Last host: ${toIP(lastHost)}`,
|
|
964
|
+
`Total hosts: ${hostCount}`,
|
|
965
|
+
`Prefix length: /${prefix}`
|
|
966
|
+
];
|
|
967
|
+
return { content: [{ type: "text", text: output.join("\n") }] };
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
case "case_convert": {
|
|
971
|
+
const { text, to } = args;
|
|
972
|
+
// Split input into words regardless of input format
|
|
973
|
+
const words = text
|
|
974
|
+
.replace(/([a-z])([A-Z])/g, "$1 $2") // camelCase splits
|
|
975
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2") // ABCDef -> ABC Def
|
|
976
|
+
.replace(/[-_]/g, " ")
|
|
977
|
+
.split(/\s+/)
|
|
978
|
+
.filter(w => w.length > 0)
|
|
979
|
+
.map(w => w.toLowerCase());
|
|
980
|
+
let result;
|
|
981
|
+
switch (to) {
|
|
982
|
+
case "camel":
|
|
983
|
+
result = words[0] + words.slice(1).map(w => w[0].toUpperCase() + w.slice(1)).join("");
|
|
984
|
+
break;
|
|
985
|
+
case "pascal":
|
|
986
|
+
result = words.map(w => w[0].toUpperCase() + w.slice(1)).join("");
|
|
987
|
+
break;
|
|
988
|
+
case "snake":
|
|
989
|
+
result = words.join("_");
|
|
990
|
+
break;
|
|
991
|
+
case "kebab":
|
|
992
|
+
result = words.join("-");
|
|
993
|
+
break;
|
|
994
|
+
case "constant":
|
|
995
|
+
result = words.map(w => w.toUpperCase()).join("_");
|
|
996
|
+
break;
|
|
997
|
+
case "title":
|
|
998
|
+
result = words.map(w => w[0].toUpperCase() + w.slice(1)).join(" ");
|
|
999
|
+
break;
|
|
1000
|
+
default:
|
|
1001
|
+
throw new Error(`Unknown case: ${to}`);
|
|
1002
|
+
}
|
|
1003
|
+
const all = {
|
|
1004
|
+
camelCase: words[0] + words.slice(1).map(w => w[0].toUpperCase() + w.slice(1)).join(""),
|
|
1005
|
+
PascalCase: words.map(w => w[0].toUpperCase() + w.slice(1)).join(""),
|
|
1006
|
+
snake_case: words.join("_"),
|
|
1007
|
+
"kebab-case": words.join("-"),
|
|
1008
|
+
CONSTANT_CASE: words.map(w => w.toUpperCase()).join("_"),
|
|
1009
|
+
"Title Case": words.map(w => w[0].toUpperCase() + w.slice(1)).join(" ")
|
|
1010
|
+
};
|
|
1011
|
+
const output = [`Result: ${result}`, "", "All formats:"];
|
|
1012
|
+
for (const [k, v] of Object.entries(all)) output.push(` ${k}: ${v}`);
|
|
1013
|
+
return { content: [{ type: "text", text: output.join("\n") }] };
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
case "markdown_toc": {
|
|
1017
|
+
const { markdown, max_depth = 3 } = args;
|
|
1018
|
+
const lines = markdown.split("\n");
|
|
1019
|
+
const toc = [];
|
|
1020
|
+
for (const line of lines) {
|
|
1021
|
+
const match = line.match(/^(#{1,6})\s+(.+)$/);
|
|
1022
|
+
if (match) {
|
|
1023
|
+
const level = match[1].length;
|
|
1024
|
+
if (level > max_depth) continue;
|
|
1025
|
+
const text = match[2].replace(/[*_`\[\]]/g, "").trim();
|
|
1026
|
+
const anchor = text.toLowerCase().replace(/[^\w\s-]/g, "").replace(/\s+/g, "-");
|
|
1027
|
+
const indent = " ".repeat(level - 1);
|
|
1028
|
+
toc.push(`${indent}- [${text}](#${anchor})`);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
if (toc.length === 0) return { content: [{ type: "text", text: "No headings found." }] };
|
|
1032
|
+
return { content: [{ type: "text", text: toc.join("\n") }] };
|
|
1033
|
+
}
|
|
1034
|
+
|
|
791
1035
|
default:
|
|
792
1036
|
throw new Error(`Unknown tool: ${name}`);
|
|
793
1037
|
}
|
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.3.0",
|
|
4
|
+
"description": "MCP server with 25 developer utilities - UUID, hash, HMAC, base64, timestamps, JWT decode, random strings, URL encode/decode, JSON format, 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, CIDR calculator, case converter, markdown TOC",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"bin": {
|
|
@@ -30,7 +30,13 @@
|
|
|
30
30
|
"html-escape",
|
|
31
31
|
"chmod",
|
|
32
32
|
"diff",
|
|
33
|
-
"permissions"
|
|
33
|
+
"permissions",
|
|
34
|
+
"number-base",
|
|
35
|
+
"lorem-ipsum",
|
|
36
|
+
"word-count",
|
|
37
|
+
"cidr",
|
|
38
|
+
"case-convert",
|
|
39
|
+
"markdown-toc"
|
|
34
40
|
],
|
|
35
41
|
"author": "Hong Teoh",
|
|
36
42
|
"license": "MIT",
|