add mcp/cf-search-mcp.js and update README & HTML
This commit is contained in:
32
.npmignore
Normal file
32
.npmignore
Normal 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*
|
||||
55
README.md
55
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 会返回来自多个搜索引擎的聚合结果,包括标题、描述和链接。
|
||||
|
||||
## 注意与提醒
|
||||
|
||||
### 🚨 重要提示
|
||||
|
||||
189
mcp/cf-search-mcp.js
Normal file
189
mcp/cf-search-mcp.js
Normal 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
38
package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
@@ -445,6 +445,87 @@ export function getSearchHtml() {
|
||||
</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="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('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) {
|
||||
|
||||
Reference in New Issue
Block a user