关于 zoekt-mcp
zoekt-mcp 将 Sourcegraph Zoekt 的索引代码搜索提供给 AI Agent 和 MCP 客户端,适用于需要跨仓库查找代码的开发者。它支持正则表达式和符号查询,并可按仓库、文件或语言缩小范围。
主要功能
- 01索引代码搜索
- 02正则表达式与符号查询
- 03列出可搜索仓库
- 04读取指定文件内容
- 05按仓库、文件和语言过滤
- 06通过 stdio 连接 MCP 客户端
适用场景
- 01在多个代码库中定位函数定义
- 02查找匹配正则表达式的代码
- 03按编程语言缩小搜索范围
- 04让编码 Agent 检索已索引代码
MCP 相关
zoekt-mcp 是通过 stdio 运行的 MCP 服务,向客户端提供 search_code、list_repos 和 get_file 工具,并连接到 zoekt-webserver。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
zoekt-mcp
一个 MCP 服务,将 Sourcegraph Zoekt 代码搜索功能提供给任何兼容 MCP 的 AI Agent——Claude Code、Claude Desktop、Cursor、MCP Inspector 等——让 Agent 能够在你的代码仓库中执行快速、基于索引并支持正则表达式和符号的代码搜索,不受所用编程语言的限制。
- MCP 服务: 使用 Python 构建,基于 FastMCP,通过 stdio 运行,以便客户端将其作为子进程启动。已发布到 PyPI、 ghcr.io, 并收录于 官方 MCP Registry, 因此无需克隆即可使用。
- 后端: 你可以通过附在每个
GitHub 发布版本中的 Docker
Compose 文件自行运行
zoekt-webserver,也可以将 MCP 服务指向已有的zoekt-webserver。 - 提供的工具:
search_code、list_repos、get_file。
架构
flowchart LR
subgraph Client["MCP client"]
CC["Claude Code<br/>Claude Desktop<br/>Cursor, etc."]
end
subgraph Server["zoekt-mcp (Python)"]
Tools["search_code<br/>list_repos<br/>get_file"]
end
subgraph Backend["Docker: zoekt backend"]
Web["zoekt-webserver"]
Idx[("zoekt index<br/>named volume")]
Indexer["zoekt-indexer<br/>(one-shot)"]
end
Code[("Your code<br/>bind mount")]
CC <-->|"stdio<br/>MCP protocol"| Tools
Tools <-->|"HTTP JSON<br/>/api/search<br/>/api/list<br/>/print"| Web
Web --> Idx
Code --> Indexer
Indexer --> Idx
单次搜索如何在系统中流转
sequenceDiagram
actor You
participant Claude as Claude Code
participant MCP as zoekt-mcp
participant Web as zoekt-webserver
participant Idx as zoekt index
You->>Claude: "where is getVideoId defined?"
Claude->>MCP: search_code("sym:getVideoId")
MCP->>Web: POST /api/search
Web->>Idx: scan shards
Idx-->>Web: matches + ctags symbols
Web-->>MCP: raw JSON result
Note over MCP: trim to {repo, file,<br/>line, text, symbols}
MCP-->>Claude: shaped result
Claude-->>You: "src/index.js:17 (function)"
快速开始
从“什么都没安装”到“Claude 可以搜索我的代码”只需三步:安装 MCP 服务、运行后端,并将其接入客户端。这些步骤均无需克隆 Git 仓库。
前提条件
运行 MCP 服务只需满足以下其中一项,后端还需要 Docker:
-
PATH中有 uv——用于通过uvx zoekt-mcp安装。MCP 客户端会通过uvx启动服务,因此which uv必须能在客户端用于启动进程的 shell 环境中找到可执行文件。每台机器只需安装一次:# Official installer (macOS / Linux) curl -LsSf https://astral.sh/uv/install.sh | sh # Homebrew brew install uv # pipx pipx install uv安装程序会将
uv和uvx安装到~/.local/bin/(Linux/macOS) 或%USERPROFILE%\.local\bin\(Windows)中。使用uv --version验证。 -
……或 Docker——用于通过
docker run ghcr.io/radiovisual/zoekt-mcp安装。任何较新的 Docker Desktop 或 Docker 引擎均可使用。无论如何,后端都需要 Docker,因此如果你尚未安装uv,选择此方式可以省去安装步骤。
后端还需要:
- Docker 和 Compose v2——通过附在每个
发布版本中的 compose 文件运行
zoekt-webserver和一次性索引器。
1. 启动后端(每台机器启动一次)
zoekt 后端是一个由你自行运行的常规 Docker Compose 堆栈——zoekt-mcp 不会管理其生命周期。从最新的 GitHub 发行版获取 compose 文件和辅助脚本,然后针对存放代码的目录启动它们:
# Fetch the two files you need from the latest release.
mkdir -p ~/.zoekt-mcp && cd ~/.zoekt-mcp
curl -LO https://github.com/radiovisual/zoekt-mcp/releases/latest/download/docker-compose.yml
curl -LO https://github.com/radiovisual/zoekt-mcp/releases/latest/download/index.sh
chmod +x index.sh
# Point the indexer at any parent directory on your machine.
# Every top-level subdirectory becomes one searchable repo.
echo "ZOEKT_REPOS_DIR=/home/you/code" > .env
# Bring up zoekt-webserver + the one-shot indexer.
docker compose up -d
例如,如果 /home/you/code 目录结构如下:
~/code/
├── project-a/ → indexed as zoekt repo "project-a"
├── project-b/ → indexed as zoekt repo "project-b"
└── scratch-notes/ → indexed as zoekt repo "scratch-notes"
……zoekt 会在一次操作中为全部三个仓库建立索引,你可以用 repo:project-a 限定查询范围——也可以省略 repo:,一次搜索所有内容。有关一台服务处理多个仓库的模式,请参阅下文的为多个代码库建立索引。
检查是否正常:
curl -s -XPOST -d '{"Q":"repo:."}' http://localhost:6070/api/list \
| python3 -m json.tool | head -20
你应该会看到 ZOEKT_REPOS_DIR 中的每个子目录都列为一个 zoekt 仓库。
在 macOS / Windows Docker Desktop 上,你选择的路径必须位于允许文件共享的根目录下(检查 Docker Desktop → Settings → Resources → File Sharing)。Linux 没有此限制。
只想试用一下,不想动真实代码目录? 克隆仓库并使用仓库内的测试夹具:
./tests/fixtures/up.sh——请参阅本文末尾的开发。
2. 将 MCP 服务接入客户端
有两种安装方式——选择与你现有工具链相匹配的一种。两种方式最终都会运行同一个带版本号的服务二进制文件;区别仅在于启动方式。
方式 A——uvx(如果你已经安装 uv,推荐使用)
uvx 首次调用时会从 PyPI 下载最新的 zoekt-mcp,将其缓存并启动。无需永久安装,也无需管理虚拟环境。
Claude Code(~/.claude.json):
{
"mcpServers": {
"zoekt": {
"type": "stdio",
"command": "uvx",
"args": ["zoekt-mcp"],
"env": { "ZOEKT_URL": "http://localhost:6070" }
}
}
}
也可以通过 claude CLI 操作:
claude mcp add zoekt \
--env ZOEKT_URL=http://localhost:6070 \
-- uvx zoekt-mcp
Claude Desktop(macOS 上的 ~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"zoekt": {
"command": "uvx",
"args": ["zoekt-mcp"],
"env": { "ZOEKT_URL": "http://localhost:6070" }
}
}
}
Cursor(~/.cursor/mcp.json 或项目中的 .cursor/mcp.json):
{
"mcpServers": {
"zoekt": {
"command": "uvx",
"args": ["zoekt-mcp"],
"env": { "ZOEKT_URL": "http://localhost:6070" }
}
}
}
若要固定使用某个版本,而不是始终使用最新版本:
"args": ["zoekt-mcp==0.1.0"]
方式 B——Docker 镜像(无需 Python 工具)
如果你已经为后端运行 Docker,并且不想安装 uv,可以改用容器镜像。MCP 客户端会像使用 uvx 一样,通过标准输入输出启动它。
Claude Code / Claude Desktop / Cursor:
{
"mcpServers": {
"zoekt": {
"command": "docker",
"args": [
"run", "-i", "--rm",
"--network=host",
"-e", "ZOEKT_URL=http://localhost:6070",
"ghcr.io/radiovisual/zoekt-mcp:latest"
]
}
}
}
在 Docker Desktop(macOS/Windows)上,容器内无法通过 localhost 访问主机。请移除 --network=host,改用 host.docker.internal:
"args": [
"run", "-i", "--rm",
"-e", "ZOEKT_URL=http://host.docker.internal:6070",
"ghcr.io/radiovisual/zoekt-mcp:latest"
]
若要固定使用某个版本,请将 :latest 替换为 semver 标签(例如 :0.1.0)。该镜像支持多种架构(linux/amd64 + linux/arm64),因此在 Apple Silicon 和 ARM Linux 主机上无需额外参数即可运行。
3. 重启客户端
重启 Claude Code / Claude Desktop / Cursor 后,应该会显示三个工具(search_code、list_repos、get_file)。试着询问“getVideoId 是在哪里定义的?”,看看它是否会调用 search_code("sym:getVideoId")。
为多个代码库建立索引
一个 zoekt-mcp 服务可以处理任意数量的仓库——这是默认配置。ZOEKT_REPOS_DIR 下的每个顶层子目录都会成为共享索引中的一个独立可搜索仓库。search_code 工具可以使用 repo:NAME(对仓库名称进行正则匹配)限定搜索范围,也可以省略 repo: 来搜索所有内容。
如果你的项目位于不同的父目录下(例如 ~/work/ 和 ~/personal/),最简单的办法是创建一个统一的“索引根目录”,并通过符号链接指向每个项目,然后将 ZOEKT_REPOS_DIR 设置为该索引根目录。一个服务、一份配置,即可搜索所有仓库。
mkdir -p ~/.zoekt-root
ln -s ~/work/project-a ~/.zoekt-root/project-a
ln -s ~/personal/side-thing ~/.zoekt-root/side-thing
cd ~/.zoekt-mcp
echo "ZOEKT_REPOS_DIR=$HOME/.zoekt-root" > .env
docker compose up -d
Docker 解析绑定挂载时必须跟随符号链接;这在 Linux 上可行,但在 Docker Desktop 上不一定可靠。如果链接的目录没有出现在容器内,请改为将实际目录(或克隆仓库)放在
~/.zoekt-root下,而不要使用符号链接。
何时确实需要两个服务
只有当你需要完全隔离的索引池时,配置第二个 zoekt-mcp 实例才值得投入成本——例如,将工作代码和个人代码放在完全独立的搜索命名空间中,或并行运行两个不同的后端(例如不同版本的 zoekt)。这不是为了索引更多代码所必需的;对于这种需求,一个包含多个子目录的服务才是合适的工具。
如果你确实需要两个实例:
-
将
~/.zoekt-mcp/docker-compose.yml复制到第二个文件,例如~/.zoekt-mcp/docker-compose.personal.yml。 -
在副本中修改:
- compose 项目的
name:(例如zoekt-mcp-personal) - 主机端口映射(例如
6071:6070) - 命名卷(例如
zoekt-mcp-personal-index) - 容器名称(例如
zoekt-mcp-personal-webserver)
- compose 项目的
-
为第二个堆栈指定独立的环境文件,例如
~/.zoekt-mcp/.env.personal,并将ZOEKT_REPOS_DIR指向 不同的目录。 -
使用各自的 compose 文件和环境文件启动每个堆栈:
cd ~/.zoekt-mcp docker compose up -d docker compose -f docker-compose.personal.yml \ --env-file .env.personal up -d -
将两个服务分别配置到 MCP 客户端中——使用相同的
zoekt-mcp二进制文件,但设置不同的ZOEKT_URL值:{ "mcpServers": { "zoekt-work": { "command": "uvx", "args": ["zoekt-mcp"], "env": { "ZOEKT_URL": "http://localhost:6070" } }, "zoekt-personal": { "command": "uvx", "args": ["zoekt-mcp"], "env": { "ZOEKT_URL": "http://localhost:6071" } } } }
Claude Code 会看到两组彼此独立的工具(来自各自命名空间的 search_code /
list_repos / get_file),并根据问题决定调用哪一组。
对大多数用户来说,只需一个配置完善的 ZOEKT_REPOS_DIR
服务即可。 除非有明确的隔离理由,否则不要使用多服务方案。
高级用法:在专用的仓库目录下暂存代码
除了将 ZOEKT_REPOS_DIR 指向实际代码目录外,你也可以创建一个专用的暂存目录,并将克隆的仓库或目录放入其中。当你无法向 Docker 暴露实际代码目录时(例如 Docker Desktop 上的公司文件共享限制),或者想临时试用本地没有的仓库时,这种方式很有用:
mkdir -p ~/.zoekt-mcp/repos
git clone https://github.com/myorg/myrepo ~/.zoekt-mcp/repos/myrepo
cd ~/.zoekt-mcp
echo "ZOEKT_REPOS_DIR=$HOME/.zoekt-mcp/repos" > .env
docker compose up -d
这种做法的代价是会带来新鲜度陷阱:现在每个项目都有两份副本——一份是你实际编辑的副本,另一份是暂存副本。重新运行索引器时会重新读取暂存副本,因此每次重新索引前,都需要在
~/.zoekt-mcp/repos/myrepo/ 中执行 git pull(或用 cp -r 复制你的修改)。除非有明确的理由,否则最好将
ZOEKT_REPOS_DIR 指向你正在使用的代码目录。
工具接口
| 工具 | 参数 | 返回值 |
|---|---|---|
search_code | query: str、max_results: int = 50、context_lines: int = 3 | {query, file_count, match_count, duration_ns, files: [{repo, file, language, branches, matches: [{line, text, ranges, symbols}]}]} |
list_repos | filter: str = ""(可选的 repo: 条件) | {count, repos: [{name, url, branches, index_time}]} |
get_file | repo: str、path: str、branch: str = "HEAD" | {repo, path, branch, content} |
查询语言
Zoekt 的查询 DSL(完整参考):
| 条件 | 示例 | 含义 |
|---|---|---|
repo: | repo:flask-app | 限定为名称匹配(正则表达式)的仓库 |
file: | file:app.py | 限定为路径匹配的文件 |
lang: | lang:python | 限定为某种语言 |
sym: | sym:list_users | 匹配符号定义 |
case:yes | case:yes Foo | 区分大小写的内容匹配 |
/regex/ | /users?/ | 正则表达式内容匹配 |
| (空白) | lang:go func main | 布尔 AND |
or | def hello or function hello | 布尔 OR |
保持索引最新
Zoekt 搜索的是预构建索引,而不是直接搜索你的文件。编辑代码后,索引不会自动更新——下一次搜索可能会返回过时的行号、漏掉新添加的符号,或让 Claude 指向已移动或重命名的函数。过时的搜索结果是最浪费令牌的主要原因,因为当 search_code 没有返回有用结果时,Claude 会退而求其次,通过 get_file 读取整个文件。
每次索引器运行时都会发生以下情况:
flowchart LR
Src["Your code<br/>(live files)"]
Mount["/src<br/>(read-only<br/>bind mount)"]
Scratch["/tmp/{repo}<br/>(ephemeral<br/>copy)"]
Git["throwaway<br/>git repo<br/>+ snapshot commit"]
Shard[("index shard<br/>/data/*.zoekt")]
Src -->|bind mount| Mount
Mount -->|cp -r| Scratch
Scratch -->|"git init; git add -A;<br/>git commit"| Git
Git -->|zoekt-git-index| Shard
复制到 /tmp/ 的内容是临时的——每次运行索引器时都会重新复制,绝不会触碰你的真实文件。每次刷新都会读取已挂载源目录中当前的内容。
好在,重新索引很快(即使是大型代码库也只需几秒),完全在 Docker 中运行,不涉及任何大模型调用,也不消耗令牌。你只需要决定要如何触发它。
由于主要快速入门指南已将 ZOEKT_REPOS_DIR 指向你的实时代码目录,每次重新索引都会自动反映最新编辑——无需通过复制来保持同步。(如果你使用的是高级暂存工作流,则需要在触发重新索引前更新 ~/.zoekt-mcp/repos/ 下的克隆,否则 zoekt 只会重新读取过时的副本。)
触发重新索引的方案
以下四种方案都在后台运行——不需要 Claude、不消耗令牌,也不占用上下文窗口。选择最适合你工作方式的方案。
1. 手动重新索引
每当你确认做出了重要更改时,运行 ~/.zoekt-mcp/index.sh。该脚本只会针对当前的 ZOEKT_REPOS_DIR 运行索引器容器,不会重启 Web 服务器,因此搜索始终可用。
~/.zoekt-mcp/index.sh
适用场景: 你只偶尔使用 Claude,不介意开始前输入一条命令。无后台开销。
2. Cron(定时重新索引)
按计划在后台重新索引。无需手动操作,但两次定时任务之间的索引会略微过时。
# Re-index every 15 minutes
*/15 * * * * cd ~/.zoekt-mcp && ./index.sh >/dev/null 2>&1
适用场景: 你大多数日子都会编写代码,希望每次打开 Claude 时搜索结果都基本保持最新。对大多数用户来说,每小时一次就足够了。
3. 文件系统监视器
通过 inotifywait(Linux)或 fswatch(macOS)近乎实时地响应文件更改。有任何编辑都会触发操作,无编辑时则保持空闲。
# Linux: one-liner, run it in a tmux pane or as a systemd --user service
while inotifywait -r -e modify,create,delete,move \
--exclude '\.git/|node_modules/|__pycache__/' \
/path/to/your/code 2>/dev/null; do
~/.zoekt-mcp/index.sh
done
# macOS equivalent with fswatch (brew install fswatch)
fswatch -o /Users/you/code | xargs -n1 -I{} ~/.zoekt-mcp/index.sh
适用场景: 你希望“搜索始终是最新的,无论何时提问”。注意:对于工具活动频繁的项目(编译器会向构建目录写入文件、IDE 锁文件等),排除列表很重要——如果不设置排除项,就会不断重新索引。
4. Claude Code SessionStart 钩子
每次启动新的 Claude Code 会话时都重新索引,确保每个会话中的首次搜索都是最新的。这可能是大多数用户的最佳默认方案:无需后台进程或 cron 任务,并且索引是否最新恰好与实际使用时机挂钩。
// ~/.claude.json
{
"hooks": {
"SessionStart": [
{
"command": "$HOME/.zoekt-mcp/index.sh"
}
]
}
}
适用场景: 你希望没有持续运行的进程,并确保实际使用 Claude 时搜索结果是最新的。会话启动会等待重新索引完成,但最多只需几秒。
我应该选择哪一种?
| 如果你…… | 使用 |
|---|---|
| ……偶尔启动 Claude,不介意手动操作 | 方案 1(手动) |
| ……想要“一劳永逸”,但能接受索引过时 N 分钟 | 方案 2(cron) |
| ……希望搜索始终保持最新,并且可以调整排除列表 | 方案 3(监视器) |
| ……主要通过 Claude Code 会话与代码交互 | 方案 4(SessionStart 钩子) |
这些方案并不互斥——例如,同时运行 cron 和 SessionStart 钩子完全没问题,既可以让索引持续保持最新,也能确保会话启动时索引是最新的。
使用 MCP Inspector 手动测试
npx @modelcontextprotocol/inspector uvx --from . zoekt-mcp
Inspector 会在 http://localhost:6274 打开浏览器界面。在
Tools
→ search_code 下试试:
lang:python def hello— 预期在flask-app/app.py中找到匹配项lang:javascript USERS— 预期在express-app/index.js中找到匹配项sym:users— 预期在两个示例中都找到匹配项
在 Tools → list_repos 下,空筛选条件应返回
flask-app 和 express-app。
开发
本节介绍如何开发 zoekt-mcp 本身。如果你只想使用它,上面的 Quickstart 已涵盖所有内容——无需克隆仓库。只有在你想 修改 Python 服务、运行完整测试套件或发布新版本时,才需要看这里。
设置
克隆仓库,并让 uv 为你管理虚拟环境:
git clone https://github.com/radiovisual/zoekt-mcp
cd zoekt-mcp
uv sync
uv sync 会创建 .venv/,依据 uv.lock 解析所有依赖,并
安装所有运行时和开发依赖。默认情况下会安装开发组(pytest、
pytest-asyncio、respx、ruff、pre-commit、pymarkdownlnt);
如果只需安装运行时依赖,请传入 uv sync --no-dev。
常用开发命令:
uv run pytest # run the full test suite
uv run zoekt-mcp --help # run the CLI from source
uv add <package> # add a new runtime dep
uv add --dev <package> # add a new dev dep
uv lock --upgrade # refresh uv.lock
若要使用本地克隆运行 zoekt-mcp,并连接到正在运行的后端 (例如迭代修改服务代码时):
uv run zoekt-mcp --zoekt-url http://localhost:6070
发布
发布流程已完全自动化——推送标签会触发流水线,将软件包发布到 PyPI 和 ghcr.io,
并创建附带 compose 文件的 GitHub 发布版本。请参阅
RELEASING.md,了解发布流程(辅助脚本和手动方式),以及首个标签推送前
必须完成的一次性 PyPI/GHCR 设置。
提交流程
代码检查和测试已通过
pre-commit 钩子接入 git 流程,因此你无需记得
手动运行它们。执行 uv sync 后,每个克隆仓库只需安装一次这两类钩子:
uv run pre-commit install --hook-type pre-commit --hook-type pre-push
此后,每次 git commit 都会运行:
- 对暂存的 Python 文件运行 ruff(
ruff check+ruff format --check)——配置位于pyproject.toml中的[tool.ruff]。 - 对暂存的 Markdown 文件运行 pymarkdownlnt(
pymarkdown scan) ——配置位于pyproject.toml中的[tool.pymarkdown]。我们禁用了MD013(行长度)和MD046(代码块样式),因为它们会影响易读的正文和 宽表格;同时禁用MD033,以允许使用用于故障排查的<details>块。
每次 git push 也会在推送离开本机前运行离线 pytest 测试套件
(tests/test_client_unit.py 和 tests/test_server_shaping.py),确保损坏的测试永远不会进入远程仓库。
测试被安排在 pre-push 而非 pre-commit 阶段,以保持本地提交的速度;
集成测试不包括在内,因为它需要运行中的 zoekt-webserver。
这些钩子会调用 uv run,因此本地和 CI 中运行的都是 uv.lock 锁定的工具版本,
不同环境之间不会出现版本偏差。同样的代码检查工具和单元测试会在每次推送到
main 以及每个拉取请求时,通过
.github/workflows/ci.yml 运行。
要手动运行所有检查(例如,在创建 PR 前):
uv run pre-commit run --all-files
若要直接修复 Python 格式,而不只是检查格式:
uv run ruff format
uv run ruff check --fix
自动化测试
# Unit tests (no Docker required)
uv run pytest tests/test_client_unit.py tests/test_server_shaping.py -v
# Integration tests: bring the stack up against the examples/ corpus,
# then run the live assertions.
./tests/fixtures/up.sh
uv run pytest tests/test_integration.py -v
./tests/fixtures/down.sh
tests/fixtures/up.sh 会设置 ZOEKT_REPOS_DIR=../examples,并调用相同的
deploy/docker-compose.yml,因此测试夹具不会泄漏到生产部署路径中。集成测试会在
ZOEKT_URL 无法访问时自动跳过,因此即使在没有 Docker 的全新检出中直接运行
uv run pytest,也能通过。
配置
| 设置 | 环境变量 | 标志 | 默认值 |
|---|---|---|---|
| Zoekt 后端 URL | ZOEKT_URL | --zoekt-url | http://localhost:6070 |
| HTTP 超时(秒) | ZOEKT_TIMEOUT | --timeout | 30 |
环境变量和标志的作用相同——根据你的 MCP 客户端配置格式选择即可。大多数客户端通过 JSON 配置中的 "env" 块设置环境变量,这就是上面的 uvx 和 Docker 代码片段使用 ZOEKT_URL 而非 --zoekt-url 的原因。
仓库布局
zoekt-mcp/
├── src/zoekt_mcp/ # the Python MCP server
├── tests/
│ ├── test_client_unit.py # offline unit tests
│ ├── test_integration.py # live tests (skip when backend down)
│ └── fixtures/ # test-only helpers (up.sh / down.sh)
├── deploy/
│ ├── docker-compose.yml # generic zoekt backend (env-driven)
│ └── repos/ # user-populated source mount (gitignored)
└── examples/
├── flask-app/ # Flask verification corpus
└── express-app/ # Express verification corpus
故障排查
常见的索引问题,以问答形式呈现。点击问题可展开答案。
问:search_code 返回 0 条结果,但我确定项目中有这个字符串。怎么回事?
十有八九是索引实际上并未包含你的代码——zoekt 搜索的是另一个(或过期的)语料库。MCP 服务本身不会过滤或改写查询;你发送的内容会原样传给 /api/search,因此 0 条结果就表示索引中没有结果。
分三步诊断:
-
让 Agent 调用
list_repos(或curl -s -XPOST -d '{"Q":"repo:."}' http://localhost:6070/api/list)。这是 zoekt 可见内容的权威来源。 -
如果列表中没有你的项目,说明索引器指向了其他位置。常见原因:
- 缺少
~/.zoekt-mcp/.env,或其中的ZOEKT_REPOS_DIR设置错误,因此docker compose up对空目录或非预期目录建立了索引。 - 有人从开发克隆目录运行了
./tests/fixtures/up.sh,该脚本会设置ZOEKT_REPOS_DIR=../examples,并且只为examples/express-app和examples/flask-app建立索引。 - 索引器每次运行都会清空
/data/*,因此先前成功建立的索引不会与之后的索引共存——Web 服务器只能看到最近一次运行索引器所建立的索引。
- 缺少
-
针对正确的目录重新运行索引器:
cd ~/.zoekt-mcp echo "ZOEKT_REPOS_DIR=/absolute/path/to/parent-of-your-repo" > .env docker compose up -d --force-recreate zoekt-indexerZOEKT_REPOS_DIR必须指向一个父目录;其下的每个顶层子目录都会成为一个仓库。索引器退出后,再次运行list_repos以确认。
问:索引器退出时显示 WARNING: no repositories were indexed。该怎么办?
ZOEKT_REPOS_DIR 指向的目录中没有可供索引器转换为仓库的顶层子目录。请将 ZOEKT_REPOS_DIR 设置为一个已包含项目子目录的父目录:
cd ~/.zoekt-mcp
echo "ZOEKT_REPOS_DIR=$HOME/code" > .env
docker compose up -d
ZOEKT_REPOS_DIR 顶层的散落文件会被忽略——compose 文件中的循环只遍历目录。
问:list_repos 显示了 express-app 和 flask-app,但没有我的代码。
它们是开发克隆目录中 examples/ 下的仓库内验证测试样例。只要有程序(通常是本地克隆目录中的 tests/fixtures/up.sh)使用 ZOEKT_REPOS_DIR=../examples 运行索引器,它们就会进入你的索引。针对你的实际项目目录重新建立索引(参见上面的第一个问答),这些测试样例就会被替换;索引器每次运行开始时都会清空 /data/,因此无需单独清理。
问:我编辑了文件,但搜索结果仍显示旧内容/行号。
索引是一个快照,并非实时视图。zoekt 只能看到上次运行索引器时 ZOEKT_REPOS_DIR 中的内容。运行 ~/.zoekt-mcp/index.sh 触发刷新,或者按照保持索引最新中的四种自动化方案之一进行设置,让它自动刷新。重新建立索引速度很快(即使是大型仓库也只需几秒),并且完全在 Docker 中运行——不会调用大模型,也不消耗 token。
问:POST /api/search 返回的是 HTML,而不是 JSON。
Web 服务器启动时没有使用 -rpc,因此 /api/* 请求会落入 HTML 搜索处理程序。发行版自带的 docker-compose.yml 已传入 -rpc(参见 zoekt-webserver 下的 command: 区块);如果你在其他位置运行自己的 zoekt-webserver,请在其 argv 中添加 -rpc 并重启。
许可证
MIT——参见 LICENSE。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。