api.request
Execute HTTP requests to API endpoints with configurable methods, parameters, and optional authentication for frontend development testing.
Instructions
Execute a live HTTP request to API_BASE_URL; optionally include an Authorization bearer token retrieved from configured browser storage. Use after 'api.searchEndpoints' or for known endpoints.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| endpoint | Yes | The API endpoint path (e.g., '/api/users', '/auth/profile'). Will be combined with API_BASE_URL from environment. | |
| method | No | HTTP method for the API call | GET |
| requestBody | No | Request body for POST/PUT/PATCH requests (will be JSON stringified) | |
| queryParams | No | Query parameters as key-value pairs | |
| includeAuthToken | No | Whether to include auth token |
Implementation Reference
- browser-tools-mcp/mcp-server.ts:1018-1208 (registration)Registration of the 'api.request' MCP tool, including description, input schema, and inline handler function.server.tool( "api.request", "Execute a live HTTP request to API_BASE_URL; optionally include an Authorization bearer token retrieved from configured browser storage. Use after 'api.searchEndpoints' or for known endpoints.", { endpoint: z .string() .describe( "The API endpoint path (e.g., '/api/users', '/auth/profile'). Will be combined with API_BASE_URL from environment." ), method: z .enum(["GET", "POST", "PUT", "PATCH", "DELETE"]) .optional() .default("GET") .describe("HTTP method for the API call"), requestBody: z .any() .optional() .describe( "Request body for POST/PUT/PATCH requests (will be JSON stringified)" ), queryParams: z .record(z.string()) .optional() .describe("Query parameters as key-value pairs"), includeAuthToken: z .boolean() .optional() .describe("Whether to include auth token"), }, async (params) => { console.log(`[api.request] - Making request to: ${params.endpoint}`); try { const { endpoint, method = "GET", requestBody, queryParams, includeAuthToken, } = params; // Check required environment variables or config const apiBaseUrl = getConfigValue("API_BASE_URL"); const authTokenResolved = await resolveAuthToken(includeAuthToken); console.log(`[api.request] - API base URL: ${apiBaseUrl} ${endpoint}`); // Validate/resolve auth token if requested if (includeAuthToken === true) { if (typeof (authTokenResolved as any)?.error === "string") { return { content: [ { type: "text", text: (authTokenResolved as any).error, }, ], isError: true, }; } } if (!apiBaseUrl) { return { content: [ { type: "text", text: "Missing required environment variable. Please set API_BASE_URL in projects.json or as environment variable.", }, ], isError: true, }; } // Build the full URL let fullUrl = `${apiBaseUrl}${endpoint}`; // Add query parameters if provided if (queryParams && Object.keys(queryParams).length > 0) { const urlParams = new URLSearchParams(); for (const [key, value] of Object.entries(queryParams)) { urlParams.append(key, value); } fullUrl += `?${urlParams.toString()}`; } // Prepare headers const headers: Record<string, string> = { "Content-Type": "application/json", }; // Add Authorization header if auth token is provided if (includeAuthToken === true) { const tokenString = typeof authTokenResolved === "string" ? authTokenResolved : undefined; if (!tokenString) { return { content: [ { type: "text", text: "Auth token requested but could not be resolved.", }, ], isError: true, }; } headers["Authorization"] = `Bearer ${tokenString}`; } // Prepare fetch options const fetchOptions: RequestInit = { method: method, headers: headers, }; // Add request body for POST/PUT/PATCH if ( requestBody && ["POST", "PUT", "PATCH"].includes(method.toUpperCase()) ) { fetchOptions.body = JSON.stringify(requestBody); } console.log(`[api.request] - Making ${method} request to ${fullUrl}`); // Make the API call const startTime = Date.now(); const response = await fetch(fullUrl, fetchOptions); const endTime = Date.now(); console.log(`[api.request] - Response status: ${response.status}`); // Parse response let responseData; const contentType = response.headers.get("content-type"); if (contentType && contentType.includes("application/json")) { responseData = await response.json(); } else { responseData = await response.text(); } // Build response object const result: any = { data: responseData, details: { status: response.status, statusText: response.statusText, headers: Object.fromEntries(response.headers.entries()), timing: { requestDuration: endTime - startTime, timestamp: new Date().toISOString(), }, url: fullUrl, method: method, }, }; return { content: [ { type: "text", text: JSON.stringify( { success: response.ok, method, url: fullUrl, responseDetails: result.details, data: result.data, }, null, 2 ), }, ], }; } catch (error) { console.error("[api.request] - Error:", error); return { content: [ { type: "text", text: `Error executing API call: ${ error instanceof Error ? error.message : String(error) }`, }, ], isError: true, }; } } );
- The core handler function for 'api.request' tool. Resolves API_BASE_URL from config, optionally retrieves auth token from browser storage, constructs full URL with query params, performs HTTP request via fetch, and returns structured response with status, headers, timing, and data.async (params) => { console.log(`[api.request] - Making request to: ${params.endpoint}`); try { const { endpoint, method = "GET", requestBody, queryParams, includeAuthToken, } = params; // Check required environment variables or config const apiBaseUrl = getConfigValue("API_BASE_URL"); const authTokenResolved = await resolveAuthToken(includeAuthToken); console.log(`[api.request] - API base URL: ${apiBaseUrl} ${endpoint}`); // Validate/resolve auth token if requested if (includeAuthToken === true) { if (typeof (authTokenResolved as any)?.error === "string") { return { content: [ { type: "text", text: (authTokenResolved as any).error, }, ], isError: true, }; } } if (!apiBaseUrl) { return { content: [ { type: "text", text: "Missing required environment variable. Please set API_BASE_URL in projects.json or as environment variable.", }, ], isError: true, }; } // Build the full URL let fullUrl = `${apiBaseUrl}${endpoint}`; // Add query parameters if provided if (queryParams && Object.keys(queryParams).length > 0) { const urlParams = new URLSearchParams(); for (const [key, value] of Object.entries(queryParams)) { urlParams.append(key, value); } fullUrl += `?${urlParams.toString()}`; } // Prepare headers const headers: Record<string, string> = { "Content-Type": "application/json", }; // Add Authorization header if auth token is provided if (includeAuthToken === true) { const tokenString = typeof authTokenResolved === "string" ? authTokenResolved : undefined; if (!tokenString) { return { content: [ { type: "text", text: "Auth token requested but could not be resolved.", }, ], isError: true, }; } headers["Authorization"] = `Bearer ${tokenString}`; } // Prepare fetch options const fetchOptions: RequestInit = { method: method, headers: headers, }; // Add request body for POST/PUT/PATCH if ( requestBody && ["POST", "PUT", "PATCH"].includes(method.toUpperCase()) ) { fetchOptions.body = JSON.stringify(requestBody); } console.log(`[api.request] - Making ${method} request to ${fullUrl}`); // Make the API call const startTime = Date.now(); const response = await fetch(fullUrl, fetchOptions); const endTime = Date.now(); console.log(`[api.request] - Response status: ${response.status}`); // Parse response let responseData; const contentType = response.headers.get("content-type"); if (contentType && contentType.includes("application/json")) { responseData = await response.json(); } else { responseData = await response.text(); } // Build response object const result: any = { data: responseData, details: { status: response.status, statusText: response.statusText, headers: Object.fromEntries(response.headers.entries()), timing: { requestDuration: endTime - startTime, timestamp: new Date().toISOString(), }, url: fullUrl, method: method, }, }; return { content: [ { type: "text", text: JSON.stringify( { success: response.ok, method, url: fullUrl, responseDetails: result.details, data: result.data, }, null, 2 ), }, ], }; } catch (error) { console.error("[api.request] - Error:", error); return { content: [ { type: "text", text: `Error executing API call: ${ error instanceof Error ? error.message : String(error) }`, }, ], isError: true, }; } }
- Input schema using Zod for validating parameters of the 'api.request' tool.endpoint: z .string() .describe( "The API endpoint path (e.g., '/api/users', '/auth/profile'). Will be combined with API_BASE_URL from environment." ), method: z .enum(["GET", "POST", "PUT", "PATCH", "DELETE"]) .optional() .default("GET") .describe("HTTP method for the API call"), requestBody: z .any() .optional() .describe( "Request body for POST/PUT/PATCH requests (will be JSON stringified)" ), queryParams: z .record(z.string()) .optional() .describe("Query parameters as key-value pairs"), includeAuthToken: z .boolean() .optional() .describe("Whether to include auth token"), },
- Helper function used by the handler to resolve authentication tokens from browser storage via the extension, with caching and JWT expiration handling.async function resolveAuthToken( includeAuthToken?: boolean ): Promise<string | undefined | { error: string }> { if (includeAuthToken !== true) return undefined; const projectName = getActiveProjectName() || "default-project"; // 1) Use cached token if valid const cached = getCachedToken(projectName); if (cached) return cached; // 2) Mandatory dynamic retrieval via extension (no fallback) const storageType = getConfigValue("AUTH_STORAGE_TYPE"); const tokenKey = getConfigValue("AUTH_TOKEN_KEY"); if (!storageType || !tokenKey) { return { error: "Auth token retrieval not configured. Set AUTH_STORAGE_TYPE, AUTH_TOKEN_KEY, and optional AUTH_ORIGIN in projects.json.", }; } const token = await retrieveTokenViaExtension(); if (!token) { return { error: "Failed to retrieve auth token from configured browser storage. Ensure the target app is open and DevTools extension connected.", }; } const ttlSecondsRaw = getConfigValue("API_AUTH_TOKEN_TTL_SECONDS"); let expiresAtMs: number | undefined; if (ttlSecondsRaw && !isNaN(Number(ttlSecondsRaw))) { expiresAtMs = Date.now() + Number(ttlSecondsRaw) * 1000; } else { expiresAtMs = decodeJwtExpirationMs(token) || Date.now() + 5 * 60 * 1000; } authTokenCacheByProject[projectName] = { token, expiresAtMs }; return token; }