Skip to main content
Glama
Winds-AI

autonomous-frontend-browser-tools

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
NameRequiredDescriptionDefault
endpointYesThe API endpoint path (e.g., '/api/users', '/auth/profile'). Will be combined with API_BASE_URL from environment.
methodNoHTTP method for the API callGET
requestBodyNoRequest body for POST/PUT/PATCH requests (will be JSON stringified)
queryParamsNoQuery parameters as key-value pairs
includeAuthTokenNoWhether to include auth token

Implementation Reference

  • 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;
    }
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It mentions the authorization token mechanism and that it's a 'live HTTP request,' but doesn't cover important behavioral aspects like error handling, timeout behavior, rate limits, or what happens when the request fails. The description adds some context but leaves significant gaps.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is extremely concise and front-loaded with the core purpose in the first clause. Every sentence earns its place by providing essential context about authorization and usage guidelines without any fluff or redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 5-parameter tool with no annotations and no output schema, the description is somewhat incomplete. While it covers the basic purpose and usage context, it lacks information about response format, error handling, and other behavioral aspects that would be important for an API request tool. The 100% schema coverage helps, but the description itself could provide more contextual completeness.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 100% schema description coverage, the baseline is 3. The description doesn't add any parameter-specific information beyond what's already in the schema descriptions. It mentions authorization tokens generally but doesn't provide additional context about the parameters themselves.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Execute a live HTTP request to API_BASE_URL' with specific details about authorization tokens and browser storage. It distinguishes itself from sibling tools like 'api.searchEndpoints' by being the execution tool rather than a discovery tool.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit guidance on when to use this tool: 'Use after api.searchEndpoints or for known endpoints.' This gives clear context about the intended workflow and distinguishes it from other tools that don't involve API requests.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Winds-AI/Frontend-development-MCP-tools-public'

If you have feedback or need assistance with the MCP directory API, please join our Discord server