add mcp/cf-search-mcp.js and update README & HTML

This commit is contained in:
Yrobot
2025-12-08 16:08:13 +08:00
parent 2140ff8692
commit 7975ee1a67
5 changed files with 407 additions and 0 deletions

32
.npmignore Normal file
View File

@@ -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*

View File

@@ -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 会返回来自多个搜索引擎的聚合结果,包括标题、描述和链接。
## 注意与提醒 ## 注意与提醒
### 🚨 重要提示 ### 🚨 重要提示

189
mcp/cf-search-mcp.js Normal file
View File

@@ -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);
});

38
package.json Normal file
View File

@@ -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"
}
}

View File

@@ -445,6 +445,87 @@ export function getSearchHtml() {
</div> </div>
</div> </div>
<!-- MCP 集成 -->
<div class="mt-8 rounded-2xl border border-purple-200 bg-purple-50 dark:border-purple-800/40 dark:bg-purple-900/10 p-6">
<h2 class="text-lg font-semibold text-purple-900 dark:text-purple-100 mb-4">
🤖 MCP 集成
</h2>
<p class="text-sm text-purple-800 dark:text-purple-200 mb-4">
通过 MCP (Model Context Protocol) 让 AI 助手 (如 Claude) 直接调用你的搜索服务,获取实时搜索结果。
</p>
<div class="space-y-4">
<!-- 步骤 1 -->
<div class="rounded-lg bg-white dark:bg-purple-900/20 p-4 border border-purple-200 dark:border-purple-800/40">
<div class="flex items-start">
<span class="flex-shrink-0 w-6 h-6 bg-purple-200 dark:bg-purple-800 text-purple-900 dark:text-purple-100 rounded-full flex items-center justify-center text-xs font-semibold mr-3">1</span>
<div class="flex-1">
<p class="text-sm font-medium text-purple-900 dark:text-purple-100 mb-2">添加 MCP 服务器配置</p>
<p class="text-xs text-purple-700 dark:text-purple-300 mb-3">
编辑配置文件 (<a href="https://modelcontextprotocol.io/quickstart/user" target="_blank" class="underline hover:text-purple-900 dark:hover:text-purple-100">配置指南</a>):
</p>
<div class="space-y-1 text-xs text-purple-700 dark:text-purple-300 mb-3">
<p><strong>Claude Code:</strong> <code class="px-1 py-0.5 bg-purple-100 dark:bg-purple-900/30 rounded">~/.claude/config.json</code> 或 <code class="px-1 py-0.5 bg-purple-100 dark:bg-purple-900/30 rounded">~/.claude.json</code></p>
<p><strong>Claude Desktop (macOS):</strong> <code class="px-1 py-0.5 bg-purple-100 dark:bg-purple-900/30 rounded">~/Library/Application Support/Claude/claude_desktop_config.json</code></p>
<p><strong>Claude Desktop (Windows):</strong> <code class="px-1 py-0.5 bg-purple-100 dark:bg-purple-900/30 rounded">%APPDATA%\\Claude\\claude_desktop_config.json</code></p>
</div>
<div class="rounded bg-purple-100 dark:bg-purple-900/30 p-3">
<pre class="text-xs overflow-x-auto text-purple-900 dark:text-purple-100"><code id='mcp-config-json'></code></pre>
</div>
</div>
</div>
</div>
<!-- 步骤 2 -->
<div class="rounded-lg bg-white dark:bg-purple-900/20 p-4 border border-purple-200 dark:border-purple-800/40">
<div class="flex items-start">
<span class="flex-shrink-0 w-6 h-6 bg-purple-200 dark:bg-purple-800 text-purple-900 dark:text-purple-100 rounded-full flex items-center justify-center text-xs font-semibold mr-3">2</span>
<div class="flex-1">
<p class="text-sm font-medium text-purple-900 dark:text-purple-100 mb-2">重启应用</p>
<p class="text-xs text-purple-700 dark:text-purple-300">
保存配置后重启 Claude Code 或 Claude Desktop。
</p>
</div>
</div>
</div>
<!-- 步骤 3 -->
<div class="rounded-lg bg-white dark:bg-purple-900/20 p-4 border border-purple-200 dark:border-purple-800/40">
<div class="flex items-start">
<span class="flex-shrink-0 w-6 h-6 bg-purple-200 dark:bg-purple-800 text-purple-900 dark:text-purple-100 rounded-full flex items-center justify-center text-xs font-semibold mr-3">3</span>
<div class="flex-1">
<p class="text-sm font-medium text-purple-900 dark:text-purple-100 mb-2">验证安装</p>
<div class="text-xs text-purple-700 dark:text-purple-300 space-y-1">
<p>• 在 Claude Code 中运行 <code class="px-1 py-0.5 bg-purple-100 dark:bg-purple-900/30 rounded">/mcp</code> 命令,应该能看到 <code class="px-1 py-0.5 bg-purple-100 dark:bg-purple-900/30 rounded">cloudflare-search</code> 工具</p>
<p>• 或 使用 <code class="px-1 py-0.5 bg-purple-100 dark:bg-purple-900/30 rounded">claude mcp list</code>, 看到 <code class="px-1 py-0.5 bg-purple-100 dark:bg-purple-900/30 rounded">cloudflare-search: ... - ✓ Connected</code> 说明配置成功</p>
</div>
</div>
</div>
</div>
<!-- 使用示例 -->
<div class="rounded-lg bg-white dark:bg-purple-900/20 p-4 border border-purple-200 dark:border-purple-800/40">
<p class="text-sm font-medium text-purple-900 dark:text-purple-100 mb-2">💬 使用示例</p>
<div class="space-y-2 text-xs text-purple-700 dark:text-purple-300">
<div class="rounded bg-purple-100 dark:bg-purple-900/30 p-2">
<code>用 cloudflare-search 搜索 "Cloudflare Workers 最佳实践"</code>
</div>
<div class="rounded bg-purple-100 dark:bg-purple-900/30 p-2">
<code>用 cloudflare-search 搜索 "Next.js 14 新特性"</code>
</div>
<p class="pt-2">AI 会返回来自多个搜索引擎的聚合结果,包括标题、描述和链接。</p>
</div>
</div>
</div>
<div class="mt-4 pt-4 border-t border-purple-200 dark:border-purple-800/40">
<p class="text-xs text-purple-700 dark:text-purple-300">
📦 NPM 包: <a href="https://www.npmjs.com/package/@yrobot/cf-search-mcp" target="_blank" class="underline hover:text-purple-900 dark:hover:text-purple-100">@yrobot/cf-search-mcp</a> |
📚 MCP 文档: <a href="https://modelcontextprotocol.io" target="_blank" class="underline hover:text-purple-900 dark:hover:text-purple-100">modelcontextprotocol.io</a>
</p>
</div>
</div>
<!-- 功能特性 --> <!-- 功能特性 -->
<div class="mt-16 grid grid-cols-2 gap-4 sm:grid-cols-4"> <div class="mt-16 grid grid-cols-2 gap-4 sm:grid-cols-4">
<div class="flex items-center text-sm text-zinc-600 dark:text-zinc-400"> <div class="flex items-center text-sm text-zinc-600 dark:text-zinc-400">
@@ -525,6 +606,18 @@ export function getSearchHtml() {
document.getElementById('apiExample1').textContent = currentOrigin + '/search?q=cloudflare' + tokenParam; 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('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) { document.getElementById('searchForm').addEventListener('submit', async function(event) {