内容
# code-search-mcp
**节省令牌、质量优先** 的 Claude Code 内置 `Explore` / Grep+Read 迭代搜索的替代方案。 作为 MCP 服务器暴露给 Claude Code,它为任何自然语言查询返回代码块的排名列表,单次工具调用即可完成。
[](https://github.com/rutmir/code-search-mcp/actions/workflows/ci.yml)
[](#license)
## 为什么
当 Claude Code 探索代码库以回答问题时,它目前采用的是繁琐的方式:多次 grep / read 迭代,每次迭代都反馈给模型。真正的探索查询会在模型收敛于答案之前消耗数万个令牌(和大量时间)。
`code-search-mcp` 在单次工具调用中返回最终的排名列表(通常为 ~1500 个令牌的结构化 `file:line + symbol + preview`)。命中率与迭代方法匹配或超过,因为:
- **混合检索** — 稠密语义嵌入 + 稀疏 BM25 + 交叉编码器重新排序共同优于任何单一信号
- **AST 感知分块** — 针对 9 种语言的函数 / 类 / 方法边界进行分块;LLM 看到的是 `fn AdaptiveBatcher::note_failure` 头部,而不是原始行范围
- **代码感知匹配** — BM25 分词器分割 snake_case / camelCase 标识符(`buildSiPortfolio` 找到 `build_si_portfolio`),查询字面命名符号将该块提升到顶部 — 精确符号查找不需要 grep
- **质量优先默认值** — 召回是杠杆,延迟不是。2 分钟的搜索找到正确答案,胜过 10 秒的搜索却找不到
**典型节省:** 代码探索的令牌消耗减少 30–100 倍,且召回率相同或更好。
## Claude Code 获得的功能
一旦连接,您的 Claude Code 会话将获得一个工具:
```jsonc
// tools/call → code_search
{
"query": "AIMD adaptive batching halving on failure",
"limit": 5, // 可选,默认 10
"lang": "rust", // 可选,限制为一种语言
"path": "crates/" // 可选,仅限路径包含此子字符串的命中
}
```
输出(每个命中携带 `file:start-end`,语言,语法种类+名称,分数,预览):
```
#1 crates/bot/src/execution/cross_pressure.rs:69-91 [rust] struct CrossPressureDetector score=0.0655
pub struct CrossPressureDetector { /// Diagnostic only ... future_feed: Arc<L2Feed>, ...
#2 crates/bot/src/execution/cross_pressure.rs:390-399 [rust] fn observe_tick_returns_verdicts score=0.0512
fn observe_tick_returns_verdicts() { let fut = L2Feed::new(); ...
```
(CLI `search` 命令还额外打印每个组件的 `dense=` / `bm25=` / `rerank=` 分数用于调试;MCP 响应中不包含这些信息以节省令牌。)
## 工作原理
```
┌──────────────────────────────────────────────────────────────────────┐
│ Claude Code (MCP 客户端) │
└─────────────────────────┬────────────────────────────────────────────┘
│ JSON-RPC over stdio
┌─────────────────────────▼────────────────────────────────────────────┐
│ code-search-mcp serve (每个项目一个进程) │
│ ┌──────────────────┐ ┌──────────────────────────────────────────┐ │
│ │ 搜索循环 │ │ 后台观察者(增量重新索引)│ │
│ └────┬─────────────┘ └────────┬─────────────────────────────────┘ │
│ │ │ │
└───────┼─────────────────────────┼────────────────────────────────────┘
│ │
▼ ▼
嵌入查询 走查 + 分块 + 嵌入(更改的文件)
│ │
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ Qdrant │ │ tantivy(本地) │
│ 稠密向量 │ │ BM25 稀疏索引 │
└──────────┬──────────┘ └──────────┬──────────┘
│ │
└────────────┬─────────────┘
▼
┌──────────────────────┐
│ RRF 合并 │
│ + 前 N 名重新排序 │
│ (bge-reranker-v2-m3)│
│ 融合为排名投票 │
└──────────────────────┘
│
▼
排名结果
```
索引是增量(sha-cached)的,观察者跟踪实时编辑,搜索是 **两阶段**(RRF 超过 30+30 个候选池,交叉编码器超过前 30 名),因此重新排序器的成本是有限的,无论语料库大小如何。交叉编码器的意见被融合到密集+稀疏 RRF 中,作为加权排名投票,而不是完全取代分数 — 一个重新排序器错失良机并不能击垮一个候选者,而这两个检索模态都同意这一点(参见 `[search].rerank_weight`)。
## 要求
您需要三个正在运行的服务。推荐的设置是在单个 LAN 主机(或工作站上的 `localhost`)上使用 `docker-compose`:
- **[Qdrant](https://qdrant.tech/)** — 向量数据库。任何最近的版本。默认端口 `6333`。
- **[llama.cpp server](https://github.com/ggml-org/llama.cpp)** 运行嵌入模型。参考堆栈使用 [`jina-code-embeddings-0.5b`](https://huggingface.co/jinaai/jina-code-embeddings-0.5b)(896-dim,代码专用)。默认端口 `7788`。
- **[llama.cpp server](https://github.com/ggml-org/llama.cpp)** 运行交叉编码器重新排序器。推荐的模型是 [`BAAI/bge-reranker-v2-m3`](https://huggingface.co/BAAI/bge-reranker-v2-m3)(8192 ctx,多语言)。默认端口 `7799`。
构建二进制文件需要 Rust 工具链。MSRV 是 1.88(在 `Cargo.toml` 中声明;由依赖树驱动)。
### 参考 docker-compose
```yaml
services:
qdrant:
image: qdrant/qdrant:latest
ports: ["6333:6333"]
volumes: ["./qdrant-storage:/qdrant/storage"]
restart: always
embedding:
image: ghcr.io/ggml-org/llama.cpp:server
ports: ["7788:7788"]
volumes: ["./llm-models:/models"]
command:
["-m", "/models/jina/jina-code-embeddings-0.5-Q8_0.gguf",
"--embedding", "--pooling", "last",
"--port", "7788", "--host", "0.0.0.0",
"-c", "8192", "-ub", "2048", "--parallel", "1",
"--no-webui"]
restart: always
reranking:
image: ghcr.io/ggml-org/llama.cpp:server
ports: ["7799:7799"]
volumes: ["./llm-models:/models"]
command:
["-m", "/models/bge/bge-reranker-v2-m3-q8_0.gguf",
"--reranking",
"--port", "7799", "--host", "0.0.0.0",
"-c", "8192", "-b", "8192", "-ub", "4096",
"--parallel", "1", "--threads", "4",
"--no-webui"]
restart: always
mem_limit: 6g
```
嵌入服务器上的 `--pooling last` **不可省略**,适用于 jina-code-embeddings-0.5b(它是 Qwen3 解码器模型;`cls`/`mean` 池化产生语义上损坏的向量)。重新排序器上的 `--parallel 1` 是正确的选择,适用于内存带宽受限的 CPU。
在重新排序器上,交叉编码器一次处理一个查询+文档对,因此物理批次(`-ub`)必须适应您最长的截断文档 — 而 llama.cpp 静默地将 `-ub` 限制为 `-b`,因此 **同时设置两者**。`-b 8192 -ub 4096` 可以舒适地适应默认的 `max_document_chars = 8000`(≈2000–4000 个令牌,取决于语言)。如果文档仍然溢出,客户端会自动将截断减少一半并重试,可能会影响质量。
## 安装
```bash
git clone <repo-url> code-search-mcp
cd code-search-mcp
cargo build --release
# 二进制文件位于 ./target/release/code-search-mcp
```
## 配置
在您要使其可搜索的**项目根目录**下创建 `.claude/code-search.toml`。最小配置:
```toml
[project]
id = "myproject"
root = "."
[index]
# `languages` 是可选的。省略它以索引除已知二进制文件或 >2 MB 之外的所有文件(零配置,无需静默删除)。仅当您要将嘈杂的存储库限制为白名单时才设置它 — 任何以下项:rust, python, cpp, dart, typescript, javascript, go, csharp, java, xml, gradle, properties, shell, systemd, env, text, yaml, json, markdown, toml。
respect_gitignore = true
exclude = ["target/**", "build/**", "**/*.lock"]
[embedding]
provider = "openai-compatible"
url = "http://localhost:7788/v1/embeddings"
model = "jina-code-embeddings-0.5b"
dimensions = 896
[vector_store]
provider = "qdrant"
url = "http://localhost:6333"
# `collection` 故意未设置 → 自动派生一个冲突安全的名称。
[bm25]
provider = "tantivy"
index_path = "~/.local/state/code-search-mcp/myproject/tantivy"
[reranker]
provider = "openai-compatible"
url = "http://localhost:7799/v1/rerank"
model = "bge-reranker-v2-m3"
enabled = true
[chunking]
strategy = "lines"
max_chunk_lines = 60
overlap_lines = 10
max_chunk_chars = 20000
[chunking.per_language.rust]
strategy = "tree-sitter"
max_chunk_chars = 8000
[chunking.per_language.markdown]
strategy = "headings"
max_chunk_chars = 15000
[chunking.per_language.python]
strategy = "tree-sitter"
max_chunk_chars = 8000
[watcher]
enabled = true # serve 自动启动观察者
debounce_ms = 500
```
一个完整的注解示例包含所有旋钮,位于 `examples/full-config.toml`。
### 语言
| 语言 | 策略 | 方法限定符 | 备注 |
|---|---|---|---|
| `rust` | tree-sitter | `Type::method` | `impl_method` 用于实现块 |
| `python` | tree-sitter | `Class.method` | `@decorator` 保留;导入分组 |
| `cpp` | tree-sitter | `Type::method` | 内联 + 大纲方法;模板展开 |
| `dart` | tree-sitter | `Class.method` | 类方法可靠;顶级函数备用 |
| `typescript` | tree-sitter | `Class.method` | 导出关键字保留,接口/类型/枚举 |
| `javascript` | tree-sitter | `Class.method` | TS 发射器的子集 |
| `go` | tree-sitter | `Receiver.method` | 指针和值接收器规范化 |
| `csharp` | tree-sitter | `Class.method` | 命名空间下降,无独立发射 |
| `java` | tree-sitter | `Class.method` | 类/接口/枚举/记录/注释 |
| `markdown` | headings | n/a | H1/H2 切割,围栏块感知 |
| `toml`,`yaml`,`json` | lines(默认) | n/a | 结构化配置 |
| `shell`,`systemd`,`env`,`text` | lines | n/a | 捕获所有 `*.sh`,`*.service`,`*.env`,`*.ini`,`*.cfg`,`*.conf`,`*.example`,`*.local` |
选择您实际拥有的语言,并将它们添加到 `[index].languages`。
## 验证堆栈
```bash
./target/release/code-search-mcp --config .claude/code-search.toml check
```
这会 ping 嵌入端点,验证模型的维度与配置匹配,ping Qdrant,(如果存在索引)验证项目身份标记,并使用对比文档对探测重新排序器 — 健康的交叉编码器必须将相关文档的评分高于无关文档,从而捕获“服务器启动但模型输出垃圾分数”的故障模式。
预期输出:
```
INFO embedding endpoint OK model=jina-code-embeddings-0.5b dimensions=896
INFO qdrant collection does not exist yet — will be created on first `index`
INFO reranker endpoint OK model=bge-reranker-v2-m3 on_topic=2.31 off_topic=-8.7
INFO all checks passed
```
## 构建第一个索引
```bash
./target/release/code-search-mcp --config .claude/code-search.toml index
```
第一次运行会进行完整的扫描和嵌入。后续运行只处理更改的文件(通过 sha-cache)。在适度的 CPU 硬件上,预计典型中型存储库的第一次运行需要 ~5–30 分钟,然后是增量更新的几秒钟。
## 从 CLI 尝试搜索
```bash
./target/release/code-search-mcp --config .claude/code-search.toml \
search "adaptive batching halving on failure" -n 5
```
```
query: adaptive batching halving on failure
results: 5 (18207 ms)
#1 src/adaptive_batcher.rs:42-89 [rust] fn AdaptiveBatcher::note_failure score=0.0648 dense=0.531 bm25=14.207 rerank=1.582
pub fn note_failure(&mut self) { self.consecutive_ok = 0; ...
#2 src/indexer.rs:445-510 [rust] fn embed_with_adaptive_batching score=0.0489 dense=0.502 rerank=0.611
...
```
每个命中都带有其组件分数:`dense=` / `bm25=` / `rerank=`。如果 `rerank=` 在每个命中上都缺失,则重新排序器失败,搜索恢复为仅 RRF 排名 — 检查重新排序器服务器。
## 连接到 Claude Code
将 `examples/mcp.json` 复制到项目根目录下的 **`.mcp.json`。编辑其中二进制文件的绝对路径:
```json
{
"mcpServers": {
"code-search": {
"command": "/abs/path/to/target/release/code-search-mcp",
"args": ["--config", ".claude/code-search.toml", "serve"]
}
}
}
```
项目范围的 MCP 服务器需要 **首次显式批准**(针对恶意存储库提供的配置进行安全防范):
1. 在项目根目录下交互式运行 `claude`
2. 接受工作区信任提示并批准 `code-search` 服务器
3. 通过 `claude mcp list` 验证 — 您的服务器应该出现,没有 `⏸ Pending approval` 标记
批准后,`code_search` 工具出现在 Claude Code 的工具列表中。MCP 服务器还在后台运行文件观察者(如果 `[watcher].enabled = true`),因此索引在您编辑时保持最新。
### 使 Claude Code 更喜欢 `code_search` 而不是 Grep/Read
仅凭工具描述无法完全覆盖 Claude Code 的习惯模式(或您自己的自动内存提示,例如“首先阅读 docs/INDEX.md”)。为了使 `code_search` 成为任何项目内容查询的默认搜索工具,请在项目根目录下添加一个项目级 `CLAUDE.md` — 这些说明位于 MCP 工具描述之上,在 Claude Code 的指令层次结构中:
```bash
cp examples/CLAUDE.md /path/to/your/project/CLAUDE.md
# 编辑“项目特定说明”部分,指向您自己的规范文件
```
示例告诉 Claude Code 在查询项目内容(代码、文档、路线图、配置)时使用 `code_search` 而不是 Grep / Glob / Read / Bash,并列出了真正的后备情况(已知文件路径、精确字节操作、未索引的路径)。重启后,Claude Code 将首先查找 `code_search` 以进行探索查询 — 在 MCP 服务器的 stderr 日志中可见,位于 `~/.cache/claude-cli-nodejs/<project-slug>/mcp-logs-code-search/`。
## 配置参考
最小配置已经足够满足大多数设置需求。您可能需要调整的可选旋钮:
**`[embedding]`**
- `startup_wait_secs`(默认 300)— 首次 `index`/`watch` 启动时,等待服务器完成模型加载的时间
- `max_input_chars` — 自适应批次器的初始预算的预热提示。如果未设置,则默认为 10,000;批次器会从那里自我调整
**`[vector_store]`**
- `collection` — 留空(自动派生)。仅在您故意要跨多个安装共享集合时才设置
**`[reranker]`**
- `enabled`(默认 true)— 如果您的环境中没有可用的重新排序器,请设置为 false;搜索恢复为仅 RRF 排名
- `max_document_chars`(默认 8000)— 每文档截断。降低到 ~4000 以实现 ~2 倍更快的重新排序,可能会影响质量
- `timeout_secs`(默认 120)— 外部请求超时;感知吞吐量的动态超时几乎总是先触发
**`[search]`** (所有字段均为可选)
- `dense_k`,`sparse_k`,`rerank_top_n`(均为默认 30)— 每个模态的前 K 名 + 重新排序上限。降低到 15-20 以实现 ~30-40% 更快的搜索,可能会影响召回率
- `rrf_k`(默认 60)— 每个 Cormack 等人的互惠排名融合常数
- `rerank_weight`(默认 2.0)— 重新排序器的排名权重,在最终融合中相对于一个检索模态。交叉编码器的意见被融合到密集+稀疏 RRF 中,作为第三排名权重,而不是完全取代分数。一个候选者如果被两个检索侧同意,则可以幸免于单个重新排序器的错失。`0.0` 按 RRF 排名(仍然报告每个命中的重新排序分数)
- `symbol_boost`(默认 1.0)— 查询中字面命名符号的额外 #1 排名权重(`AdaptiveBatcher::note_failure`,`build_si_portfolio`,`Indexer`)。使 `code_search` 在精确符号查找方面与 grep 竞争。模糊的普通英文单词(`run`,`new`)不会触发它。`0.0` 禁用
**`[serve]`** (所有字段均为可选)
- `query_log_path` — 设置后,`serve` 会在每个 `code_search` 调用后追加一个 JSON 行(时间戳、查询、过滤器、延迟、最佳命中)。MCP 客户端不会保留服务器的 stderr 日志,因此此文件是有关询问和返回内容的唯一持久记录 — 在调优检索质量时很有用。默认情况下关闭
**`[chunking]`** 和 **`[chunking.per_language.<lang>]`**
- 更改分块参数(策略、最大行数、重叠行、最大字符数、语言特定覆盖)会触发 **自动清除 + 重建** 在下一次 `index` 操作中。因为块身份发生了变化,这是正确行为,但请注意这一点
- `max_chunk_chars` 应根据检索质量偏好选择,**而不是** 服务器容量(自适应批次器无论如何都处理服务器限制)
**`[watcher]`**
- `enabled`(默认 true)— `serve` 在后台启动观察者;还提供独立的 `watch` 子命令
- `debounce_ms`(默认 500)— 文件系统事件批处理前的安静窗口
参见 `examples/full-config.toml` 以获取完整注解的引用。
## 操作说明
请参阅完整文档了解更多信息。
### Qdrant 集合身份识别 + 配置变更检测
存储为集合中特殊点的项目身份标识符可以防止:
- 两个项目意外选择相同的 `vector_store.collection` 名称,从而导致数据被静默合并(项目身份指纹不匹配 → 带有可操作错误的硬故障)
- 更改敏感的分块/嵌入参数,以防止在不同 `chunk_uuid` 下共存的旧分块(配置硬不匹配 → 自动清除 + 从头开始重建,并带有警告日志)
- 添加/删除语言或排除模式,以防止留下过时的分块(配置软不匹配 → 重新索引;过时检测会清理)
该标识符通过 `kind = "marker"` 有效载荷标签从搜索结果中过滤掉,因此您永远不会在输出中看到它。
### Tantivy 是每台机器独有的;Qdrant 可以共享
如果相同的项目树在两台机器上被检出到相同的规范路径,并且这两台机器都指向同一个 Qdrant,那么标识符的项目身份指纹就会匹配,Qdrant 分块就会被共享 — 但每台机器都需要自己的 Tantivy。索引器的文件→sha 缓存与本地 Tantivy 文件集相交,因此 Qdrant 中但本地 Tantivy 缺失的文件会重新处理(填充 Tantivy + 幂等重新插入 Qdrant)。
### MCP 取消
`tools/call` 请求在它们自己的任务中运行。匹配的 `notifications/cancelled`(带有与原始请求 ID 匹配的 `params.requestId`)通过 `tokio::select!` 在微秒内抢占搜索。当 Claude Code 决定中止查询时,这非常有用。
### 多项目
每个 Claude Code 会话一个 MCP 服务器进程。每个项目的 `.mcp.json`(在项目根目录)都会启动自己的服务器,并带有自己的 `--config`。每个项目的 Tantivy 状态都存活在 `~/.local/state/code-search-mcp/<project_id>/` 下。Qdrant 集合名称按项目命名(自动派生),因此多个项目在没有冲突的情况下共享一个 Qdrant 实例。
## 故障排除
**`embedding endpoint returned HTTP 500: ... input too large to process`**
自适应批处理程序将自动将预算减半并重试。如果您连续看到许多这样的错误,您的嵌入服务器上的 `--ubatch-size` 可能对于典型分块来说太小了;您可以增加它或降低 `chunking.max_chunk_chars`。
**`reranker rejected batch as too large; halving truncation and retrying`**
文档超过了重新排序服务器的物理批处理(`-ub`)或上下文。客户端会自动将其截断限制减半并重试(最多 2 次),因此搜索仍然会重新排序 — 但文档更短。如果这种情况经常发生,请增加重新排序服务器上的 `-b`/`-ub`(注意:llama.cpp 将 `-ub` 限制为 `-b`,因此请同时设置两者)或降低 `[reranker].max_document_chars`。
**`reranker failed; falling back to RRF-only ranking`** (以及 `WARNING: reranker unavailable` 在工具输出中)
重新排序服务器已关闭、速度太慢或持续拒绝请求。请检查重新排序服务器的日志;`check` 现在会探测重新排序服务器并报告它是否能合理评分。搜索仍然通过 RRF 返回有用的结果,MCP 工具响应会带有降级警告,以便调用 LLM 知道排序是近似的。
**`Qdrant collection '...' fingerprint mismatch`**
您的 `vector_store.collection` 指向其他人的集合(不同的 `project_id` 或根路径)。请修复配置、重命名集合或使用 `clear --yes` 清除并重新开始。
**`config-hard change detected ... AUTO-CLEARING the collection`**
自上次索引以来,您更改了 `chunking` 或 `embedding` 参数 — 或升级到具有较新的 BM25 架构/分词器修订版的二进制文件(在 [changelog](docs/CHANGELOG.md) 中注明)。这是预期行为:索引器检测到更改并正在干净地重建,这会重新嵌入整个语料库(计划与第一次索引持续时间相同)。如果这是意外的配置编辑,请立即按 Ctrl+C 并恢复。
**搜索结果返回来自无关项目的记录**
自动派生防止在默认设置下发生这种情况,但如果您将一个项目的配置复制到另一个项目,而没有更改 `project.id`(并且在两个项目中明确设置了相同的 `vector_store.collection`),那么标识符检查仍应在下一次 `index` 时捕获它。如果不知怎么的它没捕获到,请使用 `clear --yes` 并重新索引。
## 贡献
欢迎提交 PR — 请参阅 [CONTRIBUTING.md](CONTRIBUTING.md) 以获取开发栈设置、构建/测试工作流程和 PR 流程。
## 许可证
根据以下任一许可证进行双重许可:
- [Apache License 2.0](LICENSE-APACHE)
- [MIT license](LICENSE-MIT)
由您选择。
除非您明确声明,否则任何有意提交的贡献将被纳入此作品,定义为 Apache 2.0 许可证,按照上述规定进行双重许可,而不添加任何其他条款或条件。
## 开发历史
请参阅 [docs/CHANGELOG.md](docs/CHANGELOG.md) 以获取功能工作和设计决策的按日期排序的历史记录。
MCP 配置
以下是此 MCP Server 的配置信息,可直接复制到 Cursor 或其他 MCP 客户端中使用。
mcp.json
连接信息
猜你喜欢
暂无推荐服务器