search_web
Search the web using DuckDuckGo or SerpAPI to find relevant information and web pages based on your search queries.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | ||
| limit | No |
Implementation Reference
- src/server.js:75-92 (handler)The handler function for the 'search_web' tool. It first tries to use SerpAPI for Google search results, falls back to scraping DuckDuckGo if SerpAPI fails or is unavailable, formats the results as a numbered list of titles and URLs, and returns them as text content.async (input) => { const limit = input.limit ?? SEARCH_RESULTS_LIMIT_DEFAULT; let results = null; try { results = await serpApiSearch(input.query, limit); } catch (e) { } if (!results) { results = await ddgSearch(input.query, limit); } const text = results .map((r, i) => `${i + 1}. ${r.title}\n${r.url}`) .join("\n\n"); return { content: [{ type: "text", text: text || "No results" }], }; }
- src/server.js:71-74 (schema)The input schema for the 'search_web' tool, validated with Zod: requires a non-empty query string, optional limit integer between 1 and 10.{ query: z.string().min(1), limit: z.number().int().min(1).max(10).optional(), },
- src/server.js:69-93 (registration)The registration of the 'search_web' tool on the MCP server, specifying name, input schema, and handler function.server.tool( "search_web", { query: z.string().min(1), limit: z.number().int().min(1).max(10).optional(), }, async (input) => { const limit = input.limit ?? SEARCH_RESULTS_LIMIT_DEFAULT; let results = null; try { results = await serpApiSearch(input.query, limit); } catch (e) { } if (!results) { results = await ddgSearch(input.query, limit); } const text = results .map((r, i) => `${i + 1}. ${r.title}\n${r.url}`) .join("\n\n"); return { content: [{ type: "text", text: text || "No results" }], }; } );
- src/server.js:53-67 (helper)Helper function for searching via SerpAPI (Google engine), requires SERPAPI_KEY env var, returns up to limit organic results.async function serpApiSearch(query, limit) { const apiKey = process.env.SERPAPI_KEY; if (!apiKey) return null; const params = new URLSearchParams({ engine: "google", q: query, num: String(Math.min(limit, 10)), api_key: apiKey, }); const res = await fetch(`https://serpapi.com/search.json?${params.toString()}`); if (!res.ok) throw new Error(`SerpAPI error: ${res.status}`); const data = await res.json(); const organic = Array.isArray(data.organic_results) ? data.organic_results : []; return organic.slice(0, limit).map((r) => ({ title: r.title || "", url: r.link || "" })); }
- src/server.js:16-51 (helper)Fallback helper function: scrapes DuckDuckGo HTML results using JSDOM, extracts and resolves URLs from links.async function ddgSearch(query, limit) { const params = new URLSearchParams({ q: query, kl: "us-en" }); const res = await fetch(`https://duckduckgo.com/html/?${params.toString()}`, { headers: { "User-Agent": "Mozilla/5.0 (compatible; MCP-Web-Tools/0.1; +https://example.com)", "Accept-Language": "en-US,en;q=0.9", }, }); const html = await res.text(); const dom = new JSDOM(html); const doc = dom.window.document; const results = []; const nodes = doc.querySelectorAll(".result__title a.result__a"); for (const a of nodes) { const title = a.textContent?.trim() || ""; let href = a.getAttribute("href") || ""; if (!href) continue; let url = href; try { if (href.startsWith("/l/?") || href.includes("duckduckgo.com/l/?")) { const full = href.startsWith("http") ? href : `https://duckduckgo.com${href}`; const u = new URL(full); const target = u.searchParams.get("uddg"); if (target) url = decodeURIComponent(target); } else if (href.startsWith("//")) { url = `https:${href}`; } else if (href.startsWith("/")) { url = `https://duckduckgo.com${href}`; } } catch { } if (!url) continue; results.push({ title, url }); if (results.length >= limit) break; } return results; }