内容
# mcp-web-search
一个 [MCP](https://modelcontextprotocol.io) 服务器,为 AI 代理提供两个工具:**搜索网页** 和 **读取页面**。
它在无需无头浏览器的情况下实现这一点。搜索结果通过 DuckDuckGo 的无 JavaScript HTML 端点获取,并解析服务器渲染的结果列表。页面获取使用 [curl-impersonate](https://github.com/lwthiker/curl-impersonate),使请求携带真实浏览器的 TLS/HTTP2 指纹 — 这是绕过 Cloudflare/DataDome 式机器人墙的关键,而普通 HTTP 客户端则会触发这些墙 — 然后使用 Readability 提取文章文本。无需 API 密钥,无需账户,无需安装浏览器。
## 工具
| 工具 | 功能 |
|------|--------------|
| `search` | 接受查询和结果数量(1-25)。返回每个自然结果的排名、标题、网址和摘要。 |
| `fetch_page` | 接受网址。返回主要文章文本,附带标题、作者、网站、发布时间等元数据,去除模板。 如果 Readability 无法找到文章,则退回到原始文本提取。 |
预期的流程是先使用 `search` 查找结果,然后使用 `fetch_page` 读取其网址以获取完整内容。
## 要求
[.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0)。除此之外,所有内容(包括 curl-impersonate 的原生二进制文件)都来自 NuGet,在首次构建时自动获取。
原生二进制文件适用于 **win-x64** 和 **linux-x64**。其他平台(尤其是 Apple Silicon)没有可加载的原生二进制文件,因此 `fetch_page` 将失败;而 `search` 仍然有效。
## 运行
```bash
git clone https://github.com/MrJackSpade/mcp-web-search.git
cd mcp-web-search
dotnet run --project src/WebSearchMcp
```
它监听 `http://127.0.0.1:5079/` — 仅限本地主机,无身份验证。验证它是否已启动:
```bash
curl -X POST http://127.0.0.1:5079/ \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1"}}}'
```
您应该会收到一个 `serverInfo` 响应,指示 `WebSearchMcp`。
如果端口 5079 被占用,请在 `src/WebSearchMcp/appsettings.json` 中更改 `Urls` 或传递 `--urls http://127.0.0.1:PORT`。
## 连接客户端
**Claude Code** — 将其指向正在运行的服务器:
```bash
claude mcp add --transport http websearch http://127.0.0.1:5079/
```
**Claude Desktop** 不直接连接到纯本地 HTTP 服务器;使用 [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) 在 `claude_desktop_config.json` 中桥接:
```json
{
"mcpServers": {
"websearch": {
"command": "npx",
"args": ["-y", "mcp-remote", "http://127.0.0.1:5079/"]
}
}
}
```
任何其他 MCP 客户端也适用 — 它是一个标准的、可流式的 HTTP MCP 服务器。
## 配置
`src/WebSearchMcp/appsettings.json` 中的所有配置都是可选的;默认值如以下所示。
```json
{
"Search": {
"Provider": "duckduckgo",
"Region": "us-en",
"MaxResults": 25,
"TimeoutSeconds": 20
},
"Fetch": {
"BrowserProfile": "Chrome136",
"MaxContentChars": 40000,
"TimeoutSeconds": 30
}
}
```
- **`Search:Region`** — DuckDuckGo 地区代码,例如 `us-en`、`uk-en`、`de-de`。
- **`Search:MaxResults`** — `search` 返回结果的上限,无论调用者请求多少。
- **`Fetch:BrowserProfile`** — 要模拟的浏览器。默认为 `Chrome136`;截至 Loxifi.CurlImpersonate 1.3.0,*较新* 配置文件(Chrome146/145/142,Firefox147)附带过时的指纹,可能会得到 403,因此 Chrome136 是实际上有效的最新配置文件。值得在库更新时重新审视。
- **`Fetch:MaxContentChars`** — 返回正文文本的截断上限,以保持响应在模型的上下文内。
所有值也可以来自环境变量:`WEBSEARCH_PROVIDER`、`WEBSEARCH_REGION`、`WEBSEARCH_USER_AGENT`、`WEBSEARCH_FETCH_PROFILE`。
## 可选:远程托管的身份验证
默认配置由于绑定到 localhost 而无身份验证。如果您将其暴露到自己的机器之外,请通过添加 `Mcp` 部分来启用身份验证:
```json
{
"Urls": "http://0.0.0.0:5079",
"Mcp": {
"ClientId": "web-search-mcp",
"ClientSecret": "<一个长随机字符串>",
"SigningKey": "<另一个长随机字符串,32+ 个字符>",
"PublicUrl": "https://websearch.example.com"
}
}
```
当 `ClientId`、`ClientSecret` 和 `SigningKey` 全部存在时,身份验证会自动启用。然后,您将获得一个 OAuth 2.0 授权服务器(`/authorize`、`/token`、`PKCE`,在 `/.well-known/oauth-authorization-server` 处发现),并且每个 MCP 请求都需要一个 bearer token。`PublicUrl` 必须是外部可访问的 URL,因为它成为 token 的颁发者/受众。
自己生成机密并将它们排除在 git 之外 — 这些值是您自己的,而不是要复制的默认值。
## 布局
```
src/
├── WebSearchMcp/ 服务器
│ ├── Search/ ISearchProvider + DuckDuckGo 实现
│ ├── Fetch/ PageReader(curl-impersonate + Readability)
│ └── Tools/ 两个 MCP 工具定义
└── Mcp.Server.Harness/ MCP-over-HTTP 托管 + 可选 OAuth
```
### 添加搜索引擎
`ISearchProvider` 是接口。实现它,在 `Program.cs:CreateProvider` 中添加一个分支,并使用 `Search:Provider` 选择它。其他内容不会更改。
谷歌被故意排除:其无 JavaScript 路径在 2025 年 1 月被删除,当时 Search 开始需要 JS,这使得此服务器基于解析 HTML 的方法失效。
## 许可证
MIT — 请参阅 [LICENSE](LICENSE)。