sumgrep
Uses a local Ollama service for bge-m3 embeddings and optional local LLM summarization when building and searching the code index, allowing code to stay on the machine when using a local summarizer.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@sumgrepwhere do we merge the ranked lists from the two searches?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
sumgrep
Semantic code search for Claude Code. Every function in a repo gets a one-paragraph English summary written by an LLM. You search those summaries by meaning ("where do we merge the results of two searches") and get back pointers: function name, file, line range. The tool never returns source code; you (or Claude) read the real file.
> where do we merge the ranked lists from the two searches?
search_code → rrf src/sumgrep/search.py:L83-114 [function]
The function `rrf` implements Reciprocal Rank Fusion to merge ...
Read → src/sumgrep/search.py, lines 83-114Grep finds the names you already know. sumgrep finds the code when you only know what it does.
What you need
Docker, running Qdrant (the vector database) on port 6333.
Ollama with
bge-m3(embeddings, ~1.2 GB) on port 11434.uv, the Python project runner (it fetches Python 3.12 itself).
A summarizer, only for building the index: a local Ollama model (free; ~40 s per function for a 27B model on an Apple Silicon Mac), or an Anthropic API key (Haiku: ~20 min and a few dollars for 4,000 functions). Never Claude Code's login token.
Languages: Python, Go, TypeScript/TSX, JavaScript, C++. With a local
summarizer nothing leaves your machine. Tested on macOS (Apple Silicon);
Linux notes are in SETUP.md; Windows is untested.
Related MCP server: MCP Indexer
Get started
Install the pieces above and build an index:
SETUP.mdtakes a bare machine to working search, with a check at every stage.Connect Claude Code, one of:
the plugin (this repo is its own plugin marketplace):
claude plugin marketplace add wlee374/sumgrep claude plugin install sumgrep@sumgrepor register the server from your clone (§5b).
Not both, or Claude sees every tool twice.
Got a
.sumgrepfile from a teammate?QUICKSTART.md: three installs and one command, macOS or Windows.
This file is the command reference. Design and phase plan:
docs/sumgrep-build-guide.md.
How well it works
Measured on one Go codebase of ~4,600 functions with 55 labeled questions, comparing each question with the summaries (the default):
Questions | found 1st | in top 5 | MRR@10 |
25 hand-written | 0.76 | 0.92 | 0.81 |
30 real questions agents typed during work | 0.33 | 0.63 | 0.47 |
Real questions are much harder than invented ones. Comparing questions with the raw code instead of the summaries did worse on every run (top 5: 0.64 vs 0.96 on the first 25-question eval). Similarity scores are not confidence; see §5.
1. Before anything: is the machine ready?
Three services must be up. Check all three when something suddenly fails.
curl -s localhost:6333/readyz # Qdrant (vector DB, Docker) -> "all shards are ready"
curl -s localhost:11434 # Ollama (models) -> "Ollama is running"
ollama list # must show bge-m3 and your summarizer modelIf Qdrant is down: start Docker Desktop, then docker start qdrant.
If Ollama is down: open the Ollama app, or brew services start ollama.
Dashboard for eyeballing the stored data: http://localhost:6333/dashboard
Every command below is run from your sumgrep clone (~/sumgrep) as
uv run sumgrep <command> --repo PATH. Below, $REPO stands for the
repo you index:
export REPO=/path/to/your/repoThe examples use names from sumgrep's own code; put in your own.
2. Everyday commands
Search
uv run sumgrep search --repo $REPO "where do we retry with backoff"Options:
Flag | Meaning | Default |
| what the question is compared against (see §5) |
|
| how many pointers to return |
|
| print the whole summary, not just the first sentence | off |
| drop hits whose best score is below this |
|
| hybrid only: scale the code arm's vote |
|
| rewrite the question with the summarizer LLM first (slow, ~35 s) | off |
Examples:
uv run sumgrep search --repo $REPO "in-memory store with expiring entries" --k 10
uv run sumgrep search --repo $REPO "merge two ranked lists into one" --mode hybrid
uv run sumgrep search --repo $REPO "trim_repetition" --full --k 1 # look one function up by nameRe-index after the repo changed
uv run sumgrep index --repo $REPOIncremental. Only functions whose body changed are re-summarized (about 40 s each on a local 27B model). Unchanged functions, comment edits, and moved code cost nothing. A no-op run takes ~3 s.
The last line tells you what happened:
done in 41s · chunks 4215 · unchanged 4214 · lines refreshed 0 · embedded 1 · deleted 0 · LLM calls 1 · failed 0Field | Meaning |
| fingerprint identical, skipped |
| same code, moved to different lines; line numbers updated |
| new or changed functions written to the DB |
| functions that no longer exist (renames count as delete + new) |
| summaries actually generated (cache misses) |
Re-indexing is manual by design. There is no git hook; a local summarizer is too heavy to run on every commit.
Is the index stale?
uv run sumgrep meta --repo $REPOShows the indexed commit vs. your current HEAD and how many files are dirty. A stale index is safe: pointers may name wrong lines, but the reader always opens the real file.
Read the summaries
uv run sumgrep summaries --repo $REPO --symbol trim_repetition # one function
uv run sumgrep summaries --repo $REPO --symbol SummaryCache. # all methods of a class
uv run sumgrep summaries --repo $REPO --symbol search.py # everything in a file
uv run sumgrep summaries --repo $REPO --limit 20 # first 20, unfilteredReads the function as it is on disk now. If you edited it since the last
index there is no summary yet ("0 of 1 chunks have a cached summary");
run index first.
3. Evaluating search quality
Write labeled questions in a YAML file. Each has a question, a kind,
and the function(s) that count as a correct answer:
repo: sumgrep # folder name of the repo (a mismatch only warns)
queries:
- q: merge the ranked results of several searches into one list
kind: conceptual # says what the code does, never its name
answers:
- src/sumgrep/search.py:rrf # "path:symbol", or just "symbol" for any file
- q: trim_repetition
kind: literal # the identifier itself: grep's lane, a control group
answers:
- trim_repetitionA question is a hit if any of its answers is in the top k. Label the answers from the code, not from search results, or the eval only measures itself. Questions your agents really typed are worth more than invented ones (see "How well it works").
uv run sumgrep eval --repo $REPO --queries my-queries.yaml # all arms, no LLM calls, ~10 s
uv run sumgrep eval --repo $REPO --queries my-queries.yaml --kind conceptual # subset
uv run sumgrep eval --repo $REPO --queries my-queries.yaml --hyde # adds the HyDE arms, ~35 s per query
uv run sumgrep eval --repo $REPO --queries my-queries.yaml --code-weight 0.2 # try a different hybrid weightWithout --queries it reads eval/queries.yaml. It prints, per
question, the rank at which the right answer appeared in each arm
(- = not in the top 10), then a table:
Metric | Meaning |
hit@1 | fraction of questions where the right answer was first |
hit@5 | ...in the top 5 |
MRR | mean of 1/rank; 1.0 is perfect |
4. Build-time commands (rarely needed)
uv run sumgrep chunk --repo $REPO --stats # how the repo splits into functions
uv run sumgrep chunk --repo $REPO --sample 5 # print 5 random function chunks
uv run sumgrep summarize --repo $REPO --dry-run # how many summaries are missing
uv run sumgrep summarize --repo $REPO --sample 10 # summarize 10 random functions, print them, mush gate
uv run sumgrep summarize --repo $REPO --symbol NAME --force # regenerate one summary
uv run sumgrep summarize --repo $REPO # summarize everything missing (no DB write)
uv run sumgrep index --repo $REPO --recreate # drop the DB collection and rebuild (after changing the embedding model)
uv run sumgrep --help # all commands
uv run sumgrep search --help # flags for one command
uv run sumgrep export [FILE] # index + summary cache -> one .sumgrep file (default: Desktop)
uv run sumgrep import FILE.sumgrep # set up another machine from it (see §5c, QUICKSTART.md)summarize only fills the summary cache; index does summarize + embed
write to the DB. You normally just run
index.
5. Reading search output
rrf src/sumgrep/search.py:L83-114 [function]
The function `rrf` implements Reciprocal Rank Fusion to merge multiple ranked lists ...
rrf 0.0164 · summary#1 0.52Line 1: the pointer. Function, file, lines, kind.
Line 2: first sentence of its summary (--full for all of it).
Line 3: how it was found.
summary#1 0.52: in the summary search it ranked 1st with similarity 0.52.code#2 0.49: in the raw-code search it ranked 2nd with similarity 0.49.rrf: the merged score that sets the final order in hybrid mode (each arm adds 1/(60+rank)). In summary or code mode it's just the rank.
Modes. Every function is stored as two vectors: its source and its
English summary. summary compares your question to the paragraphs only.
code compares it to the source only. hybrid does both and merges.
On the first 15-question eval, summary found the answer in the top 5 93%
of the time, hybrid 67%, code 53%, so the default is summary.
Similarity scores are not confidence. With bge-m3 everything lands between 0.45 and 0.60. A question about something that does not exist still gets a top hit around 0.50; a real answer can be 0.52. You cannot tell "not found" from the number. Signs it's a miss: the top results have nothing in common, and the scores are flat. The real check is reading the file.
5b. Using it from Claude Code (MCP server)
The same search, exposed as two tools Claude Code can call:
search_code(query, k?, mode?, repo?) and index_status(repo?). There is
no re-index tool on purpose.
Connect it one way, not both (two registrations give every tool twice).
As a plugin (updates with claude plugin update sumgrep@sumgrep):
claude plugin marketplace add wlee374/sumgrep
claude plugin install sumgrep@sumgrepOr inside a session: /plugin marketplace add wlee374/sumgrep, then
/plugin install sumgrep@sumgrep. Notes:
The first start installs the Python dependencies (~120 MB, a few seconds on a fast connection) into Claude Code's plugin data folder. If
/mcpshows it failed in that very first session, reconnect it there.uvmust be on the PATH Claude Code starts with.The plugin runs from Claude Code's plugin cache, so it reads its settings from
~/.sumgrep/sumgrep.toml(or$SUMGREP_CONFIG), never from your clone.importandSETUP.mdput the file there.Its tools are named
mcp__plugin_sumgrep_sumgrep__search_codeandmcp__plugin_sumgrep_sumgrep__index_status.
Or register it from your clone (user scope, so it's available in every
project). Run in a terminal where the claude command works:
claude mcp add --scope user sumgrep -- \
uv run --project ~/sumgrep python -m sumgrep.mcp_serverIf claude is not on your PATH, add this to ~/.claude.json by hand while
Claude Code is not running (it rewrites that file on exit):
"mcpServers": {
"sumgrep": {
"type": "stdio",
"command": "uv",
"args": ["run", "--project", "/path/to/sumgrep", "python", "-m", "sumgrep.mcp_server"]
}
}Its tools are named mcp__sumgrep__search_code and
mcp__sumgrep__index_status. Check it's registered: claude mcp list,
or /mcp inside a session.
What it returns. Pointers, exactly as stored in the index: function
name, file path relative to the repo root, line range, kind, e.g.
rrf src/sumgrep/search.py:L83-114 [function]. The server never
touches the filesystem and never prints absolute paths, so the same
index works on any machine, from any checkout, wherever it lives. The
agent opens the file in its own checkout, or greps the function name.
Which repos. Every indexed repo; repo="name" narrows to one. With
more than one repo indexed, each result starts with its repo name.
index_status lists the repos and the commit each was indexed at; code
added after that commit is not in the index.
Which settings. $SUMGREP_CONFIG if set, else sumgrep.toml in the
sumgrep folder, else ~/.sumgrep/sumgrep.toml. [search] sets the
defaults (mode, k, floor).
Steering. The server's instructions (in mcp_server.py, sent to
Claude Code every session, updated with the code) say how to use the
results. A four-line generic paragraph in ~/.claude/CLAUDE.md (SETUP
§4.2) says when to reach for it; it never needs updating. Behavior details
go in the server, never in that paragraph.
Checkpoint. Open a fresh Claude Code session in your indexed repo
and ask, in your own words, "where do we handle ". In the
transcript you should see search_code called, then a Read or Grep on
the returned file. That two-step is the design working.
Testing the server without Claude Code:
uv run --project ~/sumgrep python -m sumgrep.mcp_server
# waits on stdin (that's the MCP transport); Ctrl-C to quit. Startup errors print here.Logs from a running server: ~/.claude/ debug logs, or start the server
from a terminal (above) to see its stderr.
5c. Using it for real: more repos, other machines, agents
Add another repo
Add a
[[repos]]entry to yoursumgrep.toml(name, path, excludes).Sample first, then index:
uv run sumgrep chunk --repo /path/to/repo --stats # sane function count? uv run sumgrep summarize --repo /path/to/repo --sample 10 # summaries look right? uv run sumgrep index --repo /path/to/repo # full run: ~40 s per function on a local 27BNothing else to configure: the MCP server searches every indexed repo.
All repos share one Qdrant collection, filtered by repo name. Two repos with the same folder name would collide; rename one.
Move the index to another machine
One file carries everything: a .sumgrep file is a zip of the Qdrant
snapshot (vectors + pointers, all repos) and the summary cache (so future
re-indexes skip the LLM). Its extension isn't .zip so browsers don't
unzip it on download.
On the source machine:
uv run sumgrep export # ~/Desktop/sumgrep-index-DATE.sumgrep, ~90 MB for 4k functionsOn the target machine, once Docker Desktop, Ollama and uv are installed
and the sumgrep code is there (step by step for macOS and Windows:
QUICKSTART.md):
cd ~/sumgrep
uv run sumgrep import /path/to/sumgrep-index-DATE.sumgrepimport works in four steps and asks before running anything:
Checks Qdrant (offers to start or create the Docker container), Ollama and
bge-m3(offers to download it), and creates~/.sumgrep/sumgrep.tomlfrom the example if there is no settings file. Anything it can't fix stops with one sentence saying what to do.Loads the index, replacing the existing one if you say yes, and installs the summary cache only if this machine has none.
Test-searches to prove Qdrant + Ollama + the index work together.
Connects Claude Code (
claude mcp add, if not already done). Say no if you use the plugin.
An older .snapshot from before 2026-10-12 still imports (no cache in
it). For teammates without git, send the code as a zip:
git -C ~/sumgrep archive --format=zip --prefix=sumgrep/ -o ~/Desktop/sumgrep-code.zip HEAD.
The snapshot holds every indexed repo. For the MCP server the target's
checkout can be at any path (pointers are repo-relative). The CLI
(meta, search, index --repo) keys a repo by its folder name, so keep
the same folder name if you will run it on the target. Only the
embedding model must match (bge-m3); the summarizer
model can differ, but a different one will re-summarize on the next
index, since the cache is keyed per model.
Without the snapshot, the summary cache alone is enough: the first
index on the new machine re-embeds everything (~13 min for 4k
functions) but makes no LLM calls.
Make Claude agents use it
Every interactive session already has it once it's connected (§5b): the server's built-in instructions tell Claude when to call it.
Custom subagents (.claude/agents/*.md) only get the tools you list.
Add the MCP tool names (the plugin's names are in §5b):
---
name: code-locator
description: Finds where a concept is implemented in this repo
tools: mcp__sumgrep__search_code, mcp__sumgrep__index_status, Read, Grep, Glob
---
For "where is X" questions call search_code first, then Read the pointed
file to confirm. Use Grep only for exact identifiers.Claude Agent SDK (your own programs): pass the server in mcpServers
and allow the tools:
from claude_agent_sdk import query, ClaudeAgentOptions
opts = ClaudeAgentOptions(
cwd="/path/to/indexed/repo",
mcp_servers={"sumgrep": {
"type": "stdio", "command": "uv",
"args": ["run", "--project", "/path/to/sumgrep",
"python", "-m", "sumgrep.mcp_server"],
}},
allowed_tools=["mcp__sumgrep__search_code", "mcp__sumgrep__index_status",
"Read", "Grep"],
)Project scope for a team: commit a .mcp.json in the target repo
with the same stdio block, or have everyone install the plugin. Each
teammate still needs Qdrant, Ollama, and an index on their own machine
(or point [qdrant].url at a shared Qdrant).
6. Configuration: sumgrep.toml
Put it at ~/.sumgrep/sumgrep.toml, where every route finds it:
mkdir -p ~/.sumgrep && cp ~/sumgrep/sumgrep.toml.example ~/.sumgrep/sumgrep.tomlA sumgrep.toml in the sumgrep folder (or --config FILE) wins for the
CLI and for a claude mcp add server, but the plugin doesn't see it.
[qdrant]
url = "http://localhost:6333"
collection = "sumgrep"
[embed]
model = "bge-m3" # married to the collection; changing it = index --recreate
[search]
mode = "summary" # summary | code | hybrid
k = 10
floor = 0.35
code_weight = 1.0
[summarize]
provider = "ollama" # ollama | anthropic | bedrock
model = "qwen3.8:27b-mlx"
max_words = 300
num_ctx = 12000 # ollama: context window; must fit an 8k-char function
think = false # ollama: skip the reasoning pass
temperature = 0.7
top_p = 0.8
presence_penalty = 1.5 # ollama: stops Qwen looping on tiny functions
keep_alive = "10m" # ollama: keep the model loaded between calls
max_tokens = 1500
[[repos]]
name = "myrepo"
path = "/path/to/myrepo" # matched against --repo
exclude = ["third_party/*"] # glob patterns skipped when chunkingChanging the summarizer model
Edit
[summarize]. Local:ollama pull <model>then setmodel. Anthropic API:provider = "anthropic",model = "claude-haiku-4-5", andexport ANTHROPIC_API_KEY=sk-ant-api...in your shell. Never the Claude Code login token.Test on a sample before committing:
uv run sumgrep summarize --repo $REPO --sample 10Read the pairs. The mush score at the end should be well under 0.75.uv run sumgrep summarize --repo $REPO. Summaries are cached per model, so a new model re-summarizes everything: ~47 h for 4k functions on a local 27B, ~20 min and a few dollars on Haiku. Resumable; search keeps using the old summaries meanwhile.uv run sumgrep index --repo $REPO --recreate(~13 min, no LLM calls: every summary is now a cache hit). A plainindexis not enough: it skips every function whose code hasn't changed, so the old summaries would stay in the index.
Changing the prompt (SYSTEM_PROMPT in src/sumgrep/summarizer.py)
works the same way, steps 2–4: the cache key includes a hash of the prompt.
Changing the embedding model
Different story: the vector size is baked into the DB collection.
Set [embed].model, then uv run sumgrep index --repo $REPO --recreate.
Summaries are reused from cache; only the embedding step reruns (~13 min).
7. Where things live
Path | What |
| settings (§6) |
| summary cache, keyed by (code fingerprint, model, prompt hash). Survives everything; safe to back up |
Qdrant volume | the vector DB. Survives container restarts |
| the summary prompt |
| retrieval and fusion |
| the MCP server Claude Code talks to |
| the Claude Code plugin and its marketplace entry |
| global steering paragraph for Claude Code |
8. Troubleshooting
Symptom | Cause | Fix |
| Qdrant not running | Docker Desktop, then |
| Ollama not running | open Ollama app / |
| no settings file where the server looks | create |
plugin shows failed in | first start was still installing, or | reconnect in |
search returns junk for a question you know has an answer | try | fix the summary prompt if the paragraph is bad |
| function edited since last index |
|
index: | embedding model changed |
|
|
| fix the path in sumgrep.toml; excludes won't apply otherwise |
summaries loop / repeat themselves | Qwen without penalty | keep |
License
MIT, see LICENSE.
Available Tools
2 toolsindex_statusA
What is indexed: for each repo (or only repo="name"), how many functions, at which commit, and when.
The index is a snapshot at that commit. Compare it with `git log` in
your checkout: code changed after it may have moved, and code added
after it is not in the index — grep for it. Re-indexing is a manual
CLI step, not a tool.
| Name | Required | Description | Default |
|---|---|---|---|
| repo | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations exist, so the description carries the full burden, and it discloses the key behavioral trait: the data is a point-in-time snapshot tied to a commit, with freshness limits for post-commit code. It does not cover permissions or any pagination/volume behavior for repos with many entries.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Short and front-loaded: what is returned comes first, then the staleness caveat, then the re-indexing exclusion. Every sentence contributes; nothing is filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present, the description needn't describe return shape, and it covers staleness and the manual re-index path well. The remaining gap is what a `repo` identifier looks like, which matters for the one parameter it has.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The single optional parameter is explained semantically: omitting it returns all repos, passing it narrows to one. This adds real meaning beyond the bare `repo: string|null` schema, which has 0% description coverage. It doesn't state whether the value is a display name, path, or ID, which is a missed detail.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description gives a concrete content statement: for each repo it reports function counts, the commit, and the timestamp. That is distinct from the sibling search_code (status vs. query), though the verb is implicit ("What is indexed") rather than an explicit action.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It establishes the operating context: the index is a snapshot, code changed after the commit may have moved, and code added later must be grepped instead. It also rules out a tempting alternative by stating re-indexing is a manual CLI step, not a tool. It stops short of explicitly telling the agent to call this before search_code.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_codeA
Semantic search over indexed repos' functions BY MEANING.
Use for conceptual questions where you don't know the name of the
code: "where do we cache the signing keys", "code that retries
deleting a temp file", "how are search scores from two lists merged".
Do NOT use for literal identifiers, exact strings, or error messages
— use Grep for those.
Searches every indexed repo; pass repo="name" to search only one.
Returns up to k pointers: function name, file path relative to the
repo root, line range, kind, and the first sentence of an LLM-written
summary. Never returns source code: open the file in your checkout
(or grep the function name) to get the truth. The index is a snapshot
at one commit (see index_status): line numbers may have drifted, and
code written after that commit is not in it. Similarity is not
confidence — if the results look unrelated to each other, the concept
probably isn't in the index.
mode: "summary" (default, compares the query to each function's
summary), "code" (to the raw source), or "hybrid" (both, fused).
| Name | Required | Description | Default |
|---|---|---|---|
| k | No | ||
| mode | No | ||
| repo | No | ||
| query | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does: it discloses that only pointers and an LLM-written summary are returned, never source code; that results reflect a single-commit snapshot whose line numbers may have drifted and which excludes newer code; and that similarity scores are not confidence. These are exactly the non-obvious traits that would otherwise cause misuse.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core verb and meaning-based framing, then examples, exclusions, scope, return shape and caveats in descending priority. Despite its length, every sentence carries operational information — no filler or restatement of the tool name.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 4-parameter semantic search tool with an output schema but no annotations, the description supplies the missing behavioral layer: result shape, snapshot staleness, cross-repo scope, and the interpretability caveat. An agent can call it correctly and interpret weak results without further information.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0% and every parameter's title is bare, so the description must compensate — and it largely does: repo="name" for single-repo scoping, k framed as 'up to k pointers', and mode's three values ('summary' default, 'code', 'hybrid') explained along with what each compares against. It does not state k's default or bounds, and query format is only conveyed by example.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource — semantic (meaning-based) search over indexed repos' functions — and immediately contrasts with the literal-search alternative, Grep, and with the index_status sibling it depends on. An agent can distinguish all three without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit when-to-use (conceptual questions where the symbol name is unknown) with concrete query examples, explicit when-not-to-use (literal identifiers, exact strings, error messages) routed to Grep, and a scoping rule for the repo parameter. Nothing is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
2 tool updates
v0.1.0- First observed
index_status - First observed
search_code
TDQS
Scored across 2 tools
The two tools are unmistakably distinct: search_code performs semantic retrieval while index_status reports index metadata. The search_code description even explicitly delineates what it is not for (literal greps), leaving no boundary confusion.
Both names use consistent snake_case, but search_code follows a verb_noun pattern while index_status is a noun-style status query. That is a minor stylistic deviation rather than an inconsistency.
Two tools is thin, and the server is essentially a single capability (semantic search) plus its status companion. Each tool does earn its place, and the narrow scope keeps the surface honest, but there is little room for anything else.
Coverage of the stated purpose is solid: search across repos with scoping, multiple modes, and a status tool for freshness. The main gap is that re-indexing is a manual CLI step with no tool, and source retrieval is deliberately punted to grep/open-file.
Maintenance
Related MCP Connectors
Code intelligence for coding agents: semantic, AST, graph, and full-text search. 279+ languages.
Ask a codebase what calls what: search, blast radius, paths between symbols, and diffs.
Search indexed code, trace dependencies, assess change impact, and recall repository memory.
Code intelligence for LLMs. Analyze, search, and retrieve code from any public git repository.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables querying and analyzing code relationships by building a lightweight graph of TypeScript and Python symbols. Supports symbol lookup, reference tracking, impact analysis from diffs, and code snippet retrieval through natural language.MIT
- AlicenseNot gradedqualityDmaintenanceEnables semantic code search across multiple repositories using natural language queries. Provides intelligent code discovery, symbol lookups, and cross-repo dependency analysis for AI coding agents.MIT
- AlicenseAqualityFmaintenanceEnables semantic code search across codebases with automatic incremental indexing. Searches return relevant code snippets with file paths and line numbers based on natural language queries.1801Apache 2.0
- AlicenseNot gradedqualityCmaintenanceFast semantic code search for AI agents — find symbols, references, and callers across any codebase.9Apache 2.0