request
Execute HTTP requests with relative URL resolution, variable interpolation, authentication, and adjustable verbosity to control token usage.
Instructions
Ejecuta un HTTP request. URLs relativas (/path) usan BASE_URL del entorno activo. Soporta {{variables}}. La respuesta se comprime por defecto (verbosity=normal) para ahorrar tokens; usa verbosity=full o inspect_last_response si necesitas la respuesta completa.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| method | Yes | HTTP method | |
| url | Yes | URL del endpoint. Si empieza con / se antepone BASE_URL del entorno activo. Soporta {{variables}}. | |
| headers | No | Headers HTTP como key-value pairs | |
| body | No | Body del request (JSON). Soporta {{variables}} | |
| query | No | Query parameters como key-value pairs | |
| timeout | No | Timeout en milisegundos (default: 30000) | |
| auth | No | Configuración de autenticación | |
| verbosity | No | Controls response detail to save context tokens. Default: 'normal'. - 'minimal': Only status, method, url, elapsed_ms, and first 200 chars of body. USE FOR: health checks (/health, /ping), status polling loops, fire-and-forget POST/DELETE, waiting for a job to complete, or when you only care whether the call succeeded. SAVES: ~95% tokens vs full. - 'normal' (DEFAULT): Filtered headers (omits Date, Server, CF-*, Set-Cookie, etc.) + body truncated to max_body_bytes with 'body_truncated' flag. USE FOR: most debugging — CRUDs, checking error messages, API contract exploration. SAVES: ~75% tokens vs full. - 'full': Complete response untouched. USE FOR: debugging CORS/cache/auth headers, large bodies you must inspect completely, or when the user asks to see everything. NO SAVINGS. If a response is truncated and you need more, prefer inspect_last_response({call_id}) over re-running with 'full'. | |
| only_fields | No | Cheap alternative to 'full' when you know exactly what you need from the body. Returns only these dot-paths. Supports array index and wildcard. Examples: ["data.id"], ["user.email", "user.role"], ["items[*].id", "meta.total"]. Often saves >95% vs full while keeping the fields you care about. | |
| max_body_bytes | No | Max body size in bytes for verbosity='normal' (default: 2048). Ignored for minimal/full. |
Implementation Reference
- src/tools/request.ts:43-91 (handler)The async handler function that executes the 'request' tool logic: resolves URL, interpolates variables, executes HTTP request, caches response, compresses output.
async (params) => { try { const variables = await storage.getActiveVariables() const resolvedUrl = resolveUrl(params.url, variables) const config: RequestConfig = { method: params.method, url: resolvedUrl, headers: params.headers, body: params.body, query: params.query, timeout: params.timeout, auth: params.auth, } const interpolated = interpolateRequest(config, variables) const response = await executeRequest(interpolated) const callId = makeCallId() // Guarda la response full para poder recuperarla con inspect_last_response await cache.save(callId, interpolated.method, interpolated.url, response) const compressed = compressResponse(response, { verbosity: params.verbosity, only_fields: params.only_fields, max_body_bytes: params.max_body_bytes, request_method: interpolated.method, request_url: interpolated.url, call_id: callId, }) return { content: [ { type: 'text' as const, text: JSON.stringify(compressed, null, 2), }, ], } } catch (error) { const message = error instanceof Error ? error.message : String(error) return { content: [{ type: 'text' as const, text: `Error: ${message}` }], isError: true, } } }, ) } - src/tools/request.ts:20-42 (schema)Zod schema definitions for the 'request' tool parameters: method, url, headers, body, query, timeout, auth, and verbosity options.
{ method: HttpMethodSchema.describe('HTTP method'), url: z .string() .describe( 'URL del endpoint. Si empieza con / se antepone BASE_URL del entorno activo. Soporta {{variables}}.', ), headers: z .record(z.string()) .optional() .describe('Headers HTTP como key-value pairs'), body: z.any().optional().describe('Body del request (JSON). Soporta {{variables}}'), query: z .record(z.string()) .optional() .describe('Query parameters como key-value pairs'), timeout: z.number().optional().describe('Timeout en milisegundos (default: 30000)'), auth: z .object(AuthSchemaShape) .optional() .describe('Configuración de autenticación'), ...VerbosityShape, }, - src/tools/request.ts:12-91 (registration)The registerRequestTool function that registers the 'request' tool on the MCP server via server.tool().
export function registerRequestTool( server: McpServer, storage: Storage, cache: ResponseCache, ): void { server.tool( 'request', 'Ejecuta un HTTP request. URLs relativas (/path) usan BASE_URL del entorno activo. Soporta {{variables}}. La respuesta se comprime por defecto (verbosity=normal) para ahorrar tokens; usa verbosity=full o inspect_last_response si necesitas la respuesta completa.', { method: HttpMethodSchema.describe('HTTP method'), url: z .string() .describe( 'URL del endpoint. Si empieza con / se antepone BASE_URL del entorno activo. Soporta {{variables}}.', ), headers: z .record(z.string()) .optional() .describe('Headers HTTP como key-value pairs'), body: z.any().optional().describe('Body del request (JSON). Soporta {{variables}}'), query: z .record(z.string()) .optional() .describe('Query parameters como key-value pairs'), timeout: z.number().optional().describe('Timeout en milisegundos (default: 30000)'), auth: z .object(AuthSchemaShape) .optional() .describe('Configuración de autenticación'), ...VerbosityShape, }, async (params) => { try { const variables = await storage.getActiveVariables() const resolvedUrl = resolveUrl(params.url, variables) const config: RequestConfig = { method: params.method, url: resolvedUrl, headers: params.headers, body: params.body, query: params.query, timeout: params.timeout, auth: params.auth, } const interpolated = interpolateRequest(config, variables) const response = await executeRequest(interpolated) const callId = makeCallId() // Guarda la response full para poder recuperarla con inspect_last_response await cache.save(callId, interpolated.method, interpolated.url, response) const compressed = compressResponse(response, { verbosity: params.verbosity, only_fields: params.only_fields, max_body_bytes: params.max_body_bytes, request_method: interpolated.method, request_url: interpolated.url, call_id: callId, }) return { content: [ { type: 'text' as const, text: JSON.stringify(compressed, null, 2), }, ], } } catch (error) { const message = error instanceof Error ? error.message : String(error) return { content: [{ type: 'text' as const, text: `Error: ${message}` }], isError: true, } } }, ) } - src/server.ts:62-62 (registration)Invocation of registerRequestTool in the server setup, wiring storage and response cache dependencies.
registerRequestTool(server, storage, responseCache) - src/lib/http-client.ts:57-142 (helper)The executeRequest helper function that performs the actual HTTP fetch, handles auth, body serialization, timeout, and returns structured response.
export async function executeRequest(config: RequestConfig): Promise<RequestResponse> { const timeout = config.timeout ?? DEFAULT_TIMEOUT // Construir URL con query params const url = buildUrl(config.url, config.query) // Preparar headers — Accept: application/json por defecto let headers: Record<string, string> = { Accept: 'application/json', ...config.headers, } // Aplicar auth if (config.auth) { headers = applyAuth(headers, config.auth) } // Preparar body let body: string | undefined if (config.body !== undefined && config.body !== null) { if (typeof config.body === 'string') { body = config.body } else { body = JSON.stringify(config.body) // Solo añadir Content-Type si no está definido if (!headers['Content-Type'] && !headers['content-type']) { headers['Content-Type'] = 'application/json' } } } // AbortController para timeout const controller = new AbortController() const timeoutId = setTimeout(() => controller.abort(), timeout) // Medir timing const startTime = performance.now() try { const response = await fetch(url, { method: config.method, headers, body, signal: controller.signal, }) const endTime = performance.now() const totalMs = Math.round((endTime - startTime) * 100) / 100 // Parsear response body const responseText = await response.text() let responseBody: unknown try { responseBody = JSON.parse(responseText) } catch { responseBody = responseText } // Convertir headers a Record const responseHeaders: Record<string, string> = {} response.headers.forEach((value, key) => { responseHeaders[key] = value }) // Calcular tamaño const sizeBytes = Number(response.headers.get('content-length')) || Buffer.byteLength(responseText, 'utf-8') return { status: response.status, statusText: response.statusText, headers: responseHeaders, body: responseBody, timing: { total_ms: totalMs }, size_bytes: sizeBytes, } } catch (error) { if (error instanceof Error && error.name === 'AbortError') { throw new Error(`Request timeout: superado el límite de ${timeout}ms`) } throw error } finally { clearTimeout(timeoutId) } }