Skip to main content
Glama
rawveg

Ollama MCP Server

ollama_web_search

Search the web to retrieve current information and reduce model hallucinations by augmenting queries with real-time data.

Instructions

Perform a web search using Ollama's web search API. Augments models with latest information to reduce hallucinations. Requires OLLAMA_API_KEY environment variable.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryYesThe search query string
max_resultsNoMaximum number of results to return (1-10, default 5)
formatNojson

Implementation Reference

  • The main webSearch function that performs the actual web search by making an HTTP POST request to Ollama's web search API at https://ollama.com/api/web_search. Requires OLLAMA_API_KEY. Uses retryWithBackoff with configurable retries and fetchWithTimeout for reliability. Returns formatted response (JSON or markdown).
    export async function webSearch(
      ollama: Ollama,
      query: string,
      maxResults: number,
      format: ResponseFormat
    ): Promise<string> {
      // Web search requires direct API call as it's not in the SDK
      const apiKey = process.env.OLLAMA_API_KEY;
      if (!apiKey) {
        throw new Error(
          'OLLAMA_API_KEY environment variable is required for web search'
        );
      }
    
      return retryWithBackoff(
        async () => {
          const response = await fetchWithTimeout(
            'https://ollama.com/api/web_search',
            {
              method: 'POST',
              headers: {
                'Content-Type': 'application/json',
                Authorization: `Bearer ${apiKey}`,
              },
              body: JSON.stringify({
                query,
                max_results: maxResults,
              }),
            },
            WEB_API_TIMEOUT
          );
    
          if (!response.ok) {
            const retryAfter = response.headers.get('retry-after') ?? undefined;
            throw new HttpError(
              `Web search failed: ${response.status} ${response.statusText}`,
              response.status,
              retryAfter
            );
          }
    
          const data = await response.json();
          return formatResponse(JSON.stringify(data), format);
        },
        WEB_API_RETRY_CONFIG
      );
    }
  • The toolDefinition export for 'ollama_web_search' containing the handler function. The handler validates args using WebSearchInputSchema then delegates to the webSearch function.
    export const toolDefinition: ToolDefinition = {
      name: 'ollama_web_search',
      description:
        'Perform a web search using Ollama\'s web search API. Augments models with latest information to reduce hallucinations. Requires OLLAMA_API_KEY environment variable.',
      inputSchema: {
        type: 'object',
        properties: {
          query: {
            type: 'string',
            description: 'The search query string',
          },
          max_results: {
            type: 'number',
            description: 'Maximum number of results to return (1-10, default 5)',
            default: 5,
          },
          format: {
            type: 'string',
            enum: ['json', 'markdown'],
            default: 'json',
          },
        },
        required: ['query'],
      },
      handler: async (ollama: Ollama, args: Record<string, unknown>, format: ResponseFormat) => {
        const validated = WebSearchInputSchema.parse(args);
        return webSearch(ollama, validated.query, validated.max_results, format);
      },
    };
  • Zod schema WebSearchInputSchema for the ollama_web_search tool. Validates query (string, min 1), max_results (number 1-10, default 5), and format (json/markdown, default json).
     * Schema for ollama_web_search tool
     */
    export const WebSearchInputSchema = z.object({
      query: z.string().min(1),
      max_results: z.number().int().min(1).max(10).default(5),
      format: ResponseFormatSchema.default('json'),
    });
  • The autoloader mechanism that discovers all tools. It imports modules from the tools/ directory and collects any exported toolDefinition. This is how 'ollama_web_search' gets registered as a discoverable tool.
    export async function discoverTools(): Promise<ToolDefinition[]> {
      const toolsDir = join(__dirname, 'tools');
      const files = await readdir(toolsDir);
    
      // Filter for .js files (production) or .ts files (development)
      // Exclude test files and declaration files
      const toolFiles = files.filter(
        (file) =>
          (file.endsWith('.js') || file.endsWith('.ts')) &&
          !file.includes('.test.') &&
          !file.endsWith('.d.ts')
      );
    
      const tools: ToolDefinition[] = [];
    
      for (const file of toolFiles) {
        const toolPath = join(toolsDir, file);
        const module = await import(toolPath);
    
        // Check if module exports tool metadata
        if (module.toolDefinition) {
          tools.push(module.toolDefinition);
        }
      }
    
      return tools;
    }
  • fetchWithTimeout and retryWithBackoff helpers used by the webSearch function. fetchWithTimeout wraps fetch with AbortController-based timeout. retryWithBackoff implements exponential backoff with jitter for retrying transient errors (429, 500, 502, 503, 504).
    export async function fetchWithTimeout(
      url: string,
      options?: RequestInit,
      timeout: number = 30000
    ): Promise<Response> {
      const controller = new AbortController();
      const timeoutId = setTimeout(() => controller.abort(), timeout);
    
      try {
        const response = await fetch(url, {
          ...options,
          signal: controller.signal,
        });
        return response;
      } catch (error: unknown) {
        if (error instanceof Error && error.name === 'AbortError') {
          throw new Error(`Request to ${url} timed out after ${timeout}ms`);
        }
        throw error;
      } finally {
        // Always clear timeout to prevent memory leaks and race conditions.
        // If fetch completes exactly at timeout boundary, clearTimeout ensures
        // the timeout callback doesn't execute after we've already returned.
        clearTimeout(timeoutId);
      }
    }
    
    /**
     * Parse Retry-After header value to milliseconds
     * Supports both delay-seconds and HTTP-date formats
     * @param retryAfter - Retry-After header value
     * @returns Delay in milliseconds, or null if invalid
     */
    function parseRetryAfter(retryAfter: string | undefined): number | null {
      if (!retryAfter) {
        return null;
      }
    
      // Try parsing as seconds (integer)
      const seconds = parseInt(retryAfter, 10);
      if (!isNaN(seconds) && seconds >= 0) {
        return seconds * 1000;
      }
    
      // Try parsing as HTTP-date
      const date = new Date(retryAfter);
      if (!isNaN(date.getTime())) {
        const delay = date.getTime() - Date.now();
        // Only use if it's a future date
        return delay > 0 ? delay : 0;
      }
    
      return null;
    }
    
    /**
     * Retry a function with exponential backoff on rate limit errors
     *
     * Uses exponential backoff with full jitter to prevent thundering herd:
     * - Attempt 0: 0-1 seconds (random in range [0, 1s])
     * - Attempt 1: 0-2 seconds (random in range [0, 2s])
     * - Attempt 2: 0-4 seconds (random in range [0, 4s])
     * - And so on...
     *
     * Retry attempts are logged to console.debug for debugging and telemetry purposes,
     * including attempt number, delay, and error message.
     *
     * @param fn - The function to retry
     * @param options - Retry options (maxRetries: number of retry attempts after initial call)
     * @returns The result of the function
     * @throws The last error if max retries exceeded or non-retryable error
     */
    export async function retryWithBackoff<T>(
      fn: () => Promise<T>,
      options: RetryOptions = {}
    ): Promise<T> {
      const { maxRetries = 3, initialDelay = 1000, maxDelay = 10000 } = options;
    
      for (let attempt = 0; attempt <= maxRetries; attempt++) {
        try {
          return await fn();
        } catch (error) {
          // Only retry on transient errors (429, 500, 502, 503, 504)
          // Throw immediately for any other error type
          if (!isRetryableError(error)) {
            throw error;
          }
    
          // Throw if we've exhausted all retry attempts
          if (attempt === maxRetries) {
            throw error;
          }
    
          // Check if error has Retry-After header
          let delay: number;
          const retryAfterDelay = error instanceof HttpError ? parseRetryAfter(error.retryAfter) : null;
    
          if (retryAfterDelay !== null) {
            // Use Retry-After header value, capped at maxDelay
            delay = Math.min(retryAfterDelay, maxDelay);
          } else {
            // Calculate delay with exponential backoff and full jitter, capped at maxDelay
            const exponentialDelay = Math.min(
              initialDelay * Math.pow(2, attempt),
              maxDelay
            );
            // Full jitter: random value between 0 and exponentialDelay
            delay = Math.random() * exponentialDelay;
          }
    
          // Log retry attempt for debugging/telemetry
          console.debug(
            `Retry attempt ${attempt + 1}/${maxRetries} after ${Math.round(delay)}ms delay`,
            {
              delay: Math.round(delay),
              error: error instanceof Error ? error.message : String(error)
            }
          );
    
          await sleep(delay);
        }
      }
    
      // This line is unreachable due to the throw statements above,
      // but TypeScript requires it for exhaustiveness checking
      throw new Error('Unexpected: retry loop completed without return or throw');
    }
Behavior3/5

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

With no annotations, the description carries the full burden. It discloses that the tool accesses an external API and requires a key, but does not discuss potential side effects, rate limits, or data persistence. For a read-only search tool, this is adequate but not comprehensive.

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 two sentences, front-loaded with the primary action, and includes a key requirement. No unnecessary words or repetition.

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?

The description lacks information about the output format (e.g., what fields are returned in JSON or markdown). Without an output schema, the agent may not know how to parse results. For a tool with a simple interface, it is marginally complete.

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?

The input schema already describes all three parameters (query, max_results, format) with high coverage. The description adds no new semantic detail beyond the schema, earning a baseline score of 3.

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 function: performing a web search using Ollama's API. It explicitly distinguishes from sibling tools like ollama_chat and ollama_web_fetch by specifying it searches the web, not chat or single-page fetch.

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

Usage Guidelines4/5

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

It mentions the prerequisite (OLLAMA_API_KEY) and the benefit (reducing hallucinations), implying use when current information is needed. However, it doesn't explicitly state when not to use it or provide direct comparisons to alternatives like ollama_web_fetch.

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/rawveg/ollama-mcp'

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