visitproject
by linmy666
README.md
# visitproject
一个**无头的 agentic 网络搜索服务**,通过 [MCP(Model Context Protocol)](https://modelcontextprotocol.io) 把深度 web 搜索能力暴露给 AI agent。
普通的 web search 工具只是把链接丢给 agent。visitproject 多做了一系列让搜索结果真正可用的事:
1. **聚合召回**——基于 SearXNG 元搜索,一次查遍 Google / Bing / DuckDuckGo 等 70+ 引擎
2. **去重 + 重排**——用 embedding 算相似度,过滤掉与问题无关的、去掉重复的,按相关性排序;embedding 不可用时自动降级到 URL 规范化 + 标题 Jaccard 去重
3. **正文提取**——deep 模式用无头浏览器抓取 + Readability 提取正文,再让 LLM 抽取关键事实
4. **真实的 relevance score**——quick 模式用 embedding 相似度,deep 模式用 LLM picker 打分(不是编造的递减序列)
5. **成本护栏**——deep 模式有 LLM 调用次数、抓取 URL 数的硬上限,防止研究循环跑飞烧钱
6. **结果缓存**——LRU + TTL,重复相似 query 省下 50%+ 成本
7. **多引擎 fallback**——SearXNG 不可用时自动退到 DuckDuckGo / Tavily
8. **流式中间结果**——deep 模式每轮通过 MCP progress notification 推送当前进度
最终 agent 拿到的是**干净的结构化结果**(标题 + URL + 正文 + 分数),而不是一堆要自己点开的链接。
## 架构
```
┌─────────────────────────────────────────────────────┐
│ MCP 客户端(MadCop / ZCode / Claude Desktop / ...) │
└───────────────────────┬─────────────────────────────┘
│ stdio (MCP 协议)
▼
┌─────────────────────────────────────────────────────┐
│ visitproject (Node 进程) │
│ │
│ search 工具 ──┬─ quick: 召回 → embedding 重排 │
│ └─ deep : agentic 多轮研究循环 │
│ │ │
│ ▼ │
│ scrape 工具 ── Playwright + Readability 正文提取 │
└───────────────────────┬─────────────────────────────┘
│ HTTP
▼
┌─────────────────────────────────────────────────────┐
│ SearXNG (Docker 容器) —— 元搜索引擎 │
└─────────────────────────────────────────────────────┘
```
## 两个工具
| 工具 | 作用 | 适用场景 |
|---|---|---|
| `search` | 主搜索。`depth=quick` 单次召回+重排(快);`depth=deep` agentic 多轮研究+抓正文(质量高,默认) | agent 需要联网查最新信息、调研主题 |
| `scrape` | 抓取指定 URL 的正文 | agent 已知具体网址,想直接读内容 |
`search` 支持 `source` 参数:`web`(通用)/ `academic`(arxiv/scholar/pubmed)/ `discussions`(reddit)。
## 快速开始
### 1. 启动 SearXNG
需要 Docker(macOS 推荐 [Colima](https://github.com/abiosoft/colima)):
```bash
docker-compose up -d
# 验证:应返回 JSON 结果
curl 'http://localhost:8080/search?format=json&q=hello+world' | head -c 200
```
### 2. 安装依赖并构建
```bash
npm install
npm run build
# 安装 Playwright 浏览器(deep 模式抓正文用)
npx playwright install chromium
```
### 3. 配置
```bash
cp .env.example .env
# 编辑 .env,填入 LLM_API_KEY 等
```
visitproject 需要两类模型:
- **Chat 模型**:deep 模式的 researcher 循环、结果筛选、事实抽取
- **Embedding 模型**:quick 模式的去重与重排
两者都支持 OpenAI 兼容协议(OpenAI / DeepSeek / 智谱 / NVIDIA NIM / 通义等),也支持本地 Ollama。详见 `.env.example`。
### 4. 本地测试
```bash
npm start
# server 启动后会通过 stdio 等待 MCP 客户端连接
```
## 接入 MadCop
visitproject 主要场景是给 [MadCop](https://github.com/linmy666) 的 agent 提供网络搜索。在 MadCop 的 MCP 配置(`~/.madcop/mcp_servers.json` 或设置界面)中添加:
```json
{
"name": "visitproject",
"command": "node",
"args": ["/absolute/path/to/visitproject/dist/index.js"],
"enabled": true,
"env": {
"SEARXNG_URL": "http://localhost:8080",
"LLM_PROVIDER": "openai",
"LLM_BASE_URL": "https://api.openai.com/v1",
"LLM_API_KEY": "sk-...",
"LLM_MODEL": "gpt-4o-mini",
"EMBEDDING_MODEL": "text-embedding-3-small"
}
}
```
挂载后,MadCop 的所有 agent(standard 模式 / deep 模式的 researcher 等)即可调用 `search` 和 `scrape` 工具。
## 接入其他 MCP 客户端
任何支持 stdio MCP 的客户端都能用,配置方式类似。例如 Claude Desktop 在 `claude_desktop_config.json` 中:
```json
{
"mcpServers": {
"visitproject": {
"command": "node",
"args": ["/absolute/path/to/visitproject/dist/index.js"],
"env": { "SEARXNG_URL": "http://localhost:8080", "...": "..." }
}
}
}
```
## 深度 vs 快速
| | quick | deep |
|---|---|---|
| 速度 | 快(1 次 SearXNG + 1 次 embedding) | 慢(多轮 LLM 调用 + 抓取) |
| 质量 | 摘要 + 向量重排,够用 | 抓全文 + LLM 抽事实,高 |
| LLM 依赖 | 仅 embedding | embedding + chat(多轮) |
| 适合 | 简单事实、快速核实 | 深度调研、复杂问题 |
默认 `deep`,可在 `.env` 用 `VISIT_DEPTH=quick` 改默认值,或每次调用时用 `depth` 参数覆盖。
## 项目结构
```
visitproject/
├── src/
│ ├── index.ts # MCP server 入口(stdio 传输)
│ ├── server.ts # 注册 search / scrape 工具
│ ├── config.ts # 环境变量配置
│ ├── types.ts # 共享类型
│ ├── core/
│ │ ├── searxng.ts # SearXNG HTTP 客户端
│ │ ├── scraper.ts # Playwright + Readability 正文提取
│ │ ├── pipeline.ts # 搜索管线:召回→去重→重排 / 召回→选→抓→抽
│ │ └── researcher.ts # agentic 多轮研究循环
│ ├── models/
│ │ ├── base/ # LLM / Embedding 抽象
│ │ ├── providers/ # OpenAI / Ollama 实现
│ │ └── factory.ts # 按 provider 构造实例
│ ├── prompts/researcher.ts # researcher / picker / extractor 提示词
│ └── utils/ # 相似度计算 / 文本分块
├── searxng/settings.yml # SearXNG 配置
├── docker-compose.yml # SearXNG 容器
└── .env.example # 配置模板
```
## 开发
```bash
npm run dev # watch 模式编译
npm run typecheck
npm test # 跑测试(29 个,覆盖 similarity/chunker/dedup/cache)
```
## 全部配置项
| 环境变量 | 默认值 | 说明 |
|---|---|---|
| `SEARXNG_URL` | http://localhost:8080 | SearXNG 地址(必需) |
| `LLM_PROVIDER` | openai | openai / ollama |
| `LLM_BASE_URL` / `LLM_API_KEY` / `LLM_MODEL` | — | chat 模型 |
| `EMBEDDING_*` | 同 LLM | embedding 模型 |
| `VISIT_DEPTH` | deep | 默认搜索深度 |
| `VISIT_MAX_RESULTS` | 12 | 返回结果上限 |
| `VISIT_MAX_ITERATIONS` | 6 | deep 模式最大轮数 |
| `VISIT_MAX_LLM_CALLS` | 20 | **成本护栏**:单请求 LLM 调用上限 |
| `VISIT_MAX_SCRAPE_URLS` | 8 | **成本护栏**:单请求抓取上限 |
| `VISIT_CACHE_TTL` | 3600000 | 缓存 TTL(毫秒),0=禁用 |
| `VISIT_CACHE_SIZE` | 64 | 缓存条数 |
| `VISIT_FALLBACK` | duckduckgo | SearXNG 挂时的后备引擎 |
| `TAVILY_API_KEY` | — | Tavily fallback 用(可选) |
| `VISIT_SCRAPE_CONCURRENCY` | 3 | 抓取并发上限 |
| `VISIT_INDEX_PATH` | ./data/index.json | **本地索引文件**(自建垂直索引) |
| `VISIT_SOURCES_PATH` | ./data/sources.json | **数据源配置**(要爬取哪些站) |
| `VISIT_INDEX_TOP_K` | 5 | 本地索引召回数 |
| `VISIT_INDEX_MIN_HITS` | 3 | 本地命中多少条才跳过 SearXNG |
| `HTTPS_PROXY` / `HTTP_PROXY` | — | 代理(解决国内网络) |
## 垂直自建索引(v0.4.0)
除了借 SearXNG 搜全网,visitproject 还能**自建垂直索引**——你指定几个网站,它爬取建本地索引,搜索时**先查本地索引,命中够多就不再依赖 Google/Bing**。数据你掌控,可定制,零外部依赖。
### 建索引
```bash
# 1. 编辑数据源配置(编辑 data/sources.json,加你想索引的网站)
# 2. 爬取 + 建索引
npm run index
# 或指定自定义配置
node dist/cli.js index --sources ./my-sources.json
```
数据源配置 `data/sources.json`:
```json
[
{
"name": "nodejs-docs",
"url": "https://nodejs.org/api/",
"maxDepth": 1,
"maxPages": 50
}
]
```
### 工作原理
```
search(query)
├─ 1. 查本地 MiniSearch 索引(自建,CJK 分词,毫秒级)
│ 命中 ≥ indexMinHits → 直接返回,跳过 SearXNG
├─ 2. 命中不足 → searchWithFallback(SearXNG → DDG → Tavily)
└─ 合并去重
```
爬虫:BFS 同域遍历,礼貌限速(每页 500ms 间隔),限并发(3),用 Playwright + Readability 提正文。CJK tokenizer 让中文 query 能单字召回。
## License
MIT
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues