mcp-devutils 1.1.5 → 1.2.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 +31 -0
  2. package/index.js +211 -1
  3. package/package.json +9 -3
package/README.md CHANGED
@@ -19,6 +19,11 @@ 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 |
22
27
 
23
28
  ## Installation
24
29
 
@@ -150,6 +155,32 @@ Compare two semantic versions:
150
155
  - `version2`: Second version (required), e.g. `2.0.0`
151
156
  - Returns comparison result and parsed components
152
157
 
158
+ ### http_status
159
+ Look up HTTP status codes:
160
+ - `code`: HTTP status code (required), e.g. `404`, `502`
161
+ - Returns status name, category, and description
162
+
163
+ ### slug
164
+ Generate URL-safe slugs:
165
+ - `text`: Text to slugify (required)
166
+ - `separator`: Word separator (default: `-`)
167
+
168
+ ### escape_html
169
+ Escape or unescape HTML entities:
170
+ - `text`: Input text (required)
171
+ - `action`: `escape` or `unescape` (default: escape)
172
+
173
+ ### chmod_calc
174
+ Convert Unix file permissions:
175
+ - `permission`: Numeric (e.g. `755`) or symbolic (e.g. `rwxr-xr-x`) (required)
176
+ - Returns both formats plus owner/group/other breakdown
177
+
178
+ ### diff
179
+ Compare two texts:
180
+ - `text1`: Original text (required)
181
+ - `text2`: Modified text (required)
182
+ - Returns line-by-line diff with added/removed/unchanged summary
183
+
153
184
  ## See Also
154
185
 
155
186
  - [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.2.0" },
9
9
  { capabilities: { tools: {} } }
10
10
  );
11
11
 
@@ -197,6 +197,68 @@ 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
+ }
200
262
  }
201
263
  ]
202
264
  };
@@ -578,6 +640,154 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
578
640
  };
579
641
  }
580
642
 
643
+ case "http_status": {
644
+ const { code } = args;
645
+ const statuses = {
646
+ 100: ["Continue", "Informational", "The server has received the request headers and the client should proceed to send the request body."],
647
+ 101: ["Switching Protocols", "Informational", "The server is switching protocols as requested by the client (e.g. to WebSocket)."],
648
+ 200: ["OK", "Success", "The request succeeded. Standard response for successful HTTP requests."],
649
+ 201: ["Created", "Success", "The request succeeded and a new resource was created. Typical for POST requests."],
650
+ 202: ["Accepted", "Success", "The request has been accepted for processing, but processing is not complete."],
651
+ 204: ["No Content", "Success", "The request succeeded but there is no content to return. Common for DELETE requests."],
652
+ 301: ["Moved Permanently", "Redirection", "The resource has been permanently moved to a new URL. Clients should update bookmarks."],
653
+ 302: ["Found", "Redirection", "The resource temporarily resides at a different URL. Client should continue using the original URL."],
654
+ 304: ["Not Modified", "Redirection", "The resource has not been modified since the last request. Used for caching."],
655
+ 307: ["Temporary Redirect", "Redirection", "The request should be repeated with the same method at a different URL."],
656
+ 308: ["Permanent Redirect", "Redirection", "The resource has permanently moved. The request method should not change."],
657
+ 400: ["Bad Request", "Client Error", "The server cannot process the request due to malformed syntax or invalid parameters."],
658
+ 401: ["Unauthorized", "Client Error", "Authentication is required. The client must provide valid credentials."],
659
+ 403: ["Forbidden", "Client Error", "The server understood the request but refuses to authorize it. Authentication won't help."],
660
+ 404: ["Not Found", "Client Error", "The requested resource could not be found on the server."],
661
+ 405: ["Method Not Allowed", "Client Error", "The HTTP method is not allowed for the requested resource."],
662
+ 408: ["Request Timeout", "Client Error", "The server timed out waiting for the request from the client."],
663
+ 409: ["Conflict", "Client Error", "The request conflicts with the current state of the resource. Common in concurrent updates."],
664
+ 410: ["Gone", "Client Error", "The resource is no longer available and no forwarding address is known."],
665
+ 413: ["Payload Too Large", "Client Error", "The request body exceeds the server's size limit."],
666
+ 415: ["Unsupported Media Type", "Client Error", "The server does not support the media type of the request body."],
667
+ 422: ["Unprocessable Entity", "Client Error", "The request was well-formed but semantically invalid. Common in validation errors."],
668
+ 429: ["Too Many Requests", "Client Error", "The client has sent too many requests in a given time period (rate limiting)."],
669
+ 500: ["Internal Server Error", "Server Error", "An unexpected condition was encountered on the server."],
670
+ 501: ["Not Implemented", "Server Error", "The server does not support the functionality required to fulfill the request."],
671
+ 502: ["Bad Gateway", "Server Error", "The server received an invalid response from an upstream server."],
672
+ 503: ["Service Unavailable", "Server Error", "The server is temporarily unable to handle the request (overload or maintenance)."],
673
+ 504: ["Gateway Timeout", "Server Error", "The server did not receive a timely response from an upstream server."]
674
+ };
675
+ const info = statuses[code];
676
+ if (!info) {
677
+ const category = code >= 100 && code < 200 ? "Informational" : code < 300 ? "Success" : code < 400 ? "Redirection" : code < 500 ? "Client Error" : code < 600 ? "Server Error" : "Unknown";
678
+ return { content: [{ type: "text", text: `${code}: Unknown status code\nCategory: ${category}` }] };
679
+ }
680
+ return {
681
+ content: [{ type: "text", text: `${code} ${info[0]}\nCategory: ${info[1]}\nDescription: ${info[2]}` }]
682
+ };
683
+ }
684
+
685
+ case "slug": {
686
+ const { text, separator = "-" } = args;
687
+ const slug = text.toLowerCase()
688
+ .normalize("NFD").replace(/[\u0300-\u036f]/g, "")
689
+ .replace(/[^a-z0-9\s-]/g, "")
690
+ .trim()
691
+ .replace(/[\s-]+/g, separator);
692
+ return { content: [{ type: "text", text: slug }] };
693
+ }
694
+
695
+ case "escape_html": {
696
+ const { text, action = "escape" } = args;
697
+ let result;
698
+ if (action === "escape") {
699
+ result = text
700
+ .replace(/&/g, "&amp;")
701
+ .replace(/</g, "&lt;")
702
+ .replace(/>/g, "&gt;")
703
+ .replace(/"/g, "&quot;")
704
+ .replace(/'/g, "&#39;");
705
+ } else {
706
+ result = text
707
+ .replace(/&#39;/g, "'")
708
+ .replace(/&quot;/g, '"')
709
+ .replace(/&gt;/g, ">")
710
+ .replace(/&lt;/g, "<")
711
+ .replace(/&amp;/g, "&");
712
+ }
713
+ return { content: [{ type: "text", text: result }] };
714
+ }
715
+
716
+ case "chmod_calc": {
717
+ const { permission } = args;
718
+ const numericMatch = permission.match(/^[0-7]{3,4}$/);
719
+ const symbolicMatch = permission.match(/^([r-][w-][xsS-])([r-][w-][xsS-])([r-][w-][xtT-])$/);
720
+
721
+ if (numericMatch) {
722
+ const digits = permission.length === 4 ? permission : "0" + permission;
723
+ const special = parseInt(digits[0]);
724
+ const owner = parseInt(digits[1]);
725
+ const group = parseInt(digits[2]);
726
+ const other = parseInt(digits[3]);
727
+
728
+ function toSymbolic(val, pos, specialBit) {
729
+ let r = (val & 4) ? "r" : "-";
730
+ let w = (val & 2) ? "w" : "-";
731
+ let x = (val & 1) ? "x" : "-";
732
+ if (specialBit) {
733
+ if (pos === "owner" && (special & 4)) x = (val & 1) ? "s" : "S";
734
+ if (pos === "group" && (special & 2)) x = (val & 1) ? "s" : "S";
735
+ if (pos === "other" && (special & 1)) x = (val & 1) ? "t" : "T";
736
+ }
737
+ return r + w + x;
738
+ }
739
+
740
+ const sym = toSymbolic(owner, "owner", true) + toSymbolic(group, "group", true) + toSymbolic(other, "other", true);
741
+ const desc = [];
742
+ desc.push(`Numeric: ${permission}`);
743
+ desc.push(`Symbolic: ${sym}`);
744
+ desc.push(`Owner: ${["---","--x","-w-","-wx","r--","r-x","rw-","rwx"][owner]} (${owner})`);
745
+ desc.push(`Group: ${["---","--x","-w-","-wx","r--","r-x","rw-","rwx"][group]} (${group})`);
746
+ desc.push(`Other: ${["---","--x","-w-","-wx","r--","r-x","rw-","rwx"][other]} (${other})`);
747
+ return { content: [{ type: "text", text: desc.join("\n") }] };
748
+ } else if (symbolicMatch) {
749
+ function fromSymbolic(s) {
750
+ let val = 0;
751
+ if (s[0] === "r") val += 4;
752
+ if (s[1] === "w") val += 2;
753
+ if (s[2] === "x" || s[2] === "s" || s[2] === "t") val += 1;
754
+ return val;
755
+ }
756
+ const o = fromSymbolic(symbolicMatch[1]);
757
+ const g = fromSymbolic(symbolicMatch[2]);
758
+ const t = fromSymbolic(symbolicMatch[3]);
759
+ const numeric = `${o}${g}${t}`;
760
+ return { content: [{ type: "text", text: `Symbolic: ${permission}\nNumeric: ${numeric}\nOwner: ${o}, Group: ${g}, Other: ${t}` }] };
761
+ } else {
762
+ throw new Error("Invalid permission. Use numeric (e.g. '755') or symbolic (e.g. 'rwxr-xr-x')");
763
+ }
764
+ }
765
+
766
+ case "diff": {
767
+ const { text1, text2 } = args;
768
+ const lines1 = text1.split("\n");
769
+ const lines2 = text2.split("\n");
770
+ const output = [];
771
+ const maxLen = Math.max(lines1.length, lines2.length);
772
+
773
+ // Simple line-by-line diff
774
+ let added = 0, removed = 0, unchanged = 0;
775
+ for (let i = 0; i < maxLen; i++) {
776
+ const l1 = i < lines1.length ? lines1[i] : undefined;
777
+ const l2 = i < lines2.length ? lines2[i] : undefined;
778
+ if (l1 === l2) {
779
+ output.push(` ${l1}`);
780
+ unchanged++;
781
+ } else {
782
+ if (l1 !== undefined) { output.push(`- ${l1}`); removed++; }
783
+ if (l2 !== undefined) { output.push(`+ ${l2}`); added++; }
784
+ }
785
+ }
786
+
787
+ const summary = `\n--- Summary: ${added} added, ${removed} removed, ${unchanged} unchanged`;
788
+ return { content: [{ type: "text", text: output.join("\n") + summary }] };
789
+ }
790
+
581
791
  default:
582
792
  throw new Error(`Unknown tool: ${name}`);
583
793
  }
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.2.0",
4
+ "description": "MCP server with 19 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",
5
5
  "type": "module",
6
6
  "main": "index.js",
7
7
  "bin": {
@@ -24,7 +24,13 @@
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"
28
34
  ],
29
35
  "author": "Hong Teoh",
30
36
  "license": "MIT",