diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..20812a4 --- /dev/null +++ b/.npmignore @@ -0,0 +1,32 @@ +# Git +.git +.gitignore + +# IDE +.vscode/ +.idea/ +.claude/ + +# Cloudflare Workers files (not needed for MCP server) +.wrangler/ +wrangler.toml +worker.js +envs.js +utils/ +index.d.ts + +# Documentation assets +screenshot.png + +# Node +node_modules/ + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/README.md b/README.md index c8d926e..85c4ae6 100644 --- a/README.md +++ b/README.md @@ -274,6 +274,61 @@ const byEngine = data.results.reduce((acc, result) => { }, {}); ``` +## MCP 集成 + +通过 MCP (Model Context Protocol) 让 AI 助手直接调用你的搜索服务,获取实时搜索结果。 + +### 安装配置 + +#### 1. 添加 MCP 服务器配置 + +编辑配置文件([配置指南](https://modelcontextprotocol.io/quickstart/user)): + +**Claude Code**: `~/.claude/config.json` / `~/.claude.json` +**Claude Desktop macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json` +**Claude Desktop Windows**: `%APPDATA%\Claude\claude_desktop_config.json` + +```json +{ + "mcpServers": { + "cloudflare-search": { + "command": "npx", + "args": ["-y", "@yrobot/cf-search-mcp"], + "env": { + "CF_SEARCH_URL": "https://your-worker.workers.dev", + "CF_SEARCH_TOKEN": "your-token-here" + } + } + } +} +``` + +**环境变量说明**: + +- `CF_SEARCH_URL`: Worker 部署地址(必填) +- `CF_SEARCH_TOKEN`: 鉴权 Token(如果 Worker 配置了 TOKEN 则必填) + +#### 2. 重启应用 + +保存配置后重启 Claude Code 或 Claude Desktop。 + +#### 3. 验证安装 + +- 在 Claude Code 中运行 `/mcp` 命令,应该能看到 `cloudflare-search` 工具。 +- 或 使用 `claude mcp list`, 看到 `cloudflare-search: npx -y @yrobot/cf-search-mcp@latest - ✓ Connected` 说明配置成功 + +### 使用示例 + +``` +用 cloudflare-search 搜索 "Cloudflare Workers 最佳实践" +``` + +``` +用 cloudflare-search 搜索 "Next.js 14 新特性" +``` + +AI 会返回来自多个搜索引擎的聚合结果,包括标题、描述和链接。 + ## 注意与提醒 ### 🚨 重要提示 diff --git a/mcp/cf-search-mcp.js b/mcp/cf-search-mcp.js new file mode 100644 index 0000000..b47925f --- /dev/null +++ b/mcp/cf-search-mcp.js @@ -0,0 +1,189 @@ +#!/usr/bin/env node + +/** + * Cloudflare Search MCP Server + * + * This MCP server provides access to the Cloudflare Search API, + * allowing AI assistants to search across multiple search engines. + * + * Environment Variables: + * - CF_SEARCH_URL: The URL of your Cloudflare Search Worker (required) + * - CF_SEARCH_TOKEN: Authentication token for the search API (optional) + */ + +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { + ListToolsRequestSchema, + CallToolRequestSchema, +} from "@modelcontextprotocol/sdk/types.js"; + +// Get configuration from environment variables +const CF_SEARCH_URL = process.env.CF_SEARCH_URL; +const CF_SEARCH_TOKEN = process.env.CF_SEARCH_TOKEN; + +if (!CF_SEARCH_URL) { + console.error("Error: CF_SEARCH_URL environment variable is required"); + console.error("Example: export CF_SEARCH_URL=https://your-worker.workers.dev"); + process.exit(1); +} + +/** + * Call the Cloudflare Search API + */ +async function searchAPI(query, engines = null) { + try { + const params = new URLSearchParams({ q: query }); + + if (engines && engines.length > 0) { + params.append("engines", engines.join(",")); + } + + if (CF_SEARCH_TOKEN) { + params.append("token", CF_SEARCH_TOKEN); + } + + const url = `${CF_SEARCH_URL}/search?${params.toString()}`; + + const response = await fetch(url); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`API request failed: ${response.status} ${errorText}`); + } + + return await response.json(); + } catch (error) { + throw new Error(`Failed to search: ${error.message}`); + } +} + +/** + * Create and configure the MCP server + */ +const server = new Server( + { + name: "cloudflare-search", + version: "1.0.0", + }, + { + capabilities: { + tools: {}, + }, + } +); + +/** + * Handler for listing available tools + */ +server.setRequestHandler(ListToolsRequestSchema, async () => { + return { + tools: [ + { + name: "search", + description: + "Search across multiple search engines (Google, Brave, DuckDuckGo, Bing) and return aggregated results. " + + "This tool provides comprehensive search results from multiple sources, with source attribution for each result.", + inputSchema: { + type: "object", + properties: { + query: { + type: "string", + description: "The search query string", + }, + engines: { + type: "array", + items: { + type: "string", + enum: ["google", "brave", "duckduckgo", "bing"], + }, + description: + "Optional: Array of search engines to use. If not specified, uses default engines. " + + "Available engines: google, brave, duckduckgo, bing", + }, + }, + required: ["query"], + }, + }, + ], + }; +}); + +/** + * Handler for tool execution + */ +server.setRequestHandler(CallToolRequestSchema, async (request) => { + if (request.params.name !== "search") { + throw new Error(`Unknown tool: ${request.params.name}`); + } + + if (!request.params.arguments) { + throw new Error("Missing arguments"); + } + + const { query, engines } = request.params.arguments; + + if (!query || typeof query !== "string") { + throw new Error("Query must be a non-empty string"); + } + + try { + const result = await searchAPI(query, engines); + + // Format the results for better readability + const formattedResults = result.results + .map((item, index) => { + return `${index + 1}. [${item.engine.toUpperCase()}] ${item.title}\n ${item.description}\n ${item.url}`; + }) + .join("\n\n"); + + const summary = [ + `Search Query: "${result.query}"`, + `Total Results: ${result.number_of_results}`, + `Engines Used: ${result.enabled_engines.join(", ")}`, + result.unresponsive_engines.length > 0 + ? `Unresponsive Engines: ${result.unresponsive_engines.join(", ")}` + : null, + "", + "Results:", + formattedResults, + ] + .filter(Boolean) + .join("\n"); + + return { + content: [ + { + type: "text", + text: summary, + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: "text", + text: `Search failed: ${error.message}`, + }, + ], + isError: true, + }; + } +}); + +/** + * Start the server + */ +async function main() { + const transport = new StdioServerTransport(); + await server.connect(transport); + + console.error("Cloudflare Search MCP Server running on stdio"); + console.error(`Connected to: ${CF_SEARCH_URL}`); +} + +main().catch((error) => { + console.error("Fatal error:", error); + process.exit(1); +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000..ea06d23 --- /dev/null +++ b/package.json @@ -0,0 +1,38 @@ +{ + "name": "@yrobot/cf-search-mcp", + "version": "1.0.0", + "description": "MCP server for Cloudflare Search API - Aggregated search across multiple engines", + "type": "module", + "publishConfig": { + "access": "public" + }, + "main": "mcp/cf-search-mcp.js", + "bin": { + "cf-search-mcp": "./mcp/cf-search-mcp.js" + }, + "scripts": { + "start": "node mcp/cf-search-mcp.js" + }, + "keywords": [ + "mcp", + "search", + "cloudflare", + "api", + "google", + "brave", + "duckduckgo", + "bing" + ], + "author": "Yrobot", + "license": "GPL-3.0", + "repository": { + "type": "git", + "url": "https://github.com/Yrobot/cloudflare-search.git" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.0.4" + }, + "engines": { + "node": ">=18.0.0" + } +} diff --git a/utils/getHTML.js b/utils/getHTML.js index f7064fa..576276e 100644 --- a/utils/getHTML.js +++ b/utils/getHTML.js @@ -445,6 +445,87 @@ export function getSearchHtml() { + +
+

+ 🤖 MCP 集成 +

+

+ 通过 MCP (Model Context Protocol) 让 AI 助手 (如 Claude) 直接调用你的搜索服务,获取实时搜索结果。 +

+ +
+ +
+
+ 1 +
+

添加 MCP 服务器配置

+

+ 编辑配置文件 (配置指南): +

+
+

Claude Code: ~/.claude/config.json~/.claude.json

+

Claude Desktop (macOS): ~/Library/Application Support/Claude/claude_desktop_config.json

+

Claude Desktop (Windows): %APPDATA%\\Claude\\claude_desktop_config.json

+
+
+
+
+
+
+
+ + +
+
+ 2 +
+

重启应用

+

+ 保存配置后重启 Claude Code 或 Claude Desktop。 +

+
+
+
+ + +
+
+ 3 +
+

验证安装

+
+

• 在 Claude Code 中运行 /mcp 命令,应该能看到 cloudflare-search 工具

+

• 或 使用 claude mcp list, 看到 cloudflare-search: ... - ✓ Connected 说明配置成功

+
+
+
+
+ + +
+

💬 使用示例

+
+
+ 用 cloudflare-search 搜索 "Cloudflare Workers 最佳实践" +
+
+ 用 cloudflare-search 搜索 "Next.js 14 新特性" +
+

AI 会返回来自多个搜索引擎的聚合结果,包括标题、描述和链接。

+
+
+
+ +
+

+ 📦 NPM 包: @yrobot/cf-search-mcp | + 📚 MCP 文档: modelcontextprotocol.io +

+
+
+
@@ -525,6 +606,18 @@ export function getSearchHtml() { document.getElementById('apiExample1').textContent = currentOrigin + '/search?q=cloudflare' + tokenParam; document.getElementById('apiExample2').textContent = 'curl -X POST "' + currentOrigin + '/search" -d "q=cloudflare&engines=google,brave' + tokenBodyParam + '"'; + document.getElementById('mcp-config-json').innerHTML = \`{ + "mcpServers": { + "cloudflare-search": { + "command": "npx", + "args": ["-y", "@yrobot/cf-search-mcp"], + "env": { + "CF_SEARCH_URL": "\${currentOrigin}", + "CF_SEARCH_TOKEN": "\${TOKEN_ENABLED ? TOKEN_ENABLED : ""}" + } + } + } +}\` // 搜索表单提交 document.getElementById('searchForm').addEventListener('submit', async function(event) {