mcp-devutils 1.1.5 → 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.
Files changed (3) hide show
  1. package/README.md +69 -0
  2. package/index.js +455 -1
  3. package/package.json +15 -3
package/README.md CHANGED
@@ -19,6 +19,17 @@ An MCP (Model Context Protocol) server with developer utilities. Use it directly
19
19
  | `hmac` | Generate HMAC signatures (SHA-256, SHA-512, etc.) |
20
20
  | `color_convert` | Convert colors between hex, RGB, and HSL |
21
21
  | `semver_compare` | Compare two semantic versions |
22
+ | `http_status` | Look up HTTP status code meaning and usage |
23
+ | `slug` | Generate URL-safe slugs from text |
24
+ | `escape_html` | Escape or unescape HTML entities |
25
+ | `chmod_calc` | Convert between numeric and symbolic Unix permissions |
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 |
22
33
 
23
34
  ## Installation
24
35
 
@@ -150,6 +161,64 @@ Compare two semantic versions:
150
161
  - `version2`: Second version (required), e.g. `2.0.0`
151
162
  - Returns comparison result and parsed components
152
163
 
164
+ ### http_status
165
+ Look up HTTP status codes:
166
+ - `code`: HTTP status code (required), e.g. `404`, `502`
167
+ - Returns status name, category, and description
168
+
169
+ ### slug
170
+ Generate URL-safe slugs:
171
+ - `text`: Text to slugify (required)
172
+ - `separator`: Word separator (default: `-`)
173
+
174
+ ### escape_html
175
+ Escape or unescape HTML entities:
176
+ - `text`: Input text (required)
177
+ - `action`: `escape` or `unescape` (default: escape)
178
+
179
+ ### chmod_calc
180
+ Convert Unix file permissions:
181
+ - `permission`: Numeric (e.g. `755`) or symbolic (e.g. `rwxr-xr-x`) (required)
182
+ - Returns both formats plus owner/group/other breakdown
183
+
184
+ ### diff
185
+ Compare two texts:
186
+ - `text1`: Original text (required)
187
+ - `text2`: Modified text (required)
188
+ - Returns line-by-line diff with added/removed/unchanged summary
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
+
153
222
  ## See Also
154
223
 
155
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.1.0" },
8
+ { name: "mcp-devutils", version: "1.3.0" },
9
9
  { capabilities: { tools: {} } }
10
10
  );
11
11
 
@@ -197,6 +197,144 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
197
197
  },
198
198
  required: ["version1", "version2"]
199
199
  }
200
+ },
201
+ {
202
+ name: "http_status",
203
+ description: "Look up HTTP status code meaning, category, and common usage",
204
+ inputSchema: {
205
+ type: "object",
206
+ properties: {
207
+ code: { type: "number", description: "HTTP status code (e.g. 404, 502)" }
208
+ },
209
+ required: ["code"]
210
+ }
211
+ },
212
+ {
213
+ name: "slug",
214
+ description: "Generate a URL-safe slug from text",
215
+ inputSchema: {
216
+ type: "object",
217
+ properties: {
218
+ text: { type: "string", description: "Text to slugify" },
219
+ separator: { type: "string", description: "Word separator (default: '-')" }
220
+ },
221
+ required: ["text"]
222
+ }
223
+ },
224
+ {
225
+ name: "escape_html",
226
+ description: "Escape or unescape HTML entities",
227
+ inputSchema: {
228
+ type: "object",
229
+ properties: {
230
+ text: { type: "string", description: "Text to escape or unescape" },
231
+ action: {
232
+ type: "string",
233
+ enum: ["escape", "unescape"],
234
+ description: "Action: escape or unescape (default: escape)"
235
+ }
236
+ },
237
+ required: ["text"]
238
+ }
239
+ },
240
+ {
241
+ name: "chmod_calc",
242
+ description: "Convert between numeric and symbolic Unix file permissions (e.g. 755 ↔ rwxr-xr-x)",
243
+ inputSchema: {
244
+ type: "object",
245
+ properties: {
246
+ permission: { type: "string", description: "Numeric (e.g. '755') or symbolic (e.g. 'rwxr-xr-x') permission" }
247
+ },
248
+ required: ["permission"]
249
+ }
250
+ },
251
+ {
252
+ name: "diff",
253
+ description: "Compare two text strings and show the differences line by line",
254
+ inputSchema: {
255
+ type: "object",
256
+ properties: {
257
+ text1: { type: "string", description: "First text (original)" },
258
+ text2: { type: "string", description: "Second text (modified)" }
259
+ },
260
+ required: ["text1", "text2"]
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
+ }
200
338
  }
201
339
  ]
202
340
  };
@@ -578,6 +716,322 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
578
716
  };
579
717
  }
580
718
 
719
+ case "http_status": {
720
+ const { code } = args;
721
+ const statuses = {
722
+ 100: ["Continue", "Informational", "The server has received the request headers and the client should proceed to send the request body."],
723
+ 101: ["Switching Protocols", "Informational", "The server is switching protocols as requested by the client (e.g. to WebSocket)."],
724
+ 200: ["OK", "Success", "The request succeeded. Standard response for successful HTTP requests."],
725
+ 201: ["Created", "Success", "The request succeeded and a new resource was created. Typical for POST requests."],
726
+ 202: ["Accepted", "Success", "The request has been accepted for processing, but processing is not complete."],
727
+ 204: ["No Content", "Success", "The request succeeded but there is no content to return. Common for DELETE requests."],
728
+ 301: ["Moved Permanently", "Redirection", "The resource has been permanently moved to a new URL. Clients should update bookmarks."],
729
+ 302: ["Found", "Redirection", "The resource temporarily resides at a different URL. Client should continue using the original URL."],
730
+ 304: ["Not Modified", "Redirection", "The resource has not been modified since the last request. Used for caching."],
731
+ 307: ["Temporary Redirect", "Redirection", "The request should be repeated with the same method at a different URL."],
732
+ 308: ["Permanent Redirect", "Redirection", "The resource has permanently moved. The request method should not change."],
733
+ 400: ["Bad Request", "Client Error", "The server cannot process the request due to malformed syntax or invalid parameters."],
734
+ 401: ["Unauthorized", "Client Error", "Authentication is required. The client must provide valid credentials."],
735
+ 403: ["Forbidden", "Client Error", "The server understood the request but refuses to authorize it. Authentication won't help."],
736
+ 404: ["Not Found", "Client Error", "The requested resource could not be found on the server."],
737
+ 405: ["Method Not Allowed", "Client Error", "The HTTP method is not allowed for the requested resource."],
738
+ 408: ["Request Timeout", "Client Error", "The server timed out waiting for the request from the client."],
739
+ 409: ["Conflict", "Client Error", "The request conflicts with the current state of the resource. Common in concurrent updates."],
740
+ 410: ["Gone", "Client Error", "The resource is no longer available and no forwarding address is known."],
741
+ 413: ["Payload Too Large", "Client Error", "The request body exceeds the server's size limit."],
742
+ 415: ["Unsupported Media Type", "Client Error", "The server does not support the media type of the request body."],
743
+ 422: ["Unprocessable Entity", "Client Error", "The request was well-formed but semantically invalid. Common in validation errors."],
744
+ 429: ["Too Many Requests", "Client Error", "The client has sent too many requests in a given time period (rate limiting)."],
745
+ 500: ["Internal Server Error", "Server Error", "An unexpected condition was encountered on the server."],
746
+ 501: ["Not Implemented", "Server Error", "The server does not support the functionality required to fulfill the request."],
747
+ 502: ["Bad Gateway", "Server Error", "The server received an invalid response from an upstream server."],
748
+ 503: ["Service Unavailable", "Server Error", "The server is temporarily unable to handle the request (overload or maintenance)."],
749
+ 504: ["Gateway Timeout", "Server Error", "The server did not receive a timely response from an upstream server."]
750
+ };
751
+ const info = statuses[code];
752
+ if (!info) {
753
+ const category = code >= 100 && code < 200 ? "Informational" : code < 300 ? "Success" : code < 400 ? "Redirection" : code < 500 ? "Client Error" : code < 600 ? "Server Error" : "Unknown";
754
+ return { content: [{ type: "text", text: `${code}: Unknown status code\nCategory: ${category}` }] };
755
+ }
756
+ return {
757
+ content: [{ type: "text", text: `${code} ${info[0]}\nCategory: ${info[1]}\nDescription: ${info[2]}` }]
758
+ };
759
+ }
760
+
761
+ case "slug": {
762
+ const { text, separator = "-" } = args;
763
+ const slug = text.toLowerCase()
764
+ .normalize("NFD").replace(/[\u0300-\u036f]/g, "")
765
+ .replace(/[^a-z0-9\s-]/g, "")
766
+ .trim()
767
+ .replace(/[\s-]+/g, separator);
768
+ return { content: [{ type: "text", text: slug }] };
769
+ }
770
+
771
+ case "escape_html": {
772
+ const { text, action = "escape" } = args;
773
+ let result;
774
+ if (action === "escape") {
775
+ result = text
776
+ .replace(/&/g, "&amp;")
777
+ .replace(/</g, "&lt;")
778
+ .replace(/>/g, "&gt;")
779
+ .replace(/"/g, "&quot;")
780
+ .replace(/'/g, "&#39;");
781
+ } else {
782
+ result = text
783
+ .replace(/&#39;/g, "'")
784
+ .replace(/&quot;/g, '"')
785
+ .replace(/&gt;/g, ">")
786
+ .replace(/&lt;/g, "<")
787
+ .replace(/&amp;/g, "&");
788
+ }
789
+ return { content: [{ type: "text", text: result }] };
790
+ }
791
+
792
+ case "chmod_calc": {
793
+ const { permission } = args;
794
+ const numericMatch = permission.match(/^[0-7]{3,4}$/);
795
+ const symbolicMatch = permission.match(/^([r-][w-][xsS-])([r-][w-][xsS-])([r-][w-][xtT-])$/);
796
+
797
+ if (numericMatch) {
798
+ const digits = permission.length === 4 ? permission : "0" + permission;
799
+ const special = parseInt(digits[0]);
800
+ const owner = parseInt(digits[1]);
801
+ const group = parseInt(digits[2]);
802
+ const other = parseInt(digits[3]);
803
+
804
+ function toSymbolic(val, pos, specialBit) {
805
+ let r = (val & 4) ? "r" : "-";
806
+ let w = (val & 2) ? "w" : "-";
807
+ let x = (val & 1) ? "x" : "-";
808
+ if (specialBit) {
809
+ if (pos === "owner" && (special & 4)) x = (val & 1) ? "s" : "S";
810
+ if (pos === "group" && (special & 2)) x = (val & 1) ? "s" : "S";
811
+ if (pos === "other" && (special & 1)) x = (val & 1) ? "t" : "T";
812
+ }
813
+ return r + w + x;
814
+ }
815
+
816
+ const sym = toSymbolic(owner, "owner", true) + toSymbolic(group, "group", true) + toSymbolic(other, "other", true);
817
+ const desc = [];
818
+ desc.push(`Numeric: ${permission}`);
819
+ desc.push(`Symbolic: ${sym}`);
820
+ desc.push(`Owner: ${["---","--x","-w-","-wx","r--","r-x","rw-","rwx"][owner]} (${owner})`);
821
+ desc.push(`Group: ${["---","--x","-w-","-wx","r--","r-x","rw-","rwx"][group]} (${group})`);
822
+ desc.push(`Other: ${["---","--x","-w-","-wx","r--","r-x","rw-","rwx"][other]} (${other})`);
823
+ return { content: [{ type: "text", text: desc.join("\n") }] };
824
+ } else if (symbolicMatch) {
825
+ function fromSymbolic(s) {
826
+ let val = 0;
827
+ if (s[0] === "r") val += 4;
828
+ if (s[1] === "w") val += 2;
829
+ if (s[2] === "x" || s[2] === "s" || s[2] === "t") val += 1;
830
+ return val;
831
+ }
832
+ const o = fromSymbolic(symbolicMatch[1]);
833
+ const g = fromSymbolic(symbolicMatch[2]);
834
+ const t = fromSymbolic(symbolicMatch[3]);
835
+ const numeric = `${o}${g}${t}`;
836
+ return { content: [{ type: "text", text: `Symbolic: ${permission}\nNumeric: ${numeric}\nOwner: ${o}, Group: ${g}, Other: ${t}` }] };
837
+ } else {
838
+ throw new Error("Invalid permission. Use numeric (e.g. '755') or symbolic (e.g. 'rwxr-xr-x')");
839
+ }
840
+ }
841
+
842
+ case "diff": {
843
+ const { text1, text2 } = args;
844
+ const lines1 = text1.split("\n");
845
+ const lines2 = text2.split("\n");
846
+ const output = [];
847
+ const maxLen = Math.max(lines1.length, lines2.length);
848
+
849
+ // Simple line-by-line diff
850
+ let added = 0, removed = 0, unchanged = 0;
851
+ for (let i = 0; i < maxLen; i++) {
852
+ const l1 = i < lines1.length ? lines1[i] : undefined;
853
+ const l2 = i < lines2.length ? lines2[i] : undefined;
854
+ if (l1 === l2) {
855
+ output.push(` ${l1}`);
856
+ unchanged++;
857
+ } else {
858
+ if (l1 !== undefined) { output.push(`- ${l1}`); removed++; }
859
+ if (l2 !== undefined) { output.push(`+ ${l2}`); added++; }
860
+ }
861
+ }
862
+
863
+ const summary = `\n--- Summary: ${added} added, ${removed} removed, ${unchanged} unchanged`;
864
+ return { content: [{ type: "text", text: output.join("\n") + summary }] };
865
+ }
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
+
581
1035
  default:
582
1036
  throw new Error(`Unknown tool: ${name}`);
583
1037
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mcp-devutils",
3
- "version": "1.1.5",
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",
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": {
@@ -24,7 +24,19 @@
24
24
  "cursor",
25
25
  "cron",
26
26
  "color-convert",
27
- "semver"
27
+ "semver",
28
+ "http-status",
29
+ "slugify",
30
+ "html-escape",
31
+ "chmod",
32
+ "diff",
33
+ "permissions",
34
+ "number-base",
35
+ "lorem-ipsum",
36
+ "word-count",
37
+ "cidr",
38
+ "case-convert",
39
+ "markdown-toc"
28
40
  ],
29
41
  "author": "Hong Teoh",
30
42
  "license": "MIT",