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.
- package/README.md +31 -0
- package/index.js +211 -1
- 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.
|
|
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, "&")
|
|
701
|
+
.replace(/</g, "<")
|
|
702
|
+
.replace(/>/g, ">")
|
|
703
|
+
.replace(/"/g, """)
|
|
704
|
+
.replace(/'/g, "'");
|
|
705
|
+
} else {
|
|
706
|
+
result = text
|
|
707
|
+
.replace(/'/g, "'")
|
|
708
|
+
.replace(/"/g, '"')
|
|
709
|
+
.replace(/>/g, ">")
|
|
710
|
+
.replace(/</g, "<")
|
|
711
|
+
.replace(/&/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.
|
|
4
|
-
"description": "MCP server with
|
|
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",
|