Skip to main content
Glama

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-114

Grep 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

  1. Install the pieces above and build an index: SETUP.md takes a bare machine to working search, with a check at every stage.

  2. Connect Claude Code, one of:

    • the plugin (this repo is its own plugin marketplace):

      claude plugin marketplace add wlee374/sumgrep
      claude plugin install sumgrep@sumgrep
    • or register the server from your clone (§5b).

    Not both, or Claude sees every tool twice.

  3. Got a .sumgrep file 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 model

If 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/repo

The examples use names from sumgrep's own code; put in your own.


2. Everyday commands

uv run sumgrep search --repo $REPO "where do we retry with backoff"

Options:

Flag

Meaning

Default

--mode summary|code|hybrid

what the question is compared against (see §5)

[search].mode in sumgrep.toml

--k N

how many pointers to return

[search].k (10)

--full

print the whole summary, not just the first sentence

off

--floor 0.4

drop hits whose best score is below this

[search].floor (0.35)

--code-weight 0.2

hybrid only: scale the code arm's vote

[search].code_weight (1.0)

--hyde

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 name

Re-index after the repo changed

uv run sumgrep index --repo $REPO

Incremental. 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 0

Field

Meaning

unchanged

fingerprint identical, skipped

lines refreshed

same code, moved to different lines; line numbers updated

embedded

new or changed functions written to the DB

deleted

functions that no longer exist (renames count as delete + new)

LLM calls

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 $REPO

Shows 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, unfiltered

Reads 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_repetition

A 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 weight

Without --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.52

Line 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@sumgrep

Or 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 /mcp shows it failed in that very first session, reconnect it there.

  • uv must 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. import and SETUP.md put the file there.

  • Its tools are named mcp__plugin_sumgrep_sumgrep__search_code and mcp__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_server

If 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

  1. Add a [[repos]] entry to your sumgrep.toml (name, path, excludes).

  2. 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 27B
  3. Nothing 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 functions

On 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.sumgrep

import works in four steps and asks before running anything:

  1. Checks Qdrant (offers to start or create the Docker container), Ollama and bge-m3 (offers to download it), and creates ~/.sumgrep/sumgrep.toml from the example if there is no settings file. Anything it can't fix stops with one sentence saying what to do.

  2. Loads the index, replacing the existing one if you say yes, and installs the summary cache only if this machine has none.

  3. Test-searches to prove Qdrant + Ollama + the index work together.

  4. 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.toml

A 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 chunking

Changing the summarizer model

  1. Edit [summarize]. Local: ollama pull <model> then set model. Anthropic API: provider = "anthropic", model = "claude-haiku-4-5", and export ANTHROPIC_API_KEY=sk-ant-api... in your shell. Never the Claude Code login token.

  2. Test on a sample before committing: uv run sumgrep summarize --repo $REPO --sample 10 Read the pairs. The mush score at the end should be well under 0.75.

  3. 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.

  4. uv run sumgrep index --repo $REPO --recreate (~13 min, no LLM calls: every summary is now a cache hit). A plain index is 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

~/.sumgrep/sumgrep.toml

settings (§6)

~/.sumgrep/summaries.sqlite

summary cache, keyed by (code fingerprint, model, prompt hash). Survives everything; safe to back up

Qdrant volume qdrant_storage

the vector DB. Survives container restarts

src/sumgrep/summarizer.py

the summary prompt

src/sumgrep/search.py

retrieval and fusion

src/sumgrep/mcp_server.py

the MCP server Claude Code talks to

.claude-plugin/

the Claude Code plugin and its marketplace entry

~/.claude/CLAUDE.md

global steering paragraph for Claude Code


8. Troubleshooting

Symptom

Cause

Fix

Connection refused on :6333

Qdrant not running

Docker Desktop, then docker start qdrant

Connection refused on :11434

Ollama not running

open Ollama app / brew services start ollama

sumgrep is not configured: no sumgrep.toml found

no settings file where the server looks

create ~/.sumgrep/sumgrep.toml (§6)

plugin shows failed in /mcp

first start was still installing, or uv not on PATH

reconnect in /mcp; check uv --version in a new terminal

search returns junk for a question you know has an answer

try --mode summary; check the target's summary with summaries --symbol

fix the summary prompt if the paragraph is bad

0 of 1 chunks have a cached summary

function edited since last index

uv run sumgrep index --repo $REPO

index: collection has 1024-dim vectors but ... produces N

embedding model changed

index --recreate

repo config: NONE in index output

--repo path doesn't match a [[repos]] entry

fix the path in sumgrep.toml; excludes won't apply otherwise

summaries loop / repeat themselves

Qwen without penalty

keep presence_penalty = 1.5, think = false


License

MIT, see LICENSE.

Available Tools

2 tools
index_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.
NameRequiredDescriptionDefault
repoNo

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.1/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose4/5

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.

Usage Guidelines4/5

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).
NameRequiredDescriptionDefault
kNo
modeNo
repoNo
queryYes

Output Schema

NameRequiredDescription
resultYes

TDQS

A4.9/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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.

  1. 2 tool updatesv0.1.0
    • First observedindex_status
    • First observedsearch_code

TDQS

A4.2/5.0

Scored across 2 tools

Disambiguation5/5

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.

Naming Consistency4/5

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.

Tool Count3/5

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.

Completeness4/5

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

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables 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
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables 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
  • A
    license
    A
    quality
    F
    maintenance
    Enables 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.
    1
    801
    Apache 2.0
  • A
    license
    Not graded
    quality
    C
    maintenance
    Fast semantic code search for AI agents — find symbols, references, and callers across any codebase.
    9
    Apache 2.0