@rog0x/mcp-api-tools 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 ADDED
@@ -0,0 +1,81 @@
1
+ # @rog0x/mcp-api-tools
2
+
3
+ HTTP/API testing tools for AI agents, built on the [Model Context Protocol](https://modelcontextprotocol.io).
4
+
5
+ ## Tools
6
+
7
+ ### `http_request`
8
+ Make any HTTP request (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS) with full control over headers, body, authentication, and timeouts. Returns status code, response headers, body, and timing information.
9
+
10
+ ### `api_health`
11
+ Check the health of multiple API endpoints in parallel. Returns HTTP status, response time, SSL certificate validity and expiry, and optional response body validation. Includes a summary with counts of healthy, unhealthy, and errored endpoints.
12
+
13
+ ### `jwt_decode`
14
+ Decode a JWT token without cryptographic verification. Returns the decoded header and payload, issued-at time, expiry time, and whether the token is currently expired.
15
+
16
+ ### `url_parse`
17
+ Parse a URL into its component parts (protocol, host, port, path, query parameters, hash) or build a URL from individual parts.
18
+
19
+ ### `header_analyzer`
20
+ Analyze HTTP response headers for security posture (HSTS, CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy), caching directives, CORS configuration, and cookie attributes. Provides a letter grade for security. Can fetch headers from a live URL or analyze a provided headers object.
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ npm install
26
+ npm run build
27
+ ```
28
+
29
+ ## Configuration
30
+
31
+ ### Claude Desktop
32
+
33
+ Add to your `claude_desktop_config.json`:
34
+
35
+ ```json
36
+ {
37
+ "mcpServers": {
38
+ "api-tools": {
39
+ "command": "node",
40
+ "args": ["D:/products/mcp-servers/mcp-api-tools/dist/index.js"]
41
+ }
42
+ }
43
+ }
44
+ ```
45
+
46
+ ### Claude Code
47
+
48
+ ```bash
49
+ claude mcp add api-tools node D:/products/mcp-servers/mcp-api-tools/dist/index.js
50
+ ```
51
+
52
+ ## Examples
53
+
54
+ **Make a POST request:**
55
+ ```
56
+ Use http_request to POST to https://httpbin.org/post with JSON body {"key": "value"}
57
+ ```
58
+
59
+ **Check API health:**
60
+ ```
61
+ Use api_health to check these endpoints: https://api.github.com, https://httpbin.org/get
62
+ ```
63
+
64
+ **Decode a JWT:**
65
+ ```
66
+ Use jwt_decode to decode this token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
67
+ ```
68
+
69
+ **Parse a URL:**
70
+ ```
71
+ Use url_parse to break down https://example.com:8080/api/v1?key=value&debug=true#section
72
+ ```
73
+
74
+ **Analyze security headers:**
75
+ ```
76
+ Use header_analyzer to check security headers for https://github.com
77
+ ```
78
+
79
+ ## License
80
+
81
+ MIT
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,269 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
5
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
6
+ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
7
+ const http_request_js_1 = require("./tools/http-request.js");
8
+ const api_health_js_1 = require("./tools/api-health.js");
9
+ const jwt_decode_js_1 = require("./tools/jwt-decode.js");
10
+ const url_parser_js_1 = require("./tools/url-parser.js");
11
+ const header_analyzer_js_1 = require("./tools/header-analyzer.js");
12
+ const server = new index_js_1.Server({
13
+ name: "mcp-api-tools",
14
+ version: "1.0.0",
15
+ }, {
16
+ capabilities: {
17
+ tools: {},
18
+ },
19
+ });
20
+ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
21
+ tools: [
22
+ {
23
+ name: "http_request",
24
+ description: "Make an HTTP request with full control over method, headers, body, authentication, and timeouts. Returns status, headers, body, and timing information.",
25
+ inputSchema: {
26
+ type: "object",
27
+ properties: {
28
+ method: {
29
+ type: "string",
30
+ description: "HTTP method: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS",
31
+ },
32
+ url: {
33
+ type: "string",
34
+ description: "The full URL to send the request to",
35
+ },
36
+ headers: {
37
+ type: "object",
38
+ description: "Key-value pairs of HTTP headers to include",
39
+ additionalProperties: { type: "string" },
40
+ },
41
+ body: {
42
+ type: "string",
43
+ description: "Request body (for POST, PUT, PATCH)",
44
+ },
45
+ timeout: {
46
+ type: "number",
47
+ description: "Request timeout in milliseconds (default: 30000)",
48
+ },
49
+ follow_redirects: {
50
+ type: "boolean",
51
+ description: "Whether to follow redirects (default: true)",
52
+ },
53
+ auth: {
54
+ type: "object",
55
+ description: "Authentication configuration",
56
+ properties: {
57
+ type: {
58
+ type: "string",
59
+ enum: ["basic", "bearer"],
60
+ description: "Auth type: basic or bearer",
61
+ },
62
+ username: { type: "string", description: "Username for basic auth" },
63
+ password: { type: "string", description: "Password for basic auth" },
64
+ token: { type: "string", description: "Token for bearer auth" },
65
+ },
66
+ required: ["type"],
67
+ },
68
+ },
69
+ required: ["method", "url"],
70
+ },
71
+ },
72
+ {
73
+ name: "api_health",
74
+ description: "Check the health of multiple API endpoints. Returns status, response time, SSL certificate validity, and optional response body validation for each endpoint.",
75
+ inputSchema: {
76
+ type: "object",
77
+ properties: {
78
+ endpoints: {
79
+ type: "array",
80
+ description: "Array of endpoints to check (max 20)",
81
+ items: {
82
+ type: "object",
83
+ properties: {
84
+ url: { type: "string", description: "Endpoint URL" },
85
+ expected_status: {
86
+ type: "number",
87
+ description: "Expected HTTP status code (default: 200)",
88
+ },
89
+ expected_body_contains: {
90
+ type: "string",
91
+ description: "String the response body must contain",
92
+ },
93
+ },
94
+ required: ["url"],
95
+ },
96
+ },
97
+ },
98
+ required: ["endpoints"],
99
+ },
100
+ },
101
+ {
102
+ name: "jwt_decode",
103
+ description: "Decode a JWT token without verification. Returns header, payload, expiry, issued-at time, and whether the token is expired.",
104
+ inputSchema: {
105
+ type: "object",
106
+ properties: {
107
+ token: {
108
+ type: "string",
109
+ description: "The JWT token string to decode",
110
+ },
111
+ },
112
+ required: ["token"],
113
+ },
114
+ },
115
+ {
116
+ name: "url_parse",
117
+ description: "Parse a URL into its component parts: protocol, host, port, path, query parameters, hash, and more. Can also build a URL from parts.",
118
+ inputSchema: {
119
+ type: "object",
120
+ properties: {
121
+ url: {
122
+ type: "string",
123
+ description: "URL to parse (use this OR build_params, not both)",
124
+ },
125
+ build_params: {
126
+ type: "object",
127
+ description: "Build a URL from parts instead of parsing",
128
+ properties: {
129
+ protocol: { type: "string", description: "Protocol (default: https:)" },
130
+ hostname: { type: "string", description: "Hostname (required)" },
131
+ port: { type: "string", description: "Port number" },
132
+ pathname: { type: "string", description: "Path (default: /)" },
133
+ query_params: {
134
+ type: "object",
135
+ description: "Query parameters as key-value pairs",
136
+ additionalProperties: {
137
+ oneOf: [
138
+ { type: "string" },
139
+ { type: "array", items: { type: "string" } },
140
+ ],
141
+ },
142
+ },
143
+ hash: { type: "string", description: "Hash/fragment" },
144
+ username: { type: "string", description: "URL username" },
145
+ password: { type: "string", description: "URL password" },
146
+ },
147
+ required: ["hostname"],
148
+ },
149
+ },
150
+ },
151
+ },
152
+ {
153
+ name: "header_analyzer",
154
+ description: "Analyze HTTP response headers for security (HSTS, CSP, X-Frame-Options, etc.), caching directives, CORS configuration, cookies, and server information. Provides a security grade.",
155
+ inputSchema: {
156
+ type: "object",
157
+ properties: {
158
+ headers: {
159
+ type: "object",
160
+ description: "HTTP response headers as key-value pairs to analyze",
161
+ additionalProperties: { type: "string" },
162
+ },
163
+ url: {
164
+ type: "string",
165
+ description: "Alternatively, provide a URL to fetch and analyze its response headers",
166
+ },
167
+ },
168
+ },
169
+ },
170
+ ],
171
+ }));
172
+ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
173
+ const { name, arguments: args } = request.params;
174
+ try {
175
+ switch (name) {
176
+ case "http_request": {
177
+ const result = await (0, http_request_js_1.httpRequest)({
178
+ method: args?.method,
179
+ url: args?.url,
180
+ headers: args?.headers,
181
+ body: args?.body,
182
+ timeout: args?.timeout,
183
+ follow_redirects: args?.follow_redirects,
184
+ auth: args?.auth,
185
+ });
186
+ return {
187
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
188
+ };
189
+ }
190
+ case "api_health": {
191
+ const endpoints = args?.endpoints;
192
+ const result = await (0, api_health_js_1.apiHealth)(endpoints);
193
+ return {
194
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
195
+ };
196
+ }
197
+ case "jwt_decode": {
198
+ const result = (0, jwt_decode_js_1.jwtDecode)(args?.token);
199
+ return {
200
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
201
+ };
202
+ }
203
+ case "url_parse": {
204
+ if (args?.build_params) {
205
+ const built = (0, url_parser_js_1.buildUrl)(args.build_params);
206
+ return {
207
+ content: [
208
+ { type: "text", text: JSON.stringify({ built_url: built }, null, 2) },
209
+ ],
210
+ };
211
+ }
212
+ if (args?.url) {
213
+ const parsed = (0, url_parser_js_1.parseUrl)(args.url);
214
+ return {
215
+ content: [{ type: "text", text: JSON.stringify(parsed, null, 2) }],
216
+ };
217
+ }
218
+ throw new Error("Provide either 'url' to parse or 'build_params' to build");
219
+ }
220
+ case "header_analyzer": {
221
+ if (args?.url) {
222
+ const controller = new AbortController();
223
+ const timer = setTimeout(() => controller.abort(), 15000);
224
+ try {
225
+ const response = await fetch(args.url, {
226
+ method: "HEAD",
227
+ signal: controller.signal,
228
+ redirect: "follow",
229
+ });
230
+ const fetchedHeaders = {};
231
+ response.headers.forEach((value, key) => {
232
+ fetchedHeaders[key] = value;
233
+ });
234
+ const result = (0, header_analyzer_js_1.analyzeHeaders)(fetchedHeaders);
235
+ return {
236
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
237
+ };
238
+ }
239
+ finally {
240
+ clearTimeout(timer);
241
+ }
242
+ }
243
+ if (args?.headers) {
244
+ const result = (0, header_analyzer_js_1.analyzeHeaders)(args.headers);
245
+ return {
246
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
247
+ };
248
+ }
249
+ throw new Error("Provide either 'headers' object or 'url' to fetch headers from");
250
+ }
251
+ default:
252
+ throw new Error(`Unknown tool: ${name}`);
253
+ }
254
+ }
255
+ catch (error) {
256
+ const message = error instanceof Error ? error.message : String(error);
257
+ return {
258
+ content: [{ type: "text", text: `Error: ${message}` }],
259
+ isError: true,
260
+ };
261
+ }
262
+ });
263
+ async function main() {
264
+ const transport = new stdio_js_1.StdioServerTransport();
265
+ await server.connect(transport);
266
+ console.error("MCP API Tools server running on stdio");
267
+ }
268
+ main().catch(console.error);
269
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,31 @@
1
+ export interface HealthEndpoint {
2
+ url: string;
3
+ expected_status?: number;
4
+ expected_body_contains?: string;
5
+ }
6
+ export interface EndpointHealth {
7
+ url: string;
8
+ status: "healthy" | "unhealthy" | "error";
9
+ http_status: number | null;
10
+ response_time_ms: number;
11
+ ssl: SslInfo | null;
12
+ body_valid: boolean | null;
13
+ error: string | null;
14
+ }
15
+ export interface SslInfo {
16
+ valid: boolean;
17
+ issuer: string;
18
+ subject: string;
19
+ expires: string;
20
+ days_remaining: number;
21
+ }
22
+ export declare function apiHealth(endpoints: HealthEndpoint[]): Promise<{
23
+ results: EndpointHealth[];
24
+ summary: {
25
+ total: number;
26
+ healthy: number;
27
+ unhealthy: number;
28
+ errors: number;
29
+ };
30
+ }>;
31
+ //# sourceMappingURL=api-health.d.ts.map
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.apiHealth = apiHealth;
37
+ const tls = __importStar(require("node:tls"));
38
+ function checkSsl(hostname, port) {
39
+ return new Promise((resolve) => {
40
+ const timeout = setTimeout(() => {
41
+ socket.destroy();
42
+ resolve(null);
43
+ }, 5000);
44
+ const socket = tls.connect({ host: hostname, port, servername: hostname }, () => {
45
+ clearTimeout(timeout);
46
+ const cert = socket.getPeerCertificate();
47
+ if (!cert || !cert.valid_to) {
48
+ socket.destroy();
49
+ resolve(null);
50
+ return;
51
+ }
52
+ const expiresDate = new Date(cert.valid_to);
53
+ const now = new Date();
54
+ const daysRemaining = Math.floor((expiresDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
55
+ const flatten = (val) => {
56
+ if (!val)
57
+ return "Unknown";
58
+ return Array.isArray(val) ? val[0] : val;
59
+ };
60
+ resolve({
61
+ valid: socket.authorized,
62
+ issuer: typeof cert.issuer === "object"
63
+ ? flatten(cert.issuer.O) !== "Unknown"
64
+ ? flatten(cert.issuer.O)
65
+ : flatten(cert.issuer.CN)
66
+ : "Unknown",
67
+ subject: typeof cert.subject === "object"
68
+ ? flatten(cert.subject.CN)
69
+ : "Unknown",
70
+ expires: cert.valid_to,
71
+ days_remaining: daysRemaining,
72
+ });
73
+ socket.destroy();
74
+ });
75
+ socket.on("error", () => {
76
+ clearTimeout(timeout);
77
+ resolve(null);
78
+ });
79
+ });
80
+ }
81
+ async function checkEndpoint(endpoint) {
82
+ const { url, expected_status = 200, expected_body_contains } = endpoint;
83
+ let parsedUrl;
84
+ try {
85
+ parsedUrl = new URL(url);
86
+ }
87
+ catch {
88
+ return {
89
+ url,
90
+ status: "error",
91
+ http_status: null,
92
+ response_time_ms: 0,
93
+ ssl: null,
94
+ body_valid: null,
95
+ error: "Invalid URL",
96
+ };
97
+ }
98
+ const isHttps = parsedUrl.protocol === "https:";
99
+ const sslPromise = isHttps
100
+ ? checkSsl(parsedUrl.hostname, Number(parsedUrl.port) || 443)
101
+ : Promise.resolve(null);
102
+ const controller = new AbortController();
103
+ const timer = setTimeout(() => controller.abort(), 15000);
104
+ const start = Date.now();
105
+ try {
106
+ const response = await fetch(url, {
107
+ method: "GET",
108
+ signal: controller.signal,
109
+ redirect: "follow",
110
+ });
111
+ const body = await response.text();
112
+ const elapsed = Date.now() - start;
113
+ let bodyValid = null;
114
+ if (expected_body_contains) {
115
+ bodyValid = body.includes(expected_body_contains);
116
+ }
117
+ const isHealthy = response.status === expected_status &&
118
+ (bodyValid === null || bodyValid === true);
119
+ const ssl = await sslPromise;
120
+ return {
121
+ url,
122
+ status: isHealthy ? "healthy" : "unhealthy",
123
+ http_status: response.status,
124
+ response_time_ms: elapsed,
125
+ ssl,
126
+ body_valid: bodyValid,
127
+ error: null,
128
+ };
129
+ }
130
+ catch (err) {
131
+ const elapsed = Date.now() - start;
132
+ const ssl = await sslPromise;
133
+ const message = err instanceof Error ? err.message : String(err);
134
+ return {
135
+ url,
136
+ status: "error",
137
+ http_status: null,
138
+ response_time_ms: elapsed,
139
+ ssl,
140
+ body_valid: null,
141
+ error: message,
142
+ };
143
+ }
144
+ finally {
145
+ clearTimeout(timer);
146
+ }
147
+ }
148
+ async function apiHealth(endpoints) {
149
+ if (!endpoints || endpoints.length === 0) {
150
+ throw new Error("Provide at least one endpoint to check");
151
+ }
152
+ const capped = endpoints.slice(0, 20);
153
+ const results = await Promise.all(capped.map(checkEndpoint));
154
+ const healthy = results.filter((r) => r.status === "healthy").length;
155
+ const unhealthy = results.filter((r) => r.status === "unhealthy").length;
156
+ const errors = results.filter((r) => r.status === "error").length;
157
+ return {
158
+ results,
159
+ summary: {
160
+ total: results.length,
161
+ healthy,
162
+ unhealthy,
163
+ errors,
164
+ },
165
+ };
166
+ }
167
+ //# sourceMappingURL=api-health.js.map
@@ -0,0 +1,56 @@
1
+ export interface HeaderAnalysis {
2
+ raw_headers: Record<string, string>;
3
+ security: SecurityAnalysis;
4
+ caching: CachingAnalysis;
5
+ cors: CorsAnalysis;
6
+ cookies: CookieAnalysis[];
7
+ server_info: {
8
+ server: string | null;
9
+ powered_by: string | null;
10
+ };
11
+ }
12
+ export interface SecurityAnalysis {
13
+ score: number;
14
+ max_score: number;
15
+ grade: string;
16
+ checks: SecurityCheck[];
17
+ }
18
+ export interface SecurityCheck {
19
+ header: string;
20
+ present: boolean;
21
+ value: string | null;
22
+ recommendation: string;
23
+ }
24
+ export interface CachingAnalysis {
25
+ cache_control: string | null;
26
+ etag: string | null;
27
+ last_modified: string | null;
28
+ expires: string | null;
29
+ age: string | null;
30
+ pragma: string | null;
31
+ is_cacheable: boolean;
32
+ summary: string;
33
+ }
34
+ export interface CorsAnalysis {
35
+ allow_origin: string | null;
36
+ allow_methods: string | null;
37
+ allow_headers: string | null;
38
+ allow_credentials: string | null;
39
+ expose_headers: string | null;
40
+ max_age: string | null;
41
+ is_open: boolean;
42
+ summary: string;
43
+ }
44
+ export interface CookieAnalysis {
45
+ raw: string;
46
+ name: string;
47
+ secure: boolean;
48
+ http_only: boolean;
49
+ same_site: string | null;
50
+ path: string | null;
51
+ domain: string | null;
52
+ expires: string | null;
53
+ max_age: string | null;
54
+ }
55
+ export declare function analyzeHeaders(headers: Record<string, string>): HeaderAnalysis;
56
+ //# sourceMappingURL=header-analyzer.d.ts.map