mcp-apitools 1.0.6 → 1.1.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 (2) hide show
  1. package/index.js +185 -1
  2. package/package.json +11 -3
package/index.js CHANGED
@@ -6,7 +6,7 @@ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprot
6
6
  import crypto from "crypto";
7
7
 
8
8
  const server = new Server(
9
- { name: "mcp-apitools", version: "1.0.0" },
9
+ { name: "mcp-apitools", version: "1.1.0" },
10
10
  { capabilities: { tools: {} } }
11
11
  );
12
12
 
@@ -180,6 +180,79 @@ const TOOLS = [
180
180
  },
181
181
  required: ["input"]
182
182
  }
183
+ },
184
+ {
185
+ name: "url_parse",
186
+ description: "Parse a URL into its components — protocol, host, port, pathname, search params, hash, origin",
187
+ inputSchema: {
188
+ type: "object",
189
+ properties: {
190
+ url: { type: "string", description: "URL to parse (e.g. 'https://api.example.com:8080/v1/users?page=2#section')" }
191
+ },
192
+ required: ["url"]
193
+ }
194
+ },
195
+ {
196
+ name: "bearer_token",
197
+ description: "Generate a Bearer Authorization header from a token string",
198
+ inputSchema: {
199
+ type: "object",
200
+ properties: {
201
+ token: { type: "string", description: "Bearer token value" }
202
+ },
203
+ required: ["token"]
204
+ }
205
+ },
206
+ {
207
+ name: "request_id",
208
+ description: "Generate unique request/correlation IDs for distributed tracing (UUID v4, prefixed, or nanoid-style)",
209
+ inputSchema: {
210
+ type: "object",
211
+ properties: {
212
+ format: { type: "string", enum: ["uuid", "prefixed", "short"], description: "ID format: uuid (default), prefixed (req_xxx), or short (12-char alphanumeric)" },
213
+ prefix: { type: "string", description: "Custom prefix for 'prefixed' format (default: 'req')" },
214
+ count: { type: "number", description: "Number of IDs to generate (1-20, default 1)" }
215
+ }
216
+ }
217
+ },
218
+ {
219
+ name: "api_error",
220
+ description: "Generate a standard API error response body (RFC 7807 Problem Details format)",
221
+ inputSchema: {
222
+ type: "object",
223
+ properties: {
224
+ status: { type: "number", description: "HTTP status code (e.g. 404)" },
225
+ title: { type: "string", description: "Short error title" },
226
+ detail: { type: "string", description: "Detailed error description" },
227
+ instance: { type: "string", description: "URI reference identifying the specific occurrence" }
228
+ },
229
+ required: ["status"]
230
+ }
231
+ },
232
+ {
233
+ name: "rate_limit_headers",
234
+ description: "Generate standard rate limit response headers (X-RateLimit-* and Retry-After)",
235
+ inputSchema: {
236
+ type: "object",
237
+ properties: {
238
+ limit: { type: "number", description: "Max requests per window (default 100)" },
239
+ remaining: { type: "number", description: "Remaining requests in window (default 99)" },
240
+ windowSeconds: { type: "number", description: "Window duration in seconds (default 3600)" },
241
+ retryAfter: { type: "number", description: "Seconds until rate limit resets (omit if not rate limited)" }
242
+ }
243
+ }
244
+ },
245
+ {
246
+ name: "form_encode",
247
+ description: "Encode a JSON object as application/x-www-form-urlencoded, or decode form data back to JSON",
248
+ inputSchema: {
249
+ type: "object",
250
+ properties: {
251
+ input: { type: "string", description: "JSON object to encode, or URL-encoded form string to decode" },
252
+ action: { type: "string", enum: ["encode", "decode"], description: "Action: encode (default) or decode" }
253
+ },
254
+ required: ["input"]
255
+ }
183
256
  }
184
257
  ];
185
258
 
@@ -298,6 +371,117 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
298
371
  return { content: [{ type: "text", text: `Parsed query parameters:\n${JSON.stringify(result, null, 2)}` }] };
299
372
  }
300
373
 
374
+ case "url_parse": {
375
+ try {
376
+ const u = new URL(args.url);
377
+ const params = {};
378
+ for (const [k, v] of u.searchParams) {
379
+ if (params[k]) params[k] = Array.isArray(params[k]) ? [...params[k], v] : [params[k], v];
380
+ else params[k] = v;
381
+ }
382
+ const result = {
383
+ href: u.href,
384
+ origin: u.origin,
385
+ protocol: u.protocol,
386
+ host: u.host,
387
+ hostname: u.hostname,
388
+ port: u.port || "(default)",
389
+ pathname: u.pathname,
390
+ search: u.search,
391
+ searchParams: params,
392
+ hash: u.hash,
393
+ username: u.username || undefined,
394
+ password: u.password || undefined,
395
+ };
396
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
397
+ } catch {
398
+ return { content: [{ type: "text", text: `Invalid URL: "${args.url}". Provide a full URL (e.g. https://example.com/path?key=val).` }], isError: true };
399
+ }
400
+ }
401
+
402
+ case "bearer_token": {
403
+ return { content: [{ type: "text", text: `Authorization: Bearer ${args.token}\n\n⚠️ Bearer tokens should be transmitted over HTTPS only.` }] };
404
+ }
405
+
406
+ case "request_id": {
407
+ const fmt = args.format || "uuid";
408
+ const count = Math.max(1, Math.min(20, args.count || 1));
409
+ const ids = [];
410
+ for (let i = 0; i < count; i++) {
411
+ if (fmt === "uuid") {
412
+ ids.push(crypto.randomUUID());
413
+ } else if (fmt === "prefixed") {
414
+ const prefix = args.prefix || "req";
415
+ ids.push(`${prefix}_${crypto.randomUUID().replace(/-/g, "").slice(0, 20)}`);
416
+ } else {
417
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
418
+ let id = "";
419
+ for (let j = 0; j < 12; j++) id += chars[Math.floor(Math.random() * chars.length)];
420
+ ids.push(id);
421
+ }
422
+ }
423
+ const header = count === 1
424
+ ? `X-Request-ID: ${ids[0]}`
425
+ : ids.map((id, i) => `${i + 1}. ${id}`).join("\n");
426
+ return { content: [{ type: "text", text: header }] };
427
+ }
428
+
429
+ case "api_error": {
430
+ const status = args.status;
431
+ const info = HTTP_STATUSES[status];
432
+ const title = args.title || (info ? info[0] : "Error");
433
+ const detail = args.detail || (info ? info[1] : "An error occurred.");
434
+ const body = {
435
+ type: `about:blank`,
436
+ title,
437
+ status,
438
+ detail,
439
+ };
440
+ if (args.instance) body.instance = args.instance;
441
+ return { content: [{ type: "text", text: `Content-Type: application/problem+json\n\n${JSON.stringify(body, null, 2)}\n\nSee RFC 7807 for Problem Details specification.` }] };
442
+ }
443
+
444
+ case "rate_limit_headers": {
445
+ const limit = args.limit || 100;
446
+ const remaining = args.remaining ?? 99;
447
+ const windowSec = args.windowSeconds || 3600;
448
+ const resetTime = Math.floor(Date.now() / 1000) + windowSec;
449
+ const headers = [
450
+ `X-RateLimit-Limit: ${limit}`,
451
+ `X-RateLimit-Remaining: ${remaining}`,
452
+ `X-RateLimit-Reset: ${resetTime}`,
453
+ ];
454
+ if (args.retryAfter != null) {
455
+ headers.push(`Retry-After: ${args.retryAfter}`);
456
+ }
457
+ return { content: [{ type: "text", text: `Rate Limit Headers:\n\n${headers.join("\n")}\n\nWindow: ${windowSec}s | Limit: ${limit} req/window | Remaining: ${remaining}` }] };
458
+ }
459
+
460
+ case "form_encode": {
461
+ const action = args.action || "encode";
462
+ if (action === "encode") {
463
+ try {
464
+ const obj = JSON.parse(args.input);
465
+ const params = new URLSearchParams();
466
+ for (const [k, v] of Object.entries(obj)) {
467
+ if (Array.isArray(v)) v.forEach(item => params.append(k, String(item)));
468
+ else params.append(k, String(v));
469
+ }
470
+ return { content: [{ type: "text", text: `Content-Type: application/x-www-form-urlencoded\n\n${params.toString()}` }] };
471
+ } catch {
472
+ return { content: [{ type: "text", text: `Invalid JSON input. Provide a JSON object to encode.` }], isError: true };
473
+ }
474
+ } else {
475
+ const params = new URLSearchParams(args.input);
476
+ const result = {};
477
+ for (const [k, v] of params) {
478
+ if (result[k]) result[k] = Array.isArray(result[k]) ? [...result[k], v] : [result[k], v];
479
+ else result[k] = v;
480
+ }
481
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
482
+ }
483
+ }
484
+
301
485
  default:
302
486
  return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
303
487
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mcp-apitools",
3
- "version": "1.0.6",
4
- "description": "MCP server with API & web development utilities \u2014 HTTP status codes, MIME types, JWT creation, mock data generation, CORS headers, cookie parsing",
3
+ "version": "1.1.0",
4
+ "description": "MCP server with 14 API & web development utilities HTTP status codes, MIME types, JWT creation, mock data, CORS headers, cookie parsing, URL parser, Bearer auth, request IDs, RFC 7807 errors, rate limit headers, form encoding",
5
5
  "type": "module",
6
6
  "main": "index.js",
7
7
  "bin": {
@@ -24,7 +24,15 @@
24
24
  "claude",
25
25
  "cursor",
26
26
  "developer-tools",
27
- "rest-api"
27
+ "rest-api",
28
+ "url-parser",
29
+ "bearer-auth",
30
+ "request-id",
31
+ "rate-limit",
32
+ "rfc-7807",
33
+ "form-encode",
34
+ "distributed-tracing",
35
+ "ai-tools"
28
36
  ],
29
37
  "author": "Hong Teoh",
30
38
  "license": "MIT",