Skip to main content
Glama
Eivs

MCP WebSearch

by Eivs
README.md
# MCP WebSearch

基于 Model Context Protocol (MCP) 的 Web 搜索服务,使用最新的 **MCP Streamable HTTP** 协议。

## 功能特性

- ✅ **MCP Streamable HTTP 协议**: 符合最新 MCP 规范 
- ✅ **单一端点设计**: `/mcp` 支持 POST/GET/DELETE
- ✅ **Session 管理**: 使用 `Mcp-Session-Id` header
- ✅ **实时消息推送**: 支持 streaming 响应
- ✅ **DuckDuckGo 搜索**: 集成 DuckDuckGo 搜索引擎
- ✅ **身份验证**: 基于 API Token 的身份验证
- ✅ **速率限制**: 防止 API 滥用
- ✅ **可扩展架构**: 预留其他搜索引擎集成接口
- ✅ **TypeScript**: 完整的类型安全
- ✅ **Fastify**: 高性能 Web 框架
- ✅ **Biome**: 现代化的代码格式化和 Lint 工具

## 技术栈

- **Web 框架**: Fastify 5.x
- **MCP SDK**: @modelcontextprotocol/sdk 1.21.0
- **MCP 协议**: Streamable HTTP (2025-03-26 规范)
- **搜索引擎**: DuckDuckGo (HTML API)
- **开发工具**: TypeScript, Biome, tsx
- **认证**: API Token (Bearer / X-API-Key)
- **速率限制**: @fastify/rate-limit

## MCP Streamable HTTP 协议说明

本服务实现了最新的 **MCP Streamable HTTP** transport 规范 (2025-03-26),取代了旧的 HTTP with separate endpoints transport。

### 关键特性

1. **单一端点**: `/mcp` (POST/GET/DELETE)
2. **Session 管理**: 使用 `Mcp-Session-Id` header
3. **初始化**: POST 请求自动创建 session
4. **Streaming 连接**: GET 请求建立 streaming 连接接收服务器推送
5. **Session 终止**: DELETE 请求关闭 session

### 端点说明

| 方法 | 端点 | 用途 | 需要认证 | 需要 Session ID |
|------|------|------|----------|----------------|
| GET | `/health` | 健康检查 | ❌ | ❌ |
| POST | `/mcp` | 发送 JSON-RPC 消息 | ✅ | 首次请求不需要 |
| GET | `/mcp` | 建立 streaming 连接 | ✅ | ✅ |
| DELETE | `/mcp` | 终止 session | ✅ | ✅ |

## 快速开始

### 1. 安装依赖

```bash
npm install
```

### 2. 配置环境变量

复制 `.env.example` 到 `.env`:

```bash
cp .env.example .env
```

编辑 `.env` 文件,设置你的 API Token:

```env
PORT=3000
NODE_ENV=production

# 设置一个或多个 API Token(逗号分隔)
API_TOKENS=your-secret-token-1,your-secret-token-2

# 速率限制配置
RATE_LIMIT_WINDOW_MS=900000    # 15分钟
RATE_LIMIT_MAX_REQUESTS=100    # 每15分钟最多100个请求

# 搜索配置
SEARCH_TIMEOUT_MS=10000        # 搜索超时时间(毫秒)
MAX_SEARCH_RESULTS=10          # 默认最大搜索结果数
```

### 3. 启动服务

**开发模式** (支持热重载):
```bash
npm run dev
```

**生产模式**:
```bash
npm run build
npm start
```

服务将在 `http://localhost:3000` 启动。

## API 使用

### 健康检查

```bash
curl http://localhost:3000/health
```

响应:
```json
{
  "status": "ok",
  "timestamp": "2025-01-07T10:00:00.000Z",
  "service": "mcp-websearch"
}
```

### MCP HTTP Streamable 协议

本服务完整实现 MCP Streamable HTTP 协议:

**工作流程**:

1. **初始化 (POST /mcp)**
   ```bash
   curl -X POST http://localhost:3000/mcp \
     -H "Authorization: Bearer your-token" \
     -H "Content-Type: application/json" \
     -d '{
       "jsonrpc": "2.0",
       "id": 1,
       "method": "initialize",
       "params": {...}
     }'
   ```
   响应会包含 `Mcp-Session-Id` header

2. **建立 Streaming 连接 (GET /mcp)**
   ```bash
   curl -N -H "Authorization: Bearer your-token" \
        -H "Mcp-Session-Id: <session-id>" \
        http://localhost:3000/mcp
   ```

3. **发送后续请求 (POST /mcp)**
   ```bash
   curl -X POST http://localhost:3000/mcp \
     -H "Authorization: Bearer your-token" \
     -H "Mcp-Session-Id: <session-id>" \
     -H "Content-Type: application/json" \
     -d '{
       "jsonrpc": "2.0",
       "id": 2,
       "method": "tools/call",
       "params": {...}
     }'
   ```

4. **终止 Session (DELETE /mcp)**
   ```bash
   curl -X DELETE http://localhost:3000/mcp \
     -H "Authorization: Bearer your-token" \
     -H "Mcp-Session-Id: <session-id>"
   ```

### 使用示例

**建立 Streaming 连接**:

```bash
curl -N -H "Authorization: Bearer your-secret-token-1" \
     -H "Mcp-Session-Id: your-session-id" \
     http://localhost:3000/mcp
```

或使用 `X-API-Key` header:

```bash
curl -N -H "X-API-Key: your-secret-token-1" \
     -H "Mcp-Session-Id: your-session-id" \
     http://localhost:3000/mcp
```

### Web 搜索工具

MCP 工具名称: `web_search`

**参数**:
- `query` (必需): 搜索查询字符串
- `max_results` (可选): 返回的最大结果数,默认 10

**响应格式**:
```json
{
  "query": "search term",
  "results": [
    {
      "title": "Result Title",
      "url": "https://example.com",
      "snippet": "Result description...",
      "source": "duckduckgo"
    }
  ],
  "totalResults": 10,
  "engine": "duckduckgo",
  "timestamp": "2025-01-07T10:00:00.000Z"
}
```

## 在 Claude Desktop 中使用

在 Claude Desktop 的 MCP 配置中添加:

```json
{
  "mcpServers": {
    "websearch": {
      "url": "http://your-server:3000/mcp",
      "headers": {
        "Authorization": "Bearer your-secret-token-1"
      }
    }
  }
}
```

或使用 X-API-Key:

```json
{
  "mcpServers": {
    "websearch": {
      "url": "http://your-server:3000/mcp",
      "headers": {
        "X-API-Key": "your-secret-token-1"
      }
    }
  }
}
```

**注意**: 使用的是单一的 `/mcp` 端点,支持 POST/GET/DELETE 方法。

## 开发

### 开发服务器

```bash
npm run dev
```

### 代码格式化和 Lint

```bash
# 格式化代码
npm run format

# 检查代码质量
npm run lint

# 自动修复问题
npm run lint:fix
```

### 构建

```bash
npm run build
```

### Nginx 反向代理配置

创建 Nginx 配置文件:

```nginx
server {
    listen 80;
    server_name your-domain.com;

    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;

        # Streamable HTTP 支持
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_buffering off;
        proxy_cache off;
        proxy_read_timeout 86400s;
    }
}
```

### 使用 Docker 部署

构建和运行:

```bash
docker build -t mcp-websearch .
docker run -d \
  -p 3000:3000 \
  -e API_TOKENS=your-token \
  --name mcp-websearch \
  mcp-websearch
```

或使用 `docker-compose.yml`:

```yaml
version: '3.8'

services:
  websearch:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - API_TOKENS=${API_TOKENS}
      - PORT=3000
    restart: unless-stopped
```

启动:
```bash
docker-compose up -d
```

## 扩展其他搜索引擎

项目架构已预留扩展接口,添加新搜索引擎步骤:

1. 在 `src/search/providers/` 创建新的提供商文件
2. 继承 `SearchProvider` 基类
3. 实现 `search()` 方法
4. 在 MCP 服务器中注册新工具

示例:

```typescript
// src/search/providers/bing.ts
import { SearchProvider } from './base';
import type { SearchResult, SearchOptions } from '../types';

export class BingProvider extends SearchProvider {
  readonly name = 'bing';

  async search(query: string, options?: SearchOptions): Promise<SearchResult[]> {
    // 实现 Bing 搜索逻辑
    // ...
  }
}
```

## 项目结构

```
websearch/
├── src/
│   ├── server.ts              # Fastify 服务器入口
│   ├── config/
│   │   └── index.ts          # 配置管理
│   ├── mcp/
│   │   └── server.ts         # MCP 服务器实现
│   ├── search/
│   │   ├── types.ts          # 类型定义
│   │   └── providers/
│   │       ├── base.ts       # 搜索引擎基类
│   │       └── duckduckgo.ts # DuckDuckGo 实现
│   ├── auth/
│   │   ├── middleware.ts     # Fastify 认证钩子
│   │   └── rateLimiter.ts    # 速率限制配置
│   └── utils/
│       ├── logger.ts         # 日志工具
│       └── errors.ts         # 错误处理
├── dist/                      # 编译输出
├── biome.json                 # Biome 配置
├── .env                       # 环境变量(不提交)
├── .env.example              # 环境变量模板
├── package.json
├── tsconfig.json
└── README.md
```

## 故障排查

### 问题: 搜索结果为空

- 检查网络连接
- 检查 DuckDuckGo 是否可访问
- 增加 `SEARCH_TIMEOUT_MS` 值

### 问题: 认证失败

- 确认 `.env` 文件中 `API_TOKENS` 配置正确
- 确认请求 header 格式正确
- 检查 token 中是否有多余的空格

### 问题: 速率限制触发

- 调整 `RATE_LIMIT_MAX_REQUESTS` 和 `RATE_LIMIT_WINDOW_MS`
- 检查是否有异常请求

## MCP 协议说明

本服务实现了 MCP Streamable HTTP transport 规范 (2025-03-26):

1. **初始化连接**: 客户端向 `POST /mcp` 发送 initialize 请求
2. **Session 创建**: 服务器返回 `Mcp-Session-Id` header
3. **建立 Streaming**: 客户端用 session ID 向 `GET /mcp` 建立 streaming 连接
4. **消息交换**: 客户端用 session ID 向 `POST /mcp` 发送请求,通过 streaming 连接接收响应
5. **连接保持**: Streaming 连接保持打开状态,支持双向通信
6. **Session 终止**: 客户端向 `DELETE /mcp` 发送请求关闭 session

### 端点说明

- `GET /health` - 健康检查(无需认证)
- `POST /mcp` - 发送 MCP 请求(首次不需要 session ID)
- `GET /mcp` - 建立 streaming 连接(需要 session ID)
- `DELETE /mcp` - 终止 session(需要 session ID)

### Session 管理

- **Stateful 模式**: 服务器自动生成并管理 session ID
- **Session Header**: 使用 `Mcp-Session-Id` header
- **Session 验证**: 非初始化请求必须提供有效的 session ID
- **Session 清理**: DELETE 请求或连接关闭时自动清理

## License

MIT

## 贡献

欢迎提交 Issue 和 Pull Request!