fetch_api
Make REST API requests using HTTP methods like GET, POST, PUT, and DELETE with custom headers, body content, and timeout settings.
Instructions
Make a REST API request with various methods, headers, and body.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | The URL for the API endpoint. | |
| method | Yes | HTTP method for the request. | |
| headers | No | Request headers (e.g., for authorization). | |
| body | No | Request body (JSON object, string, etc.). | |
| timeout | No | Request timeout in milliseconds (default: 60000). | |
| limit | Yes | Maximum number of characters to return in the response body (required). | |
| redirect | No | Redirect mode for the request (default: follow). |
Implementation Reference
- src/rest-client.ts:40-138 (handler)The core handler function that performs the HTTP fetch request, handles timeouts, body serialization, response parsing (JSON/text/binary), truncation to limit characters, and returns structured response.export const fetchApi = async (args: FetchApiArgs): Promise<FetchApiResponse> => { const { url, method, headers, body, timeout = 60000, limit, redirect = 'follow' } = args; const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeout); const startTime = performance.now(); // Start time measurement try { const options: RequestInit = { method, headers, signal: controller.signal, redirect, // Add redirect option // The `timeout` option is specific to `node-fetch` and not part of the standard `RequestInit`. // The timeout logic is now handled manually using AbortController. }; if (body !== undefined) { if (typeof body === 'object' && headers && headers['Content-Type'] === 'application/json') { options.body = JSON.stringify(body); } else { options.body = body; // Works for string, Buffer, FormData, URLSearchParams } } const response: Response = await fetch(url, options); clearTimeout(timeoutId); const endTime = performance.now(); // End time measurement const responseTimeMs = endTime - startTime; // Calculate response time let responseBody: any; const contentType = response.headers.get('content-type'); if (contentType && contentType.includes('application/json')) { responseBody = await response.json(); } else if (contentType && (contentType.includes('text/') || contentType.includes('application/xml') || contentType.includes('application/xhtml+xml'))) { responseBody = await response.text(); } else { // For binary data or unknown content types, try to get as buffer then base64 encode try { const buffer = await response.arrayBuffer(); responseBody = Buffer.from(buffer).toString('base64'); } catch (e) { responseBody = 'Could not parse body (binary or unknown content type)'; } } const responseHeaders: Record<string, string> = {}; response.headers.forEach((value, name) => { responseHeaders[name] = value; }); // Enforce output limit: convert body to string for length measurement/truncation. let fullBodyString: string; try { if (typeof responseBody === 'string') { fullBodyString = responseBody; } else { fullBodyString = JSON.stringify(responseBody); } } catch (e) { fullBodyString = String(responseBody); } const bodyLength = fullBodyString.length; let truncated = false; let finalBody: any = responseBody; if (typeof limit === 'number' && bodyLength > limit) { // When truncation is necessary, return a string truncated to 'limit' characters. finalBody = fullBodyString.substring(0, limit); truncated = true; } else { // If not truncated and original was JSON-parsed, keep original parsed object, // otherwise keep the string. finalBody = responseBody; } return { status: response.status, statusText: response.statusText, headers: responseHeaders, body: finalBody, ok: response.ok, url: response.url, bodyLength, truncated, responseTimeMs, // Include response time }; } catch (error: any) { clearTimeout(timeoutId); if (error.name === 'AbortError') { throw new Error(`Request timed out after ${timeout / 1000} seconds`); } throw error; } };
- src/index.ts:170-211 (registration)Registration of the 'fetch_api' tool in the MCP server's listTools handler, including name, description, autoApprove, and detailed inputSchema.name: 'fetch_api', description: 'Make a REST API request with various methods, headers, and body.', autoApprove: true, inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'The URL for the API endpoint.', }, method: { type: 'string', enum: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'], description: 'HTTP method for the request.', }, headers: { type: 'object', description: 'Request headers (e.g., for authorization).', }, body: { type: ['object', 'string', 'null'], // Allow object, string, or null for body description: 'Request body (JSON object, string, etc.).', }, timeout: { type: 'number', description: 'Request timeout in milliseconds (default: 60000).', }, limit: { type: 'number', description: 'Maximum number of characters to return in the response body (required).' }, redirect: { type: 'string', enum: ['follow', 'error', 'manual'], description: 'Redirect mode for the request (default: follow).' }, }, required: ['url', 'method', 'limit'], additionalProperties: false, description: 'HTTP API request (GET/POST/etc), custom header/body, timeout, debug mode for verbose output/logging.' }, },
- src/rest-client.ts:3-11 (schema)TypeScript interface defining the input parameters for the fetch_api tool.export interface FetchApiArgs { url: string; method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS'; headers?: Record<string, string>; body?: any; // Can be string, Buffer, stream, or URLSearchParams timeout?: number; // Timeout in milliseconds limit: number; // Maximum number of characters to return in the response body (required) redirect?: 'follow' | 'error' | 'manual'; // Redirect mode }
- src/rest-client.ts:27-36 (schema)TypeScript interface defining the output response structure from the fetch_api tool.export interface FetchApiResponse { status: number; statusText: string; headers: Record<string, string>; body: any; ok: boolean; url: string; bodyLength?: number; // length of the un-truncated body (characters) truncated?: boolean; // whether the body was truncated to satisfy limit responseTimeMs: number; // Add response time in milliseconds
- src/rest-client.ts:14-25 (helper)Validation function to check if provided arguments conform to FetchApiArgs interface before calling the handler.export const isValidFetchApiArgs = (args: any): args is FetchApiArgs => { if (typeof args !== 'object' || args === null) return false; if (typeof args.url !== 'string') return false; if (!['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'].includes(args.method)) return false; if (args.headers !== undefined && (typeof args.headers !== 'object' || args.headers === null || Array.isArray(args.headers))) return false; // body can be of various types, so a simple check might not be sufficient, // but for now, we'll assume it's provided correctly if it exists. if (args.timeout !== undefined && typeof args.timeout !== 'number') return false; if (args.limit === undefined || typeof args.limit !== 'number') return false; // limit is required and must be a number if (args.redirect !== undefined && !['follow', 'error', 'manual'].includes(args.redirect)) return false; return true; };