added duckduckgo
This commit is contained in:
parent
018c7d87cd
commit
9b105232c2
5 changed files with 246 additions and 0 deletions
|
|
@ -17,6 +17,7 @@
|
||||||
"publish-all": "npm publish --workspaces --access public && npm publish --access public"
|
"publish-all": "npm publish --workspaces --access public && npm publish --access public"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@modelcontextprotocol/server-duckduckgo": "*",
|
||||||
"@modelcontextprotocol/server-everything": "*",
|
"@modelcontextprotocol/server-everything": "*",
|
||||||
"@modelcontextprotocol/server-gdrive": "*",
|
"@modelcontextprotocol/server-gdrive": "*",
|
||||||
"@modelcontextprotocol/server-postgres": "*",
|
"@modelcontextprotocol/server-postgres": "*",
|
||||||
|
|
|
||||||
39
src/duckduckgo/README.md
Normal file
39
src/duckduckgo/README.md
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
# DuckDuckGo MCP Server
|
||||||
|
MCP server providing search functionality via DuckDuckGo's HTML interface.
|
||||||
|
|
||||||
|
## Core Concepts
|
||||||
|
### Resources
|
||||||
|
Single resource endpoint for search results:
|
||||||
|
```duckduckgo://search```
|
||||||
|
|
||||||
|
### Tools
|
||||||
|
Search tool with configurable result count:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "search",
|
||||||
|
"arguments": {
|
||||||
|
"query": "your search query",
|
||||||
|
"numResults": 5 // optional, defaults to 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Details
|
||||||
|
- HTML scraping via JSDOM
|
||||||
|
- Clean result formatting with titles, snippets, and URLs
|
||||||
|
- Error handling for network/parsing issues
|
||||||
|
- Request rate limiting built-in via DuckDuckGo's interface
|
||||||
|
|
||||||
|
## Usage Example
|
||||||
|
```typescript
|
||||||
|
// Search tool response format
|
||||||
|
{
|
||||||
|
content: [{
|
||||||
|
type: "text",
|
||||||
|
text: "Title: Example Result\nSnippet: Result description...\nURL: https://..."
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
Requires Node.js and npm. Uses ES modules.
|
||||||
166
src/duckduckgo/index.ts
Normal file
166
src/duckduckgo/index.ts
Normal file
|
|
@ -0,0 +1,166 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||||||
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||||
|
import {
|
||||||
|
CallToolRequestSchema,
|
||||||
|
ListResourcesRequestSchema,
|
||||||
|
ListToolsRequestSchema,
|
||||||
|
ReadResourceRequestSchema,
|
||||||
|
} from "@modelcontextprotocol/sdk/types.js";
|
||||||
|
import fetch from "node-fetch";
|
||||||
|
import { JSDOM } from "jsdom";
|
||||||
|
|
||||||
|
const server = new Server(
|
||||||
|
{
|
||||||
|
name: "example-servers/duckduckgo",
|
||||||
|
version: "0.1.0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
capabilities: {
|
||||||
|
tools: {},
|
||||||
|
resources: {}, // Required since we're using ListResourcesRequestSchema
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// 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",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add Read Resource Handler
|
||||||
|
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
||||||
|
if (request.params.uri.toString() === "duckduckgo://search") {
|
||||||
|
return {
|
||||||
|
contents: [
|
||||||
|
{
|
||||||
|
uri: "duckduckgo://search",
|
||||||
|
mimeType: "text/plain",
|
||||||
|
text: "DuckDuckGo search interface",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
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(CallToolRequestSchema, async (request) => {
|
||||||
|
if (request.params.name === "search") {
|
||||||
|
try {
|
||||||
|
const { query, numResults = 5 } = 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 formattedResults = results
|
||||||
|
.map(
|
||||||
|
(result) =>
|
||||||
|
`Title: ${result.title}\nSnippet: ${result.snippet}\nURL: ${result.url}\n`,
|
||||||
|
)
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: formattedResults || "No results found.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: `Error performing search: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
isError: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Unknown tool: ${request.params.name}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function runServer() {
|
||||||
|
const transport = new StdioServerTransport();
|
||||||
|
await server.connect(transport);
|
||||||
|
console.error("DuckDuckGo MCP Server running on stdio");
|
||||||
|
}
|
||||||
|
|
||||||
|
runServer().catch((error) => {
|
||||||
|
console.error("Fatal error running server:", error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
30
src/duckduckgo/package.json
Normal file
30
src/duckduckgo/package.json
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"name": "@modelcontextprotocol/server-duckduckgo",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "MCP server for DuckDuckGo search",
|
||||||
|
"license": "MIT",
|
||||||
|
"author": "Anthropic, PBC (https://anthropic.com)",
|
||||||
|
"homepage": "https://modelcontextprotocol.io",
|
||||||
|
"bugs": "https://github.com/modelcontextprotocol/servers/issues",
|
||||||
|
"type": "module",
|
||||||
|
"bin": {
|
||||||
|
"mcp-server-duckduckgo": "dist/index.js"
|
||||||
|
},
|
||||||
|
"files": ["dist"],
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc && shx chmod +x dist/*.js",
|
||||||
|
"prepare": "npm run build",
|
||||||
|
"watch": "tsc --watch"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@modelcontextprotocol/sdk": "0.5.0",
|
||||||
|
"jsdom": "^24.0.0",
|
||||||
|
"node-fetch": "^3.3.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"shx": "^0.3.4",
|
||||||
|
"typescript": "^5.6.2",
|
||||||
|
"@types/jsdom": "^21.1.6",
|
||||||
|
"@types/node": "^20.10.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/duckduckgo/tsconfig.json
Normal file
10
src/duckduckgo/tsconfig.json
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "."
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./**/*.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue