diff --git a/src/duckduckgo/index.ts b/src/duckduckgo/index.ts index e463142d..7125ce02 100644 --- a/src/duckduckgo/index.ts +++ b/src/duckduckgo/index.ts @@ -7,10 +7,33 @@ import { ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, + Tool, } from "@modelcontextprotocol/sdk/types.js"; import fetch from "node-fetch"; import { JSDOM } from "jsdom"; +const SEARCH_TOOL: Tool = { + name: "duckduckgo_search", + description: + "Performs a search using DuckDuckGo and returns the top search results. " + + "Returns titles, snippets, and URLs of the search results. ", + inputSchema: { + type: "object", + properties: { + query: { + type: "string", + description: "The search query to look up", + }, + numResults: { + type: "number", + description: "Number of results to return (default: 10)", + default: 10, + }, + }, + required: ["query"], + }, +}; + const server = new Server( { name: "example-servers/duckduckgo", @@ -19,25 +42,21 @@ const server = new Server( { capabilities: { tools: {}, - resources: {}, // Required since we're using ListResourcesRequestSchema + resources: {}, }, }, ); -// Add Resources List Handler - Important for showing up in the tools list -server.setRequestHandler(ListResourcesRequestSchema, async () => { - return { - resources: [ - { - uri: "duckduckgo://search", - mimeType: "text/plain", - name: "DuckDuckGo Search Results", - }, - ], - }; -}); +server.setRequestHandler(ListResourcesRequestSchema, async () => ({ + resources: [ + { + uri: "duckduckgo://search", + mimeType: "text/plain", + name: "DuckDuckGo Search Results", + }, + ], +})); -// Add Read Resource Handler server.setRequestHandler(ReadResourceRequestSchema, async (request) => { if (request.params.uri.toString() === "duckduckgo://search") { return { @@ -53,76 +72,57 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => { throw new Error("Resource not found"); }); -server.setRequestHandler(ListToolsRequestSchema, async () => { - return { - tools: [ - { - name: "search", - description: - "Performs a search using DuckDuckGo and returns the top search results. " + - "Returns titles, snippets, and URLs of the search results. " + - "Use this tool when you need to search for current information on the internet.", - inputSchema: { - type: "object", - properties: { - query: { - type: "string", - description: "The search query to look up", - }, - numResults: { - type: "number", - description: "Number of results to return (default: 5)", - default: 5, - }, - }, - required: ["query"], - }, - }, - ], +server.setRequestHandler(ListToolsRequestSchema, async () => ({ + tools: [SEARCH_TOOL], +})); + +async function performSearch(query: string, numResults: number = 10) { + const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`; + const headers = { + "User-Agent": + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", }; -}); + + const response = await fetch(url, { headers }); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const html = await response.text(); + const dom = new JSDOM(html); + const document = dom.window.document; + + const results = []; + const resultElements = document.querySelectorAll(".result"); + + for (let i = 0; i < Math.min(numResults, resultElements.length); i++) { + const result = resultElements[i]; + const titleElem = result.querySelector(".result__title"); + const snippetElem = result.querySelector(".result__snippet"); + const urlElem = result.querySelector(".result__url"); + + if (titleElem && snippetElem) { + results.push({ + title: titleElem.textContent?.trim() || "", + snippet: snippetElem.textContent?.trim() || "", + url: urlElem?.getAttribute("href") || "", + }); + } + } + + return results; +} server.setRequestHandler(CallToolRequestSchema, async (request) => { - if (request.params.name === "search") { + if (request.params.name === "duckduckgo_search") { try { - const { query, numResults = 5 } = request.params.arguments as { + const { query, numResults = 10 } = request.params.arguments as { query: string; numResults?: number; }; - const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`; - const headers = { - "User-Agent": - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", - }; - - const response = await fetch(url, { headers }); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const html = await response.text(); - const dom = new JSDOM(html); - const document = dom.window.document; - - const results = []; - const resultElements = document.querySelectorAll(".result"); - - for (let i = 0; i < Math.min(numResults, resultElements.length); i++) { - const result = resultElements[i]; - const titleElem = result.querySelector(".result__title"); - const snippetElem = result.querySelector(".result__snippet"); - const urlElem = result.querySelector(".result__url"); - - if (titleElem && snippetElem) { - results.push({ - title: titleElem.textContent?.trim() || "", - snippet: snippetElem.textContent?.trim() || "", - url: urlElem?.getAttribute("href") || "", - }); - } - } - + const results = await performSearch(query, numResults); + const formattedResults = results .map( (result) => @@ -137,6 +137,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { text: formattedResults || "No results found.", }, ], + isError: false, }; } catch (error) { return { @@ -151,7 +152,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { } } - throw new Error(`Unknown tool: ${request.params.name}`); + return { + content: [ + { + type: "text", + text: `Unknown tool: ${request.params.name}`, + }, + ], + isError: true, + }; }); async function runServer() { @@ -163,4 +172,4 @@ async function runServer() { runServer().catch((error) => { console.error("Fatal error running server:", error); process.exit(1); -}); +}); \ No newline at end of file