mcp-devutils 1.0.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 +134 -0
- package/index.js +342 -0
- package/package.json +23 -0
package/README.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# mcp-devutils
|
|
2
|
+
|
|
3
|
+
An MCP (Model Context Protocol) server with developer utilities. Use it directly with Claude Desktop, Cursor, or any MCP-compatible client.
|
|
4
|
+
|
|
5
|
+
## Tools
|
|
6
|
+
|
|
7
|
+
| Tool | Description |
|
|
8
|
+
|------|-------------|
|
|
9
|
+
| `uuid` | Generate UUID v4 (up to 10 at once) |
|
|
10
|
+
| `hash` | Hash text with md5, sha1, or sha256 |
|
|
11
|
+
| `base64` | Encode or decode base64 |
|
|
12
|
+
| `timestamp` | Convert between Unix timestamps and ISO 8601 |
|
|
13
|
+
| `jwt_decode` | Decode JWT token payload (no verification) |
|
|
14
|
+
| `random_string` | Generate random strings/passwords |
|
|
15
|
+
| `url_encode` | URL encode or decode strings |
|
|
16
|
+
| `json_format` | Pretty-print or minify JSON |
|
|
17
|
+
| `regex_test` | Test regex patterns against strings |
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install -g mcp-devutils
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Or use directly with `npx`:
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"mcpServers": {
|
|
30
|
+
"devutils": {
|
|
31
|
+
"command": "npx",
|
|
32
|
+
"args": ["-y", "mcp-devutils"]
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Usage with Claude Desktop
|
|
39
|
+
|
|
40
|
+
Add to your `claude_desktop_config.json`:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"mcpServers": {
|
|
45
|
+
"devutils": {
|
|
46
|
+
"command": "mcp-devutils"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Or with npx (no install needed):
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"mcpServers": {
|
|
57
|
+
"devutils": {
|
|
58
|
+
"command": "npx",
|
|
59
|
+
"args": ["-y", "mcp-devutils"]
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Usage with Cursor
|
|
66
|
+
|
|
67
|
+
Add to your Cursor MCP settings:
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"mcpServers": {
|
|
72
|
+
"devutils": {
|
|
73
|
+
"command": "npx",
|
|
74
|
+
"args": ["-y", "mcp-devutils"]
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Tool Examples
|
|
81
|
+
|
|
82
|
+
### uuid
|
|
83
|
+
Generate one or more UUIDs:
|
|
84
|
+
- `count`: Number of UUIDs to generate (1–10, default: 1)
|
|
85
|
+
|
|
86
|
+
### hash
|
|
87
|
+
Hash text with a chosen algorithm:
|
|
88
|
+
- `text`: Text to hash (required)
|
|
89
|
+
- `algorithm`: `md5`, `sha1`, or `sha256` (default: sha256)
|
|
90
|
+
|
|
91
|
+
### base64
|
|
92
|
+
Encode or decode base64:
|
|
93
|
+
- `text`: Input text (required)
|
|
94
|
+
- `action`: `encode` or `decode` (default: encode)
|
|
95
|
+
|
|
96
|
+
### timestamp
|
|
97
|
+
Convert timestamps:
|
|
98
|
+
- `value`: Unix timestamp or ISO date string (leave empty for current time)
|
|
99
|
+
|
|
100
|
+
### jwt_decode
|
|
101
|
+
Decode a JWT without verifying the signature:
|
|
102
|
+
- `token`: JWT string (required)
|
|
103
|
+
- Returns header, payload, expiry info
|
|
104
|
+
|
|
105
|
+
### random_string
|
|
106
|
+
Generate random strings:
|
|
107
|
+
- `length`: String length (default: 16, max: 256)
|
|
108
|
+
- `charset`: `alphanumeric`, `alpha`, `numeric`, `hex`, `password`, or `url-safe`
|
|
109
|
+
|
|
110
|
+
### url_encode
|
|
111
|
+
Encode or decode URLs:
|
|
112
|
+
- `text`: Input text (required)
|
|
113
|
+
- `action`: `encode` or `decode` (default: encode)
|
|
114
|
+
|
|
115
|
+
### json_format
|
|
116
|
+
Format or minify JSON:
|
|
117
|
+
- `json`: JSON string (required)
|
|
118
|
+
- `action`: `format` or `minify` (default: format)
|
|
119
|
+
- `indent`: Spaces for indentation (default: 2)
|
|
120
|
+
|
|
121
|
+
### regex_test
|
|
122
|
+
Test regex patterns:
|
|
123
|
+
- `pattern`: Regex pattern (required)
|
|
124
|
+
- `text`: String to test against (required)
|
|
125
|
+
- `flags`: Regex flags like `g`, `i`, `gi` (optional)
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
MIT — [Hong Teoh](https://github.com/hlteoh37)
|
|
130
|
+
|
|
131
|
+
## Support
|
|
132
|
+
|
|
133
|
+
If this tool saves you time, consider buying me a coffee:
|
|
134
|
+
[buymeacoffee.com/gl89tu25lp](https://buymeacoffee.com/gl89tu25lp)
|
package/index.js
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import crypto from "crypto";
|
|
6
|
+
|
|
7
|
+
const server = new Server(
|
|
8
|
+
{ name: "mcp-devutils", version: "1.0.0" },
|
|
9
|
+
{ capabilities: { tools: {} } }
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
13
|
+
return {
|
|
14
|
+
tools: [
|
|
15
|
+
{
|
|
16
|
+
name: "uuid",
|
|
17
|
+
description: "Generate a UUID v4",
|
|
18
|
+
inputSchema: {
|
|
19
|
+
type: "object",
|
|
20
|
+
properties: {
|
|
21
|
+
count: {
|
|
22
|
+
type: "number",
|
|
23
|
+
description: "Number of UUIDs to generate (default: 1, max: 10)"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: "hash",
|
|
30
|
+
description: "Hash text using md5, sha1, or sha256",
|
|
31
|
+
inputSchema: {
|
|
32
|
+
type: "object",
|
|
33
|
+
properties: {
|
|
34
|
+
text: { type: "string", description: "Text to hash" },
|
|
35
|
+
algorithm: {
|
|
36
|
+
type: "string",
|
|
37
|
+
enum: ["md5", "sha1", "sha256"],
|
|
38
|
+
description: "Hash algorithm (default: sha256)"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
required: ["text"]
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: "base64",
|
|
46
|
+
description: "Encode or decode base64",
|
|
47
|
+
inputSchema: {
|
|
48
|
+
type: "object",
|
|
49
|
+
properties: {
|
|
50
|
+
text: { type: "string", description: "Text to encode or decode" },
|
|
51
|
+
action: {
|
|
52
|
+
type: "string",
|
|
53
|
+
enum: ["encode", "decode"],
|
|
54
|
+
description: "Action: encode or decode (default: encode)"
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
required: ["text"]
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: "timestamp",
|
|
62
|
+
description: "Convert between Unix timestamps and ISO 8601 dates",
|
|
63
|
+
inputSchema: {
|
|
64
|
+
type: "object",
|
|
65
|
+
properties: {
|
|
66
|
+
value: {
|
|
67
|
+
type: "string",
|
|
68
|
+
description: "Unix timestamp (number) or ISO date string to convert. Leave empty for current time."
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: "jwt_decode",
|
|
75
|
+
description: "Decode a JWT token (no signature verification, just decode the payload)",
|
|
76
|
+
inputSchema: {
|
|
77
|
+
type: "object",
|
|
78
|
+
properties: {
|
|
79
|
+
token: { type: "string", description: "JWT token to decode" }
|
|
80
|
+
},
|
|
81
|
+
required: ["token"]
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: "random_string",
|
|
86
|
+
description: "Generate a random string or password",
|
|
87
|
+
inputSchema: {
|
|
88
|
+
type: "object",
|
|
89
|
+
properties: {
|
|
90
|
+
length: { type: "number", description: "Length of the string (default: 16)" },
|
|
91
|
+
charset: {
|
|
92
|
+
type: "string",
|
|
93
|
+
enum: ["alphanumeric", "alpha", "numeric", "hex", "password", "url-safe"],
|
|
94
|
+
description: "Character set to use (default: alphanumeric)"
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: "url_encode",
|
|
101
|
+
description: "URL encode or decode a string",
|
|
102
|
+
inputSchema: {
|
|
103
|
+
type: "object",
|
|
104
|
+
properties: {
|
|
105
|
+
text: { type: "string", description: "Text to encode or decode" },
|
|
106
|
+
action: {
|
|
107
|
+
type: "string",
|
|
108
|
+
enum: ["encode", "decode"],
|
|
109
|
+
description: "Action: encode or decode (default: encode)"
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
required: ["text"]
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: "json_format",
|
|
117
|
+
description: "Format (pretty-print) or minify JSON",
|
|
118
|
+
inputSchema: {
|
|
119
|
+
type: "object",
|
|
120
|
+
properties: {
|
|
121
|
+
json: { type: "string", description: "JSON string to format or minify" },
|
|
122
|
+
action: {
|
|
123
|
+
type: "string",
|
|
124
|
+
enum: ["format", "minify"],
|
|
125
|
+
description: "Action: format or minify (default: format)"
|
|
126
|
+
},
|
|
127
|
+
indent: { type: "number", description: "Indent spaces for format (default: 2)" }
|
|
128
|
+
},
|
|
129
|
+
required: ["json"]
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: "regex_test",
|
|
134
|
+
description: "Test a regex pattern against a string",
|
|
135
|
+
inputSchema: {
|
|
136
|
+
type: "object",
|
|
137
|
+
properties: {
|
|
138
|
+
pattern: { type: "string", description: "Regular expression pattern" },
|
|
139
|
+
text: { type: "string", description: "Text to test against" },
|
|
140
|
+
flags: { type: "string", description: "Regex flags (e.g. 'gi' for global case-insensitive)" }
|
|
141
|
+
},
|
|
142
|
+
required: ["pattern", "text"]
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
]
|
|
146
|
+
};
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
150
|
+
const { name, arguments: args } = request.params;
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
switch (name) {
|
|
154
|
+
case "uuid": {
|
|
155
|
+
const count = Math.min(Math.max(1, args?.count || 1), 10);
|
|
156
|
+
const uuids = Array.from({ length: count }, () => crypto.randomUUID());
|
|
157
|
+
return {
|
|
158
|
+
content: [{ type: "text", text: uuids.join("\n") }]
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
case "hash": {
|
|
163
|
+
const { text, algorithm = "sha256" } = args;
|
|
164
|
+
const hash = crypto.createHash(algorithm).update(text, "utf8").digest("hex");
|
|
165
|
+
return {
|
|
166
|
+
content: [{ type: "text", text: `${algorithm}: ${hash}` }]
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
case "base64": {
|
|
171
|
+
const { text, action = "encode" } = args;
|
|
172
|
+
let result;
|
|
173
|
+
if (action === "encode") {
|
|
174
|
+
result = Buffer.from(text, "utf8").toString("base64");
|
|
175
|
+
} else {
|
|
176
|
+
result = Buffer.from(text, "base64").toString("utf8");
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
content: [{ type: "text", text: result }]
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
case "timestamp": {
|
|
184
|
+
const { value } = args || {};
|
|
185
|
+
let output = [];
|
|
186
|
+
if (!value) {
|
|
187
|
+
const now = new Date();
|
|
188
|
+
output.push(`Current Unix timestamp: ${Math.floor(now.getTime() / 1000)}`);
|
|
189
|
+
output.push(`Current ISO 8601: ${now.toISOString()}`);
|
|
190
|
+
output.push(`Current UTC: ${now.toUTCString()}`);
|
|
191
|
+
} else if (/^\d+(\.\d+)?$/.test(value.trim())) {
|
|
192
|
+
const unix = parseFloat(value.trim());
|
|
193
|
+
const ms = unix > 1e10 ? unix : unix * 1000;
|
|
194
|
+
const date = new Date(ms);
|
|
195
|
+
output.push(`Unix: ${Math.floor(unix)}`);
|
|
196
|
+
output.push(`ISO 8601: ${date.toISOString()}`);
|
|
197
|
+
output.push(`UTC: ${date.toUTCString()}`);
|
|
198
|
+
} else {
|
|
199
|
+
const date = new Date(value);
|
|
200
|
+
if (isNaN(date.getTime())) {
|
|
201
|
+
throw new Error(`Invalid date/timestamp: ${value}`);
|
|
202
|
+
}
|
|
203
|
+
output.push(`ISO 8601: ${date.toISOString()}`);
|
|
204
|
+
output.push(`Unix timestamp: ${Math.floor(date.getTime() / 1000)}`);
|
|
205
|
+
output.push(`UTC: ${date.toUTCString()}`);
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
content: [{ type: "text", text: output.join("\n") }]
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
case "jwt_decode": {
|
|
213
|
+
const { token } = args;
|
|
214
|
+
const parts = token.trim().split(".");
|
|
215
|
+
if (parts.length !== 3) {
|
|
216
|
+
throw new Error("Invalid JWT: must have 3 parts separated by dots");
|
|
217
|
+
}
|
|
218
|
+
const decodeB64 = (str) => {
|
|
219
|
+
const padded = str + "=".repeat((4 - (str.length % 4)) % 4);
|
|
220
|
+
return Buffer.from(padded.replace(/-/g, "+").replace(/_/g, "/"), "base64").toString("utf8");
|
|
221
|
+
};
|
|
222
|
+
const header = JSON.parse(decodeB64(parts[0]));
|
|
223
|
+
const payload = JSON.parse(decodeB64(parts[1]));
|
|
224
|
+
const result = {
|
|
225
|
+
header,
|
|
226
|
+
payload,
|
|
227
|
+
signature: parts[2],
|
|
228
|
+
note: "Signature NOT verified — decode only"
|
|
229
|
+
};
|
|
230
|
+
if (payload.exp) {
|
|
231
|
+
const expDate = new Date(payload.exp * 1000);
|
|
232
|
+
result.expires = expDate.toISOString();
|
|
233
|
+
result.expired = expDate < new Date();
|
|
234
|
+
}
|
|
235
|
+
if (payload.iat) {
|
|
236
|
+
result.issued_at = new Date(payload.iat * 1000).toISOString();
|
|
237
|
+
}
|
|
238
|
+
return {
|
|
239
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
case "random_string": {
|
|
244
|
+
const { length = 16, charset = "alphanumeric" } = args || {};
|
|
245
|
+
const len = Math.min(Math.max(1, length), 256);
|
|
246
|
+
const charsets = {
|
|
247
|
+
alphanumeric: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
|
|
248
|
+
alpha: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
|
|
249
|
+
numeric: "0123456789",
|
|
250
|
+
hex: "0123456789abcdef",
|
|
251
|
+
password: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-_=+[]{}|;:,.<>?",
|
|
252
|
+
"url-safe": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~"
|
|
253
|
+
};
|
|
254
|
+
const chars = charsets[charset] || charsets.alphanumeric;
|
|
255
|
+
const bytes = crypto.randomBytes(len);
|
|
256
|
+
let result = "";
|
|
257
|
+
for (let i = 0; i < len; i++) {
|
|
258
|
+
result += chars[bytes[i] % chars.length];
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
content: [{ type: "text", text: result }]
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
case "url_encode": {
|
|
266
|
+
const { text, action = "encode" } = args;
|
|
267
|
+
let result;
|
|
268
|
+
if (action === "encode") {
|
|
269
|
+
result = encodeURIComponent(text);
|
|
270
|
+
} else {
|
|
271
|
+
result = decodeURIComponent(text);
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
content: [{ type: "text", text: result }]
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
case "json_format": {
|
|
279
|
+
const { json, action = "format", indent = 2 } = args;
|
|
280
|
+
const parsed = JSON.parse(json);
|
|
281
|
+
let result;
|
|
282
|
+
if (action === "minify") {
|
|
283
|
+
result = JSON.stringify(parsed);
|
|
284
|
+
} else {
|
|
285
|
+
result = JSON.stringify(parsed, null, Math.min(Math.max(0, indent), 8));
|
|
286
|
+
}
|
|
287
|
+
return {
|
|
288
|
+
content: [{ type: "text", text: result }]
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
case "regex_test": {
|
|
293
|
+
const { pattern, text, flags = "" } = args;
|
|
294
|
+
const regex = new RegExp(pattern, flags);
|
|
295
|
+
const matches = [];
|
|
296
|
+
let match;
|
|
297
|
+
if (flags.includes("g")) {
|
|
298
|
+
while ((match = regex.exec(text)) !== null) {
|
|
299
|
+
matches.push({
|
|
300
|
+
match: match[0],
|
|
301
|
+
index: match.index,
|
|
302
|
+
groups: match.groups || null
|
|
303
|
+
});
|
|
304
|
+
if (match.index === regex.lastIndex) regex.lastIndex++;
|
|
305
|
+
}
|
|
306
|
+
} else {
|
|
307
|
+
match = regex.exec(text);
|
|
308
|
+
if (match) {
|
|
309
|
+
matches.push({
|
|
310
|
+
match: match[0],
|
|
311
|
+
index: match.index,
|
|
312
|
+
groups: match.groups || null,
|
|
313
|
+
captures: match.slice(1)
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
const result = {
|
|
318
|
+
pattern,
|
|
319
|
+
flags: flags || "(none)",
|
|
320
|
+
text,
|
|
321
|
+
matched: matches.length > 0,
|
|
322
|
+
match_count: matches.length,
|
|
323
|
+
matches
|
|
324
|
+
};
|
|
325
|
+
return {
|
|
326
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
default:
|
|
331
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
332
|
+
}
|
|
333
|
+
} catch (error) {
|
|
334
|
+
return {
|
|
335
|
+
content: [{ type: "text", text: `Error: ${error.message}` }],
|
|
336
|
+
isError: true
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
const transport = new StdioServerTransport();
|
|
342
|
+
await server.connect(transport);
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mcp-devutils",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server with developer utilities - UUID, hash, base64, timestamps, JWT decode, random strings, URL encode/decode, JSON format, regex test",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mcp-devutils": "index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node index.js"
|
|
12
|
+
},
|
|
13
|
+
"keywords": ["mcp", "model-context-protocol", "developer-tools", "utilities", "uuid", "hash", "base64", "jwt", "claude", "cursor"],
|
|
14
|
+
"author": "Hong Teoh",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"funding": {
|
|
17
|
+
"type": "buymeacoffee",
|
|
18
|
+
"url": "https://buymeacoffee.com/gl89tu25lp"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
22
|
+
}
|
|
23
|
+
}
|