Skip to main content
Glama
semantic-code-mcp
by vrppaul

semantic-code-mcp

MCP server that provides code search for Claude Code. Instead of iterative grep/glob, it indexes your codebase's words in seconds and returns ranked functions, classes and sections, a definition first when you ask its name. A setting adds a search by meaning, which embeds the code; agents measured with and without it found no gain from it.

Supports Python, Rust, and Markdown — more languages planned.

How It Works

Claude Code ──(MCP/STDIO)──▶ semantic-code-mcp server
                                    │
                    ┌───────────────┼───────────────┐
                    ▼               ▼               ▼
              AST Chunker      Embedder         SQLite
             (tree-sitter)   (onnxruntime)  (words, vectors)
  1. Chunking — tree-sitter parses source files into functions, classes, methods, structs, traits, markdown sections, etc.; a repository of 8 MiB of source or more is parsed in up to four processes beside the server, half its cores, for the seconds of its first index

  2. Storage — the chunks and a full-text index of their words in one SQLite database per index (LanceDB, an optional extra, in its place)

  3. Search — by words (BM25), the chunks named as the query first

  4. Meaning, by a setting — with the extra semantic-code-mcp[meaning] and SEMANTIC_CODE_MCP_SEARCH_MODE=words_and_meaning the server embeds each chunk once with onnxruntime and the model's ONNX export (all-MiniLM-L6-v2, 384d, no torch), keeps each model's vectors beside the chunks, and fuses every search by meaning with one by words by rank

Indexing is incremental: the index records each file's modification time and size, and re-indexes the files that differ. Between searches the server follows each repository's files with the system's own watcher (inotify on Linux, FSEvents on macOS, ReadDirectoryChangesW on Windows), so a search reads only what changed; it scans every file where no watcher can tell, and every 5 minutes as a check. Each chunker version and storage layout keeps an index of its own, so a new version builds a new index once, and switching back finds the old one; each model's vectors are kept apart inside it, so another model embeds again without chunking again; an index no session used for a week is removed. In a git repository, git lists the files to index — tracked ones and untracked ones .gitignore does not exclude; elsewhere the server walks the file system. The embedding model loads on the first search of a server set to search by meaning, never for a search by words.

Related MCP server: codebaxing

Installation

One command on every platform, about 95 MB installed. A search by meaning needs the model's libraries, the extra semantic-code-mcp[meaning] (190 MB in all: uvx --from 'semantic-code-mcp[meaning]' semantic-code-mcp), and SEMANTIC_CODE_MCP_SEARCH_MODE=words_and_meaning; such a server fetches the model's ONNX file (90 MB) from the Hugging Face hub once, on its first search:

uvx semantic-code-mcp

Claude Code, as a plugin — the server and the skill that tells the agent how to word a search and when to Grep instead, in one install (Claude Code 2.1.275 or later):

/plugin install semantic-code --marketplace vrppaul/semantic-code-mcp

On an earlier Claude Code, add the marketplace first:

/plugin marketplace add vrppaul/semantic-code-mcp
/plugin install semantic-code@semantic-code-marketplace

Claude Code, the server alone:

claude mcp add --scope user semantic-code -- uvx semantic-code-mcp

The skill alone, for an agent that has the server some other way:

npx skills add vrppaul/semantic-code-mcp
{
  "mcpServers": {
    "semantic-code": {
      "command": "uvx",
      "args": ["semantic-code-mcp"]
    }
  }
}

Updating

uvx caches the installed version. To get the latest release:

uvx --upgrade semantic-code-mcp

Or pin a specific version in your MCP config:

claude mcp add --scope user semantic-code -- uvx semantic-code-mcp@<version>

MCP Tools

search_code

Search code by the words it uses. Auto-indexes on first search.

Parameter

Type

Default

Description

query

str

required

The words to find, as code would write them, or a name (Stack.push), whose chunks come first

project_path

str

the client's workspace root, inside a git repository

Absolute path to the project, or to a directory inside its git repository: the repository is indexed once, and a search keeps to the directory; not a repository that ignores the directory or holds the user's home directory, where the directory is a project of its own

limit

int

10

Maximum number of results, at most 50

chunk_kinds

list[str]

all

Only chunks of these kinds: module, function, method, class, section

languages

list[str]

all

Only chunks of files in these languages: python, rust, markdown

path_glob

str

none

Only files this glob names, as in a .gitignore: without a slash a name at any depth below the directory project_path names (*.rs, and tests/, as a trailing slash does not count), with one before a name a path from that directory (src/**/*.py, /tests); * stays within a directory, ** crosses any number, even inside a name (src**.py, where a .gitignore reads it as *), ? is one character, a directory takes what it holds; a character class ([ab]), braces, escapes, a leading !, .. or more than 1000 characters is refused with the reason

exclude_glob

str

none

No file this glob names, read as path_glob is

include_debug

bool

False

Also return timings and counts of the search

Returns project_root (the project's root: its repository's, or the directory itself outside git; given as project_path, it searches all of it), searched_directory (the directory the search kept to: project_root, or a directory under it) and ranked results with file_path (absolute), line_start, line_end, name, kind (module, function, method, class, section; Rust's structs, enums, traits and impls are classes), parent (the class, impl, module or heading the chunk sits in, as Stack for Stack.push), content (cut to 50 lines, truncated says so); results come best first. A failure — a missing or relative path, an index that cannot be written — comes back as a tool error with its cause.

index_codebase

Index a codebase's words for search; it embeds nothing, and a server set to search by meaning embeds the chunks on its next search. Only processes new and changed files unless force=True.

Parameter

Type

Default

Description

project_path

str

the client's workspace root, inside a git repository

Absolute path to the project, or to a directory inside its git repository: the repository is indexed once, and a search keeps to the directory; not a repository that ignores the directory or holds the user's home directory, where the directory is a project of its own

force

bool

False

Re-index all files regardless of changes

Returns project_root (a directory inside a repository indexes the repository from its root), files_indexed, chunks_indexed, files_deleted and duration_seconds.

index_status

Check indexing status for a project.

Parameter

Type

Default

Description

project_path

str

the client's workspace root, inside a git repository

Absolute path to the project, or to a directory inside its git repository: the repository is indexed once, and a search keeps to the directory; not a repository that ignores the directory or holds the user's home directory, where the directory is a project of its own

Returns project_root (a directory inside a repository reports the repository's root), is_indexed, last_updated, files_count, chunks_count, embedded_chunks_count (the chunks with the server's model's vectors, which a server set to search by meaning reads without embedding first), model (the model, its revision and the embedder version whose vectors embedded_chunks_count counts), stale_files_count, manifest (the chunker and storage versions that built the index) and being_built (a run, this session's or another's, holds the index now: until it ends, the counts may be what it has written so far).

Configuration

All settings are environment variables with the SEMANTIC_CODE_MCP_ prefix (via pydantic-settings):

Variable

Default

Description

SEMANTIC_CODE_MCP_CACHE_DIR

~/.cache/semantic-code-mcp

Where indexes are stored

SEMANTIC_CODE_MCP_INDEX_STORE

sqlite

What keeps the indexes: sqlite, or lancedb, whose libraries come with the extra semantic-code-mcp[lancedb] (uvx --from 'semantic-code-mcp[lancedb]' semantic-code-mcp). Each keeps indexes of its own, so a change builds the index again once

SEMANTIC_CODE_MCP_LOCAL_INDEX

false

Store index in .semantic-code/ at the root of each project's repository; it holds a .gitignore, so git never shows it

SEMANTIC_CODE_MCP_EMBEDDING_MODEL

all-MiniLM-L6-v2

A sentence-transformers model that pools by the mean, with an ONNX export in one file (onnx/model.onnx), by hub name or directory. Only the default model's vectors are tested against sentence-transformers'; another is read from its configs as well as the embedder can, and refused where it can see the vectors would differ

SEMANTIC_CODE_MCP_DEBUG

false

Enable debug logging

SEMANTIC_CODE_MCP_PROFILE

false

Enable pyinstrument profiling (a development checkout only: pyinstrument is a dev dependency)

SEMANTIC_CODE_MCP_USE_GITIGNORE

true

Let .gitignore decide what is indexed. Set false for a repo whose .gitignore hides source on purpose (an allowlist such as /*); the server then walks the file system and applies the ignore patterns only

SEMANTIC_CODE_MCP_GIT_TIMEOUT_SECONDS

10

How long git may take to list the files before discovery walks the file system instead

SEMANTIC_CODE_MCP_WATCH_FILES

true

Follow each repository's files with the system's own watcher, so a search reads what changed since the last one instead of every file, with a full scan every 5 minutes as a check: inotify on Linux, which takes a watch for each directory git does not ignore, from the limit your editor shares (fs.inotify.max_user_watches), and an inotify instance per project; FSEvents on macOS, a stream for the project's root and one for each of git's directories outside it; ReadDirectoryChangesW on Windows, a handle of the project's root, which keeps a directory holding it from being moved meanwhile. Past those limits, outside a repository, with submodules, and on a network or FUSE file system (on macOS and Windows, the one the project's root lies on) each search scans the files, as with false

SEMANTIC_CODE_MCP_SEARCH_MODE

words

How every search ranks: words, BM25 over the chunks' words, no model loaded; or words_and_meaning, the model's vectors searched too and fused with the words by rank. The first search of a project by meaning embeds every chunk inside the call, minutes at 20k chunks and about an hour at 90k; later ones embed what changed. Meaning needs the extra semantic-code-mcp[meaning]: without it a server set to it stops as it starts, naming the extra

SEMANTIC_CODE_MCP_MIN_SIMILARITY

0.35

In a search by meaning, how close in meaning to the query, a cosine similarity, a result no keyword matched must be to come back; set for the default model, whose scale another model does not share. Below 0.25, lower MIN_SIMILARITY_WHEN_WORDS_MATCH with it, or the server refuses to start

SEMANTIC_CODE_MCP_MIN_SIMILARITY_WHEN_WORDS_MATCH

0.25

The same, for a result whose words match the query's; at most MIN_SIMILARITY. A result named as the query comes back however far

SEMANTIC_CODE_MCP_IGNORE_PATTERNS

caches, virtualenvs, node_modules, top-level build/, dist/, target/

JSON list of patterns skipped in every mode. A pattern without / matches a name at any depth (node_modules); one with / matches from the project root (docs/**)

Every session on one project should run with the same IGNORE_PATTERNS and USE_GITIGNORE: an index keeps the files its last run found, so two sessions that find different files delete each other's at every search.

Pass environment variables via the env field in your MCP config:

{
  "mcpServers": {
    "semantic-code": {
      "command": "uvx",
      "args": ["semantic-code-mcp"],
      "env": {
        "SEMANTIC_CODE_MCP_DEBUG": "true",
        "SEMANTIC_CODE_MCP_LOCAL_INDEX": "true"
      }
    }
  }
}

Or with Claude Code CLI:

claude mcp add --scope user semantic-code \
  -e SEMANTIC_CODE_MCP_DEBUG=true \
  -e SEMANTIC_CODE_MCP_LOCAL_INDEX=true \
  -- uvx semantic-code-mcp

Tech Stack

Component

Choice

Rationale

MCP Framework

mcp 2.x (MCPServer)

The official Python SDK, STDIO transport

Embeddings

onnxruntime over the model's ONNX export

Local, no API costs, no torch

Index

SQLite with FTS5

In Python's standard library: one file, a write in one transaction, no server; LanceDB as an optional extra

Chunking

tree-sitter

AST-based, respects code structure

Development

uv sync                            # Install dependencies
uv run pre-commit install --hook-type pre-commit --hook-type commit-msg
uv run python -m semantic_code_mcp # Run server
uv run pytest                      # Run tests
uv run pre-commit run --all-files  # Every gate a commit runs

The gates are one list, .pre-commit-config.yaml: ruff (lint and format), ty (types), import-linter (the package's layers), vulture (code nothing calls), the project's code rules (nested functions, short names, models taken apart by field names), bandit (security), deptry (dependencies as declared), typos, a check that every test module loads, a check of the agent instruction files, the skill's gate (the skill and the server's instructions name what the server has), validate-pyproject, and the file checks of pre-commit-hooks; commit messages are held to Conventional Commits. CI runs the same hooks and the tests on every push to master and every pull request. See CONTRIBUTING.md.

Releasing

Versions are derived from git tags automatically (hatch-vcs) — there's no hardcoded version in pyproject.toml.

git tag v0.2.0
git push origin v0.2.0

CI runs the whole suite on the tagged commit, then builds the package, publishes to PyPI, and creates a GitHub Release with auto-generated notes. A required reviewer on the pypi environment would add the owner's approval before publishing; until the repository's settings name one, a green CI publishes at once.

Adding a New Language

The chunker system is designed to make adding languages straightforward. Each language needs:

  1. A tree-sitter grammar package (e.g. tree-sitter-javascript)

  2. A chunker subclass that walks the AST and extracts meaningful chunks

Steps:

uv add tree-sitter-mylang

Add the language to Language in src/semantic_code_mcp/domain/models.py, and the separator its qualified names are written with (Stack.push, outer::Stack) to PARENT_SEPARATORS beside it; then create src/semantic_code_mcp/chunkers/mylang.py:

from enum import StrEnum, auto
from typing import override

import tree_sitter
import tree_sitter_mylang as tsmylang
from tree_sitter import Node

from semantic_code_mcp.chunkers.base import BaseTreeSitterChunker, ChunkCollector, field_text
from semantic_code_mcp.domain.models import ChunkKind, Language


class NodeType(StrEnum):
    function_definition = auto()
    # ... other node types


class FieldName(StrEnum):
    name = auto()


class MyLangChunker(BaseTreeSitterChunker):
    language = Language.mylang
    grammar = tree_sitter.Language(tsmylang.language())
    extensions = (".ml",)

    @override
    def _extract_chunks(self, root: Node, collector: ChunkCollector) -> None:
        for node in root.children:
            match node.type:
                case NodeType.function_definition:
                    name = field_text(node, FieldName.name)
                    if name is not None:
                        collector.add(node, node, ChunkKind.function, name, parent=None)
                # ... other node types

Register it in src/semantic_code_mcp/app.py, and bump CHUNKER_VERSION in src/semantic_code_mcp/chunkers/composite.py, so indexes built before it are built again:

from semantic_code_mcp.chunkers.mylang import MyLangChunker

def build_chunker() -> CompositeChunker:
    return CompositeChunker([PythonChunker(), RustChunker(), MarkdownChunker(), MyLangChunker()])

Project Structure

  • src/semantic_code_mcp/server/ — the MCP tools, their answers and presenters

  • src/semantic_code_mcp/services/ — indexing, search, the registry of projects

  • src/semantic_code_mcp/discovery.py — which files get indexed (git or a file-system walk, ignore patterns)

  • src/semantic_code_mcp/watching/ — each system's watcher of a project's files (inotify, FSEvents, ReadDirectoryChangesW)

  • src/semantic_code_mcp/pools.py — the pool of processes that chunks a first index of much source

  • src/semantic_code_mcp/chunkers/ — language chunkers (base.py, composite.py, python.py, rust.py, markdown.py)

  • src/semantic_code_mcp/embedding/, src/semantic_code_mcp/storage/ — the embedder and the model it loads from the meaning extra, and the stores (SQLite, LanceDB) behind one contract

  • src/semantic_code_mcp/domain/ — models and interfaces

  • evals/ — search quality and speed on pinned corpora (uv run python -m evals quality, speed), speed also on a repository of Sentry's shape made where it runs (evals/generated/, which CI's Speed workflow times on three systems), and agents' sessions with and without the search (agents, on corpora it names)

  • docs/decisions/ — architecture decision records

  • TODO.md — epics and planning

  • CHANGELOG.md — completed work (Keep a Changelog format)

  • .claude/rules/ — context-specific coding rules for AI agents

License

MIT

Available Tools

3 tools
index_codebaseA

Index a codebase's words for search.

Splits Python, Rust and Markdown files into functions, classes, methods, module docstrings and document sections, and stores them with their words; it embeds nothing, and a server set to search by meaning too embeds them on its next search. search_code indexes on its own; call this only to re-index everything.

With force: true, it re-indexes everything even if files haven't changed; a model whose weights nothing pins, as a local path, has its vectors dropped too, so a server set to search by meaning embeds every chunk again on its next search. Otherwise, only new and modified files are indexed (incremental).

Args: project_path: Absolute path to the project; a directory inside a git repository indexes the whole repository, unless the repository ignores it or holds the user's home directory. Left out, the client's workspace root, which must lie in a git repository. force: Re-index all files regardless of changes.

Returns: The root of the project indexed, and what the run indexed and deleted.

NameRequiredDescriptionDefault
forceNo
project_pathNoAbsolute path to the project, or to a directory inside its git repository. Left out, the client's workspace root: the directory Claude Code was started in, which must lie in a git repository.

Output Schema

NameRequiredDescription
project_rootYesThe root of the project whose index this is: a directory inside a git repository is indexed, and reported, with its whole repository.
files_deletedYes
files_indexedYes
chunks_indexedYes
duration_secondsYes

TDQS

A4.4/5.0
Behavior4/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 substantial work: it discloses that no embeddings are computed here, that a meaning-search server embeds chunks lazily on its next search, and that force drops existing vectors for unpinned models. It stops short of cost, duration, or concurrency behavior for large repositories, so it is rich but not complete.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The content is front-loaded and organized with Args/Returns sections, but the embedding behavior is explained twice in near-duplicate sentences, and phrasing like "a model whose weights nothing pins, as a local path" is convoluted enough to slow parsing. It is informative but not tight.

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?

An output schema exists and the description still summarizes the return value (indexed root, what was indexed and deleted), and it handles the tricky path-resolution edge cases an agent would otherwise get wrong. Only operational scale questions (time, resource use on large repos) are left open.

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 only 50% (force has no schema description), and the Args section compensates fully for force and adds real meaning beyond the schema for project_path: a directory inside a git repo indexes the whole repository, with exceptions for ignored paths and the user's home directory. That repository-expansion rule is not inferable from the schema at all.

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 ("Index a codebase's words for search") and immediately scopes what is parsed (Python, Rust, Markdown into functions, classes, docstrings, sections) and what is not done ("it embeds nothing"). It also explicitly separates itself from the sibling search_code, so an agent can choose between them without opening a 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?

Gives an explicit when-not: "`search_code` indexes on its own; call this only to re-index everything." The force parameter section then defines the second usage mode (full re-index vs. incremental), so both branches of when-to-call are covered with the named alternative.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

index_statusA

Get the index status for a project.

Returns whether the project is indexed, when it was last updated, how many of its Python, Rust and Markdown files and their chunks are indexed, how many chunks have the model's vectors, how many files changed since, what built the index (each chunker version keeps an index of its own, and each model's vectors are kept apart in it), and whether a run, this session's or another's, holds it now, so its counts may be what the run has written so far.

Note: search_code automatically re-indexes stale files before searching, so there is no need to check or act on staleness manually.

Args: project_path: Absolute path to the project; a directory inside a git repository reports the whole repository's index, unless the repository ignores it or holds the user's home directory. Left out, the client's workspace root, which must lie in a git repository.

Returns: The root of the project whose index this is, and its status, including the counts of files and chunks.

NameRequiredDescriptionDefault
project_pathNoAbsolute path to the project, or to a directory inside its git repository. Left out, the client's workspace root: the directory Claude Code was started in, which must lie in a git repository.

Output Schema

NameRequiredDescription
modelYes
manifestYes
is_indexedYes
being_builtYesA run holds the index now, this session's or another's, and may be writing it: until it ends, the counts may be what it has written so far; a search waits for it.
files_countYes
chunks_countYes
last_updatedYes
project_rootYesThe root of the project whose index this is: a directory inside a git repository is indexed, and reported, with its whole repository.
stale_files_countYes
embedded_chunks_countYesHow many of the chunks have the server's model's vectors; a server set to search by meaning too embeds the rest before it answers.

TDQS

A3.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full load and does substantial work: it explains that each chunker version keeps its own index slice, that each model's vectors are stored separately, and that an in-flight run (this session's or another's) can make reported counts a partial snapshot. That concurrency caveat is exactly the kind of behavior an agent cannot infer from a schema. It never states outright that the call is a side-effect-free read, which is why it stops short of a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The core purpose and the staleness note are front-loaded, which is good, but the prose is dense and long for a single optional parameter. The Args block largely restates the schema description and the Returns block restates information that the output schema already supplies, so a meaningful share of the text does not earn its place.

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?

For a one-optional-parameter read tool with an output schema and no annotations, the description covers what the call does, the argument's path-resolution edge cases, and concurrent-run semantics. The only real gap is return-value framing, which the output schema already handles, making the verbose Returns paragraph redundant rather than missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the parameter is already documented and the baseline is 3. The description does add nuance beyond the schema text: the git-repository promotion rule, the exception for ignored directories and the user's home directory, and the workspace-root default. That is a modest addition over an already-complete schema, so it lands at the baseline rather than above it.

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?

"Get the index status for a project" pairs a specific verb with a specific resource, and the enumeration of what the status contains (indexed flag, last update, per-language file/chunk counts, vector coverage, changed files, chunker/model partitioning, active run) removes any ambiguity about what "status" means. It distinguishes itself from search_code via the staleness note, but never mentions the sibling index_codebase, so the read-vs-write split with that tool is left implicit.

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?

Contains an explicit when-not-to-use: staleness should not be checked or acted on manually because search_code re-indexes automatically. That is real routing guidance an agent could otherwise get wrong. What is missing is a positive statement of the scenario that does warrant this call (diagnostics, verifying an index_codebase run finished) rather than a pointer to the indexing sibling.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_codeA

Find the functions, classes, methods and document sections of a project by the words their code uses.

It matches the words of code: write the identifiers and terms the code would use, and on a miss search again with others. A query that is a name, or a qualified one, brings the chunks of that name first:

  • "retry backoff delay attempts"

  • "parse_args config options"

  • "Stack.push"

For every place a name is used, Grep: this returns only the best matches.

Indexes Python, Rust and Markdown files. Automatically indexes the project if not already indexed, and re-indexes any files that have changed since the last search.

Args: query: The words to find, as code would write them, or a name. project_path: Absolute path to the project, or to a directory inside its git repository, which keeps the search to that directory; left out, the client's workspace root, which must lie in a git repository. limit: Maximum number of results to return. chunk_kinds: Only chunks of these kinds. languages: Only chunks of files in these languages. path_glob: Only files this glob names, read from the directory searched. exclude_glob: No file this glob names, read as path_glob is. include_debug: Also return timings and counts of the search.

Returns: The root of the project whose index answered and the directory the search kept to; then matching chunks, best first, with the file's absolute path, line range, name, kind (module, function, method, class, section), the class, impl, module or heading it belongs to, and content.

NameRequiredDescriptionDefault
limitNo
queryYes
languagesNoOnly chunks of files in these languages.
path_globNoOnly files this glob names, as in a .gitignore: without a slash a name at any depth below the directory project_path names (`*.rs`, and `tests/`, as a trailing slash does not count), with one before a name a path from that directory (`src/**/*.py`, `/tests`); `*` stays within a directory, `**` crosses any number, even inside a name (`src**.py`, where a .gitignore reads it as `*`), `?` is one character, and a directory takes what it holds. No character classes (`[ab]`), braces, escapes, a leading `!`, `..` or more than 1000 characters: each is refused with the reason.
chunk_kindsNoOnly chunks of these kinds; Rust's structs, enums, traits and impls are classes.
exclude_globNoNo file this glob names, read as path_glob is (`tests`, `*_test.py`).
project_pathNoAbsolute path to the project, or to a directory inside its git repository. Left out, the client's workspace root: the directory Claude Code was started in, which must lie in a git repository.
include_debugNo

Output Schema

NameRequiredDescription
debugYes
resultsYes
project_rootYesThe root of the project whose index answered: as project_path, it searches the whole repository.
searched_directoryYesThe directory the search kept to: project_root, or a directory under it; the search did not look outside it.

TDQS

A3.9/5.0
Behavior4/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 well: it discloses the side effect of automatically indexing the project and re-indexing changed files, the language coverage (Python, Rust, Markdown), that only best matches are returned, and that invalid globs are refused with a reason. It does not cover permissions, cost/latency, or behavior when no results are found, so it stops short of fully transparent.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Well front-loaded and conventionally structured with Args/Returns sections, but the Args block duplicates schema text nearly verbatim for several parameters, and phrasing like 'For every place a name is used, Grep: this returns only the best matches' is awkward and costs a read. The examples earn their place; the restated parameter lines mostly do not.

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?

For an eight-parameter tool with no annotations, the definition covers the essentials: what is indexed, when indexing happens, how scoping works, and what a result contains (path, line range, name, kind, owner, content), which the output schema also backs up. Missing only edge-case behavior such as empty result sets or failure modes, which are minor given the output schema's presence.

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?

At 63% schema coverage, the Args block touches all eight parameters and adds real meaning for the two most important ones: query (write identifiers as the code would, with examples) and project_path (a directory inside the repo narrows the search). The remaining entries (limit, chunk_kinds, languages, include_debug) largely restate the schema descriptions, so the added value is concentrated rather than uniform.

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?

States a specific verb and resource with unusual precision: 'Find the functions, classes, methods and document sections of a project by the words their code uses.' It also carves out a boundary against plain text search ('For every place a name is used, Grep: this returns only the best matches'), so the agent can tell it apart from a generic grep without opening the schema. The sibling tools index_codebase/index_status are only addressed indirectly via the auto-indexing sentence.

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?

Gives concrete usage guidance: write the identifiers the code would use, and 'on a miss search again with others', plus three worked query examples. It names the alternative (Grep) and the condition that selects it. There is no explicit statement of when this tool should be avoided in favor of index_codebase or index_status, but the context is otherwise clear.

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. 3 tool updatesv0.5.0
    • Changedindex_codebase15 fields changed
      • addedInput schema / properties / project_path / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • addedInput schema / properties / project_path / default
        Added value: +null
      • addedInput schema / properties / project_path / description
        Added value: +"Absolute path to the project, or to a directory inside its git repository. Left out, the client's workspace root: the directory Claude Code was started in, which must lie in a git repository."
      • removedInput schema / properties / project_path / type
        Removed value: -"string"
      • removedInput schema / required
        Removed value: -[
        -  "project_path"
        -]
      • removedOutput schema / $defs
        Removed value: -{
        -  "ErrorResponse": {
        -    "description": "Error response for tool failures.",
        -    "properties": {
        -      "error": {
        -        "title": "Error",
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "error"
        -    ],
        -    "title": "ErrorResponse",
        -    "type": "object"
        -  },
        -  "IndexCodebaseResponse": {
        -    "description": "Response from index_codebase tool.",
        -    "properties": {
        -      "chunks_indexed": {
        -        "title": "Chunks Indexed",
        -        "type": "integer"
        -      },
        -      "duration_seconds": {
        -        "title": "Duration Seconds",
        -        "type": "number"
        -      },
        -      "files_deleted": {
        -        "title": "Files Deleted",
        -        "type": "integer"
        -      },
        -      "files_indexed": {
        -        "title": "Files Indexed",
        -        "type": "integer"
        -      }
        -    },
        -    "required": [
        -      "files_indexed",
        -      "chunks_indexed",
        -      "files_deleted",
        -      "duration_seconds"
        -    ],
        -    "title": "IndexCodebaseResponse",
        -    "type": "object"
        -  }
        -}
      • addedOutput schema / description
        Added value: +"The answer of `index_codebase`."
      • addedOutput schema / properties / chunks_indexed
        Added value: +{
        +  "title": "Chunks Indexed",
        +  "type": "integer"
        +}
      • addedOutput schema / properties / duration_seconds
        Added value: +{
        +  "title": "Duration Seconds",
        +  "type": "number"
        +}
      • addedOutput schema / properties / files_deleted
        Added value: +{
        +  "title": "Files Deleted",
        +  "type": "integer"
        +}
      • addedOutput schema / properties / files_indexed
        Added value: +{
        +  "title": "Files Indexed",
        +  "type": "integer"
        +}
      • addedOutput schema / properties / project_root
        Added value: +{
        +  "description": "The root of the project whose index this is: a directory inside a git repository is indexed, and reported, with its whole repository.",
        +  "title": "Project Root",
        +  "type": "string"
        +}
      • removedOutput schema / properties / result
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "$ref": "#/$defs/IndexCodebaseResponse"
        -    },
        -    {
        -      "$ref": "#/$defs/ErrorResponse"
        -    }
        -  ],
        -  "title": "Result"
        -}
      • changedOutput schema / required
        Previous value: -[
        -  "result"
        -]New value: +[
        +  "project_root",
        +  "files_indexed",
        +  "chunks_indexed",
        +  "files_deleted",
        +  "duration_seconds"
        +]
      • changedOutput schema / title
        Previous value: -"index_codebaseOutput"New value: +"IndexCodebaseResponse"
    • Changedindex_status23 fields changed
      • addedInput schema / properties / project_path / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • addedInput schema / properties / project_path / default
        Added value: +null
      • addedInput schema / properties / project_path / description
        Added value: +"Absolute path to the project, or to a directory inside its git repository. Left out, the client's workspace root: the directory Claude Code was started in, which must lie in a git repository."
      • removedInput schema / properties / project_path / type
        Removed value: -"string"
      • removedInput schema / required
        Removed value: -[
        -  "project_path"
        -]
      • removedOutput schema / $defs / ErrorResponse
        Removed value: -{
        -  "description": "Error response for tool failures.",
        -  "properties": {
        -    "error": {
        -      "title": "Error",
        -      "type": "string"
        -    }
        -  },
        -  "required": [
        -    "error"
        -  ],
        -  "title": "ErrorResponse",
        -  "type": "object"
        -}
      • addedOutput schema / $defs / IndexManifestView
        Added value: +{
        +  "description": "What built an index: its chunker and the store's schema, each of whose versions keeps\nan index of its own.",
        +  "properties": {
        +    "chunker_version": {
        +      "title": "Chunker Version",
        +      "type": "integer"
        +    },
        +    "schema_version": {
        +      "title": "Schema Version",
        +      "type": "integer"
        +    }
        +  },
        +  "required": [
        +    "chunker_version",
        +    "schema_version"
        +  ],
        +  "title": "IndexManifestView",
        +  "type": "object"
        +}
      • removedOutput schema / $defs / IndexStatusResponse
        Removed value: -{
        -  "description": "Response from index_status tool.",
        -  "properties": {
        -    "chunks_count": {
        -      "title": "Chunks Count",
        -      "type": "integer"
        -    },
        -    "files_count": {
        -      "title": "Files Count",
        -      "type": "integer"
        -    },
        -    "is_indexed": {
        -      "title": "Is Indexed",
        -      "type": "boolean"
        -    },
        -    "last_updated": {
        -      "anyOf": [
        -        {
        -          "type": "string"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "title": "Last Updated"
        -    }
        -  },
        -  "required": [
        -    "is_indexed",
        -    "last_updated",
        -    "files_count",
        -    "chunks_count"
        -  ],
        -  "title": "IndexStatusResponse",
        -  "type": "object"
        -}
      • addedOutput schema / $defs / ModelView
        Added value: +{
        +  "description": "The model a search by meaning embeds with, whose vectors `embedded_chunks_count`\ncounts: the index keeps each model's vectors apart.",
        +  "properties": {
        +    "embedder_version": {
        +      "title": "Embedder Version",
        +      "type": "integer"
        +    },
        +    "model_name": {
        +      "title": "Model Name",
        +      "type": "string"
        +    },
        +    "model_revision": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "title": "Model Revision"
        +    }
        +  },
        +  "required": [
        +    "model_name",
        +    "model_revision",
        +    "embedder_version"
        +  ],
        +  "title": "ModelView",
        +  "type": "object"
        +}
      • addedOutput schema / description
        Added value: +"The answer of `index_status`."
      • addedOutput schema / properties / being_built
        Added value: +{
        +  "description": "A run holds the index now, this session's or another's, and may be writing it: until it ends, the counts may be what it has written so far; a search waits for it.",
        +  "title": "Being Built",
        +  "type": "boolean"
        +}
      • addedOutput schema / properties / chunks_count
        Added value: +{
        +  "title": "Chunks Count",
        +  "type": "integer"
        +}
      • addedOutput schema / properties / embedded_chunks_count
        Added value: +{
        +  "description": "How many of the chunks have the server's model's vectors; a server set to search by meaning too embeds the rest before it answers.",
        +  "title": "Embedded Chunks Count",
        +  "type": "integer"
        +}
      • addedOutput schema / properties / files_count
        Added value: +{
        +  "title": "Files Count",
        +  "type": "integer"
        +}
      • addedOutput schema / properties / is_indexed
        Added value: +{
        +  "title": "Is Indexed",
        +  "type": "boolean"
        +}
      • addedOutput schema / properties / last_updated
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "title": "Last Updated"
        +}
      • addedOutput schema / properties / manifest
        Added value: +{
        +  "anyOf": [
        +    {
        +      "$ref": "#/$defs/IndexManifestView"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ]
        +}
      • addedOutput schema / properties / model
        Added value: +{
        +  "$ref": "#/$defs/ModelView"
        +}
      • addedOutput schema / properties / project_root
        Added value: +{
        +  "description": "The root of the project whose index this is: a directory inside a git repository is indexed, and reported, with its whole repository.",
        +  "title": "Project Root",
        +  "type": "string"
        +}
      • removedOutput schema / properties / result
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "$ref": "#/$defs/IndexStatusResponse"
        -    },
        -    {
        -      "$ref": "#/$defs/ErrorResponse"
        -    }
        -  ],
        -  "title": "Result"
        -}
      • addedOutput schema / properties / stale_files_count
        Added value: +{
        +  "title": "Stale Files Count",
        +  "type": "integer"
        +}
      • changedOutput schema / required
        Previous value: -[
        -  "result"
        -]New value: +[
        +  "project_root",
        +  "is_indexed",
        +  "last_updated",
        +  "files_count",
        +  "chunks_count",
        +  "embedded_chunks_count",
        +  "model",
        +  "stale_files_count",
        +  "manifest",
        +  "being_built"
        +]
      • changedOutput schema / title
        Previous value: -"index_statusOutput"New value: +"IndexStatusResponse"
    • Changedsearch_code35 fields changed
      • addedInput schema / $defs
        Added value: +{
        +  "ChunkKind": {
        +    "description": "What a chunk is. Rust's structs, enums, traits and impls are classes.",
        +    "enum": [
        +      "module",
        +      "function",
        +      "class",
        +      "method",
        +      "section"
        +    ],
        +    "title": "ChunkKind",
        +    "type": "string"
        +  },
        +  "Language": {
        +    "description": "A language the chunkers read.",
        +    "enum": [
        +      "python",
        +      "rust",
        +      "markdown"
        +    ],
        +    "title": "Language",
        +    "type": "string"
        +  }
        +}
      • addedInput schema / properties / chunk_kinds
        Added value: +{
        +  "anyOf": [
        +    {
        +      "items": {
        +        "$ref": "#/$defs/ChunkKind"
        +      },
        +      "type": "array"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Only chunks of these kinds; Rust's structs, enums, traits and impls are classes.",
        +  "title": "Chunk Kinds"
        +}
      • addedInput schema / properties / exclude_glob
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "No file this glob names, read as path_glob is (`tests`, `*_test.py`).",
        +  "title": "Exclude Glob"
        +}
      • addedInput schema / properties / include_debug
        Added value: +{
        +  "default": false,
        +  "title": "Include Debug",
        +  "type": "boolean"
        +}
      • addedInput schema / properties / languages
        Added value: +{
        +  "anyOf": [
        +    {
        +      "items": {
        +        "$ref": "#/$defs/Language"
        +      },
        +      "type": "array"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Only chunks of files in these languages.",
        +  "title": "Languages"
        +}
      • addedInput schema / properties / limit / maximum
        Added value: +50
      • addedInput schema / properties / limit / minimum
        Added value: +1
      • addedInput schema / properties / path_glob
        Added value: +{
        +  "anyOf": [
        +    {
        +      "type": "string"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ],
        +  "default": null,
        +  "description": "Only files this glob names, as in a .gitignore: without a slash a name at any depth below the directory project_path names (`*.rs`, and `tests/`, as a trailing slash does not count), with one before a name a path from that directory (`src/**/*.py`, `/tests`); `*` stays within a directory, `**` crosses any number, even inside a name (`src**.py`, where a .gitignore reads it as `*`), `?` is one character, and a directory takes what it holds. No character classes (`[ab]`), braces, escapes, a leading `!`, `..` or more than 1000 characters: each is refused with the reason.",
        +  "title": "Path Glob"
        +}
      • addedInput schema / properties / project_path / anyOf
        Added value: +[
        +  {
        +    "type": "string"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • addedInput schema / properties / project_path / default
        Added value: +null
      • addedInput schema / properties / project_path / description
        Added value: +"Absolute path to the project, or to a directory inside its git repository. Left out, the client's workspace root: the directory Claude Code was started in, which must lie in a git repository."
      • removedInput schema / properties / project_path / type
        Removed value: -"string"
      • changedInput schema / required
        Previous value: -[
        -  "query",
        -  "project_path"
        -]New value: +[
        +  "query"
        +]
      • addedOutput schema / $defs / ChunkKind
        Added value: +{
        +  "description": "What a chunk is. Rust's structs, enums, traits and impls are classes.",
        +  "enum": [
        +    "module",
        +    "function",
        +    "class",
        +    "method",
        +    "section"
        +  ],
        +  "title": "ChunkKind",
        +  "type": "string"
        +}
      • removedOutput schema / $defs / ErrorResponse
        Removed value: -{
        -  "description": "Error response for tool failures.",
        -  "properties": {
        -    "error": {
        -      "title": "Error",
        -      "type": "string"
        -    }
        -  },
        -  "required": [
        -    "error"
        -  ],
        -  "title": "ErrorResponse",
        -  "type": "object"
        -}
      • removedOutput schema / $defs / FormattedSearchResult
        Removed value: -{
        -  "description": "A search result formatted for response.",
        -  "properties": {
        -    "chunk_type": {
        -      "title": "Chunk Type",
        -      "type": "string"
        -    },
        -    "content": {
        -      "title": "Content",
        -      "type": "string"
        -    },
        -    "file_path": {
        -      "title": "File Path",
        -      "type": "string"
        -    },
        -    "line_end": {
        -      "title": "Line End",
        -      "type": "integer"
        -    },
        -    "line_start": {
        -      "title": "Line Start",
        -      "type": "integer"
        -    },
        -    "name": {
        -      "title": "Name",
        -      "type": "string"
        -    },
        -    "score": {
        -      "title": "Score",
        -      "type": "number"
        -    },
        -    "truncated": {
        -      "default": false,
        -      "title": "Truncated",
        -      "type": "boolean"
        -    }
        -  },
        -  "required": [
        -    "file_path",
        -    "line_start",
        -    "line_end",
        -    "name",
        -    "chunk_type",
        -    "content",
        -    "score"
        -  ],
        -  "title": "FormattedSearchResult",
        -  "type": "object"
        -}
      • removedOutput schema / $defs / IndexResultSummary
        Removed value: -{
        -  "description": "Summary of indexing result for search debug info.",
        -  "properties": {
        -    "chunks_indexed": {
        -      "title": "Chunks Indexed",
        -      "type": "integer"
        -    },
        -    "files_indexed": {
        -      "title": "Files Indexed",
        -      "type": "integer"
        -    }
        -  },
        -  "required": [
        -    "files_indexed",
        -    "chunks_indexed"
        -  ],
        -  "title": "IndexResultSummary",
        -  "type": "object"
        -}
      • removedOutput schema / $defs / IndexStatusSummary
        Removed value: -{
        -  "description": "Summary of index status for search debug info.",
        -  "properties": {
        -    "chunks_count": {
        -      "title": "Chunks Count",
        -      "type": "integer"
        -    },
        -    "files_count": {
        -      "title": "Files Count",
        -      "type": "integer"
        -    },
        -    "was_stale": {
        -      "anyOf": [
        -        {
        -          "type": "boolean"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "title": "Was Stale"
        -    }
        -  },
        -  "required": [
        -    "files_count",
        -    "chunks_count"
        -  ],
        -  "title": "IndexStatusSummary",
        -  "type": "object"
        -}
      • addedOutput schema / $defs / IndexingSummaryView
        Added value: +{
        +  "description": "What the search indexed before it searched.",
        +  "properties": {
        +    "chunks_indexed": {
        +      "title": "Chunks Indexed",
        +      "type": "integer"
        +    },
        +    "files_indexed": {
        +      "title": "Files Indexed",
        +      "type": "integer"
        +    }
        +  },
        +  "required": [
        +    "files_indexed",
        +    "chunks_indexed"
        +  ],
        +  "title": "IndexingSummaryView",
        +  "type": "object"
        +}
      • removedOutput schema / $defs / SearchDebugInfo
        Removed value: -{
        -  "description": "Debug information included in search response.",
        -  "properties": {
        -    "index_result": {
        -      "$ref": "#/$defs/IndexResultSummary"
        -    },
        -    "index_status": {
        -      "$ref": "#/$defs/IndexStatusSummary"
        -    },
        -    "stats": {
        -      "$ref": "#/$defs/SearchStats"
        -    },
        -    "timings": {
        -      "$ref": "#/$defs/SearchTimings"
        -    }
        -  },
        -  "required": [
        -    "timings",
        -    "stats",
        -    "index_status",
        -    "index_result"
        -  ],
        -  "title": "SearchDebugInfo",
        -  "type": "object"
        -}
      • addedOutput schema / $defs / SearchDebugView
        Added value: +{
        +  "description": "Timings and counts of a search, sent only when asked for.",
        +  "properties": {
        +    "indexing": {
        +      "anyOf": [
        +        {
        +          "$ref": "#/$defs/IndexingSummaryView"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ]
        +    },
        +    "stats": {
        +      "$ref": "#/$defs/SearchStatsView"
        +    },
        +    "timings": {
        +      "$ref": "#/$defs/SearchTimingsView"
        +    }
        +  },
        +  "required": [
        +    "timings",
        +    "stats",
        +    "indexing"
        +  ],
        +  "title": "SearchDebugView",
        +  "type": "object"
        +}
      • removedOutput schema / $defs / SearchResponse
        Removed value: -{
        -  "description": "Complete search response with results and debug info.",
        -  "properties": {
        -    "debug": {
        -      "$ref": "#/$defs/SearchDebugInfo"
        -    },
        -    "results": {
        -      "items": {
        -        "$ref": "#/$defs/FormattedSearchResult"
        -      },
        -      "title": "Results",
        -      "type": "array"
        -    }
        -  },
        -  "required": [
        -    "results",
        -    "debug"
        -  ],
        -  "title": "SearchResponse",
        -  "type": "object"
        -}
      • addedOutput schema / $defs / SearchResultView
        Added value: +{
        +  "description": "One hit as the agent sees it; `content` is cut to a line limit, `truncated` says so.",
        +  "properties": {
        +    "content": {
        +      "title": "Content",
        +      "type": "string"
        +    },
        +    "file_path": {
        +      "title": "File Path",
        +      "type": "string"
        +    },
        +    "kind": {
        +      "$ref": "#/$defs/ChunkKind"
        +    },
        +    "line_end": {
        +      "title": "Line End",
        +      "type": "integer"
        +    },
        +    "line_start": {
        +      "title": "Line Start",
        +      "type": "integer"
        +    },
        +    "name": {
        +      "title": "Name",
        +      "type": "string"
        +    },
        +    "parent": {
        +      "anyOf": [
        +        {
        +          "type": "string"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "title": "Parent"
        +    },
        +    "truncated": {
        +      "title": "Truncated",
        +      "type": "boolean"
        +    }
        +  },
        +  "required": [
        +    "file_path",
        +    "line_start",
        +    "line_end",
        +    "name",
        +    "kind",
        +    "parent",
        +    "content",
        +    "truncated"
        +  ],
        +  "title": "SearchResultView",
        +  "type": "object"
        +}
      • removedOutput schema / $defs / SearchStats
        Removed value: -{
        -  "description": "Statistics about search results.",
        -  "properties": {
        -    "filtered_out": {
        -      "title": "Filtered Out",
        -      "type": "integer"
        -    },
        -    "results_count": {
        -      "title": "Results Count",
        -      "type": "integer"
        -    },
        -    "tokens_estimate": {
        -      "title": "Tokens Estimate",
        -      "type": "integer"
        -    },
        -    "unique_files": {
        -      "title": "Unique Files",
        -      "type": "integer"
        -    }
        -  },
        -  "required": [
        -    "results_count",
        -    "filtered_out",
        -    "unique_files",
        -    "tokens_estimate"
        -  ],
        -  "title": "SearchStats",
        -  "type": "object"
        -}
      • addedOutput schema / $defs / SearchStatsView
        Added value: +{
        +  "description": "What a search kept and dropped.",
        +  "properties": {
        +    "filtered_out": {
        +      "title": "Filtered Out",
        +      "type": "integer"
        +    },
        +    "results_count": {
        +      "title": "Results Count",
        +      "type": "integer"
        +    },
        +    "tokens_estimate": {
        +      "title": "Tokens Estimate",
        +      "type": "integer"
        +    },
        +    "unique_files": {
        +      "title": "Unique Files",
        +      "type": "integer"
        +    }
        +  },
        +  "required": [
        +    "results_count",
        +    "filtered_out",
        +    "unique_files",
        +    "tokens_estimate"
        +  ],
        +  "title": "SearchStatsView",
        +  "type": "object"
        +}
      • removedOutput schema / $defs / SearchTimings
        Removed value: -{
        -  "description": "Timing breakdown for search operation.",
        -  "properties": {
        -    "embedding_ms": {
        -      "title": "Embedding Ms",
        -      "type": "number"
        -    },
        -    "indexing_ms": {
        -      "anyOf": [
        -        {
        -          "type": "number"
        -        },
        -        {
        -          "type": "null"
        -        }
        -      ],
        -      "default": null,
        -      "title": "Indexing Ms"
        -    },
        -    "search_ms": {
        -      "title": "Search Ms",
        -      "type": "number"
        -    },
        -    "total_ms": {
        -      "title": "Total Ms",
        -      "type": "number"
        -    }
        -  },
        -  "required": [
        -    "embedding_ms",
        -    "search_ms",
        -    "total_ms"
        -  ],
        -  "title": "SearchTimings",
        -  "type": "object"
        -}
      • addedOutput schema / $defs / SearchTimingsView
        Added value: +{
        +  "description": "Where the time of a search went, in milliseconds.",
        +  "properties": {
        +    "embedding_ms": {
        +      "title": "Embedding Ms",
        +      "type": "number"
        +    },
        +    "indexing_ms": {
        +      "anyOf": [
        +        {
        +          "type": "number"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "title": "Indexing Ms"
        +    },
        +    "search_ms": {
        +      "title": "Search Ms",
        +      "type": "number"
        +    },
        +    "total_ms": {
        +      "title": "Total Ms",
        +      "type": "number"
        +    }
        +  },
        +  "required": [
        +    "embedding_ms",
        +    "search_ms",
        +    "total_ms",
        +    "indexing_ms"
        +  ],
        +  "title": "SearchTimingsView",
        +  "type": "object"
        +}
      • addedOutput schema / description
        Added value: +"The answer of `search_code`."
      • addedOutput schema / properties / debug
        Added value: +{
        +  "anyOf": [
        +    {
        +      "$ref": "#/$defs/SearchDebugView"
        +    },
        +    {
        +      "type": "null"
        +    }
        +  ]
        +}
      • addedOutput schema / properties / project_root
        Added value: +{
        +  "description": "The root of the project whose index answered: as project_path, it searches the whole repository.",
        +  "title": "Project Root",
        +  "type": "string"
        +}
      • removedOutput schema / properties / result
        Removed value: -{
        -  "anyOf": [
        -    {
        -      "$ref": "#/$defs/SearchResponse"
        -    },
        -    {
        -      "$ref": "#/$defs/ErrorResponse"
        -    }
        -  ],
        -  "title": "Result"
        -}
      • addedOutput schema / properties / results
        Added value: +{
        +  "items": {
        +    "$ref": "#/$defs/SearchResultView"
        +  },
        +  "title": "Results",
        +  "type": "array"
        +}
      • addedOutput schema / properties / searched_directory
        Added value: +{
        +  "description": "The directory the search kept to: project_root, or a directory under it; the search did not look outside it.",
        +  "title": "Searched Directory",
        +  "type": "string"
        +}
      • changedOutput schema / required
        Previous value: -[
        -  "result"
        -]New value: +[
        +  "project_root",
        +  "searched_directory",
        +  "results",
        +  "debug"
        +]
      • changedOutput schema / title
        Previous value: -"search_codeOutput"New value: +"SearchCodeResponse"
  2. 3 tool updatesv0.1.0
    • First observedindex_codebase
    • First observedindex_status
    • First observedsearch_code

TDQS

A3.9/5.0

Scored across 3 tools

Disambiguation4/5

The three tools map to distinct operations: retrieval (search_code), bulk indexing (index_codebase), and inspection (index_status). The only overlap is that search_code auto-indexes and re-indexes changed files, but the description explicitly says to call index_codebase 'only to re-index everything', which resolves the boundary.

Naming Consistency4/5

All names are snake_case verb_noun (search_code, index_codebase, index_status), which is predictable. Minor deviation: index_status is a read-only getter sharing the 'index_' prefix with the mutating index_codebase, but the meaning is still clear.

Tool Count4/5

Three tools is lean but each earns its place for a focused code-search server: search, index, and status. Nothing looks padded or redundant, though the surface is on the thin side of the 3-15 sweet spot.

Completeness4/5

The search lifecycle (index, re-index, status, query with filters) is covered with no dead ends for its stated purpose. Gaps are minor: there is no explicit index deletion/reset tool, so force re-index is the only workaround, and no way to fetch chunks by file path without a query.

Maintenance

ActivityNo data
ResponsivenessSyncing

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    C
    maintenance
    A semantic code search MCP server that enables natural language queries against your codebase, supporting features like related file discovery and context expansion, all running locally.
    2
    -
  • A
    license
    A
    quality
    D
    maintenance
    MCP server for semantic code search that indexes your codebase and allows AI editors to search using natural language queries.
    9
    21 npm
    52
    MIT
  • F
    license
    Not graded
    quality
    D
    maintenance
    A local MCP server that enables LLM clients like Claude to perform semantic code search and answer questions about a codebase using tree-sitter parsing and sqlite-vec vector storage.
    -