Skip to main content
Glama

fetch

Execute HTTP requests with customizable methods, headers, body, and options to retrieve or send data to web resources.

Instructions

Perform HTTP requests with full control over method, headers, body, and other fetch options

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
urlYesThe URL to fetch from
methodNoThe HTTP method to use. Defaults to GETGET
headersNoHTTP headers to include in the request as key-value pairs
bodyNoThe request body. Can be a string, JSON, or form data
modeNoThe mode for the request (cors, no-cors, or same-origin)
credentialsNoWhether to include credentials with the request
cacheNoThe cache mode for the request
redirectNoHow to handle redirects. Defaults to followfollow
referrerNoThe referrer to send with the request
referrerPolicyNoThe referrer policy for the request
signalNoAn AbortSignal to abort the request
timeoutNoRequest timeout in milliseconds
followRedirectsNoWhether to follow redirects. Defaults to true

Implementation Reference

  • Main handler function for the 'fetch' tool. Performs HTTP requests using globalThis.fetch, validates inputs, handles timeouts, body content types, parses responses (JSON/text/binary), and returns structured results or errors.
    export default async function fetch(params: InferSchema<typeof schema>) {
      try {
        const { url, method, headers, body, timeout, followRedirects, signal, ...fetchOptions } = params
    
        // Validate URL
        if (!url || url.trim() === '') {
          throw new TypeError('Invalid URL: URL cannot be empty');
        }
    
        try {
          new URL(url);
        } catch {
          throw new TypeError('Invalid URL: Not a valid URL format');
        }
    
        // Build fetch options
        const options: RequestInit = {
          method,
          headers: headers || {},
          ...fetchOptions,
        }
    
        // Handle followRedirects parameter
        if (followRedirects === false) {
          options.redirect = 'manual';
        }
    
        // Add body if provided and method supports it
        if (body && ["POST", "PUT", "PATCH", "DELETE"].includes(method)) {
          options.body = body
    
          // Ensure headers is a proper object for type safety
          const requestHeaders = options.headers as Record<string, string>
    
          // Auto-detect content type if not provided
          if (!requestHeaders["Content-Type"] && !requestHeaders["content-type"]) {
            try {
              JSON.parse(body)
              requestHeaders["Content-Type"] = "application/json"
            } catch {
              // If not JSON, assume plain text
              requestHeaders["Content-Type"] = "text/plain"
            }
          }
        }
    
        // Create abort controller for timeout
        let abortController: AbortController | undefined
        let timeoutId: NodeJS.Timeout | undefined
    
        if (timeout) {
          abortController = new AbortController()
          options.signal = abortController.signal
    
          timeoutId = setTimeout(() => {
            abortController?.abort()
          }, timeout)
        }
    
        // Perform the fetch
        const startTime = Date.now()
        const response = await globalThis.fetch(url, options)
        const responseTime = Date.now() - startTime
    
        // Clear timeout if set
        if (timeoutId) {
          clearTimeout(timeoutId)
        }
    
        // Get response headers
        const responseHeaders: Record<string, string> = {}
        response.headers.forEach((value, key) => {
          responseHeaders[key] = value
        })
    
        // Determine response content type
        const contentType = response.headers.get("content-type") || ""
        let responseBody: any
    
        try {
          if (contentType.includes("application/json")) {
            responseBody = await response.json()
          } else if (contentType.includes("text/") || contentType.includes("application/xml")) {
            responseBody = await response.text()
          } else {
            // For binary data, convert to base64
            const buffer = await response.arrayBuffer()
            responseBody = {
              type: "binary",
              size: buffer.byteLength,
              base64: Buffer.from(buffer).toString("base64")
            }
          }
        } catch (error) {
          responseBody = {
            error: "Failed to parse response body",
            message: error instanceof Error ? error.message : "Unknown error"
          }
        }
    
        return JSON.stringify({
          success: true,
          status: response.status,
          statusText: response.statusText,
          headers: responseHeaders,
          body: responseBody,
          url: response.url,
          redirected: response.redirected,
          responseTime: `${responseTime}ms`,
          request: {
            method,
            url: params.url,
            headers: headers || {}
          }
        }, null, 2)
    
      } catch (error) {
        // Handle different error types
        let errorMessage = "Unknown error occurred"
        let errorType = "UnknownError"
    
        if (error instanceof Error) {
          errorMessage = error.message
    
          if (error.name === "AbortError") {
            errorType = "TimeoutError"
            errorMessage = `Request timed out after ${params.timeout}ms`
          } else if (error.message.includes("Failed to fetch") || error.message.includes("ECONNREFUSED") || error.message.includes("ENOTFOUND")) {
            errorType = "NetworkError"
            errorMessage = "Network error - could not connect to the server"
          } else if (error.message.includes("Invalid URL") || error.message.includes("invalid URL")) {
            errorType = "InvalidURLError"
          } else if (error instanceof TypeError && (error.message.includes("fetch") || error.message.includes("URL"))) {
            errorType = "InvalidURLError"
          }
        }
    
        return JSON.stringify({
          success: false,
          error: errorMessage,
          errorType,
          request: {
            method: params.method,
            url: params.url,
            headers: params.headers || {}
          }
        }, null, 2)
      }
    }
  • Input schema for the fetch tool using Zod, defining parameters like url, method, headers, body, and various fetch options.
    export const schema = {
      url: z.string().describe("The URL to fetch from"),
      method: z.enum(["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"])
        .optional()
        .default("GET")
        .describe("The HTTP method to use. Defaults to GET"),
      headers: z.record(z.string())
        .optional()
        .describe("HTTP headers to include in the request as key-value pairs"),
      body: z.string()
        .optional()
        .describe("The request body. Can be a string, JSON, or form data"),
      mode: z.enum(["cors", "no-cors", "same-origin"])
        .optional()
        .describe("The mode for the request (cors, no-cors, or same-origin)"),
      credentials: z.enum(["omit", "same-origin", "include"])
        .optional()
        .describe("Whether to include credentials with the request"),
      cache: z.enum(["default", "no-store", "reload", "no-cache", "force-cache", "only-if-cached"])
        .optional()
        .describe("The cache mode for the request"),
      redirect: z.enum(["follow", "error", "manual"])
        .optional()
        .default("follow")
        .describe("How to handle redirects. Defaults to follow"),
      referrer: z.string()
        .optional()
        .describe("The referrer to send with the request"),
      referrerPolicy: z.enum([
        "no-referrer",
        "no-referrer-when-downgrade",
        "origin",
        "origin-when-cross-origin",
        "same-origin",
        "strict-origin",
        "strict-origin-when-cross-origin",
        "unsafe-url"
      ])
        .optional()
        .describe("The referrer policy for the request"),
      signal: z.any()
        .optional()
        .describe("An AbortSignal to abort the request"),
      timeout: z.number()
        .optional()
        .describe("Request timeout in milliseconds"),
      followRedirects: z.boolean()
        .optional()
        .default(true)
        .describe("Whether to follow redirects. Defaults to true"),
    }
  • Tool metadata registration defining the name 'fetch', description, and annotations for the MCP tool.
    export const metadata: ToolMetadata = {
      name: "fetch",
      description: "Perform HTTP requests with full control over method, headers, body, and other fetch options",
      annotations: {
        title: "HTTP Fetch",
        readOnlyHint: false,
        destructiveHint: false,
        idempotentHint: false,
      },
    }
Behavior3/5

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

Annotations provide readOnlyHint=false, idempotentHint=false, and destructiveHint=false, indicating this is a non-idempotent, non-destructive operation that may have side effects. The description adds context about 'full control' over HTTP aspects, which hints at flexibility but doesn't disclose behavioral traits like rate limits, error handling, or response formats. No contradiction with annotations exists, but the description offers minimal additional behavioral insight beyond what annotations already convey.

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 a single, efficient sentence that front-loads the core purpose without unnecessary elaboration. Every word earns its place by summarizing the tool's capabilities succinctly. It avoids redundancy and is appropriately sized for a general-purpose HTTP tool.

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?

Given the tool's complexity (13 parameters, no output schema) and rich schema coverage, the description is adequate but incomplete. It covers the 'what' (perform HTTP requests) but lacks context on 'why' or 'how' to use it effectively, such as error handling, response interpretation, or integration with sibling tools. The absence of an output schema means the description should ideally hint at return values, but it doesn't, leaving gaps for the agent.

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?

Schema description coverage is 100%, with detailed descriptions for all 13 parameters including enums and defaults. The description adds no parameter-specific semantics beyond stating 'full control over method, headers, body, and other fetch options,' which merely echoes the schema's scope. Since the schema comprehensively documents parameters, the baseline score of 3 is appropriate, as the description doesn't enhance understanding of individual parameters.

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

Purpose4/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: 'Perform HTTP requests with full control over method, headers, body, and other fetch options.' It specifies the verb ('perform HTTP requests') and resource (HTTP endpoints via URL), but doesn't distinguish it from potential sibling tools like 'graphql' or 'socket' that might also perform network requests. The description is accurate but lacks sibling differentiation.

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

Usage Guidelines2/5

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

The description provides no guidance on when to use this tool versus alternatives. It doesn't mention when to choose 'fetch' over 'graphql' or 'socket' for network operations, nor does it specify prerequisites like authentication requirements or appropriate use cases. The agent must infer usage from the tool name and parameters alone.

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/matiasngf/mcp-fetch'

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