semantic-code-mcp
Use this MCP server to index codebases and retrieve ranked code chunks through search.
Search code by words, with optional meaning-based search when configured; symbol-like queries prioritize definitions.
Auto-index on first search and incrementally re-index changed files.
Manually index a codebase, optionally forcing a full rebuild.
Check index status: indexed state, last update, file/chunk counts, staleness, and embedded-chunk details.
Filter results by chunk kind, language, path glob, and exclusion glob; limit and debug results.
Supports Python, Rust, and Markdown, returning file paths, line ranges, names, kinds, parents, content, and ranks.
Enables semantic indexing and searching of Python codebases by using tree-sitter to parse AST structures like functions, classes, and methods for precise code retrieval.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@semantic-code-mcp find the logic for processing user authentication in /home/user/project"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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)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
Storage — the chunks and a full-text index of their words in one SQLite database per index (LanceDB, an optional extra, in its place)
Search — by words (BM25), the chunks named as the query first
Meaning, by a setting — with the extra
semantic-code-mcp[meaning]andSEMANTIC_CODE_MCP_SEARCH_MODE=words_and_meaningthe 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-mcpClaude 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-mcpOn an earlier Claude Code, add the marketplace first:
/plugin marketplace add vrppaul/semantic-code-mcp
/plugin install semantic-code@semantic-code-marketplaceClaude Code, the server alone:
claude mcp add --scope user semantic-code -- uvx semantic-code-mcpThe 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-mcpOr 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 |
|
| required | The words to find, as code would write them, or a name ( |
|
| 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 |
|
|
| Maximum number of results, at most 50 |
|
| all | Only chunks of these kinds: |
|
| all | Only chunks of files in these languages: |
|
| none | Only files this glob names, as in a |
|
| none | No file this glob names, read as |
|
|
| 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 |
|
| 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 |
|
|
| 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 |
|
| 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 |
|
| Where indexes are stored |
|
| What keeps the indexes: |
|
| Store index in |
|
| A sentence-transformers model that pools by the mean, with an ONNX export in one file ( |
|
| Enable debug logging |
|
| Enable pyinstrument profiling (a development checkout only: pyinstrument is a dev dependency) |
|
| Let |
|
| How long git may take to list the files before discovery walks the file system instead |
|
| 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 ( |
|
| How every search ranks: |
|
| 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 |
|
| The same, for a result whose words match the query's; at most |
| caches, virtualenvs, | JSON list of patterns skipped in every mode. A pattern without |
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-mcpTech Stack
Component | Choice | Rationale |
MCP Framework | mcp 2.x ( | 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 runsThe 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.0CI 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:
A tree-sitter grammar package (e.g.
tree-sitter-javascript)A chunker subclass that walks the AST and extracts meaningful chunks
Steps:
uv add tree-sitter-mylangAdd 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 typesRegister 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 presenterssrc/semantic_code_mcp/services/— indexing, search, the registry of projectssrc/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 sourcesrc/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 themeaningextra, and the stores (SQLite, LanceDB) behind one contractsrc/semantic_code_mcp/domain/— models and interfacesevals/— 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'sSpeedworkflow times on three systems), and agents' sessions with and without the search (agents, on corpora it names)docs/decisions/— architecture decision recordsTODO.md— epics and planningCHANGELOG.md— completed work (Keep a Changelog format).claude/rules/— context-specific coding rules for AI agents
License
MIT
Available Tools
3 toolsindex_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.
Inputs: force, project_path. Returns: The answer of `index_codebase`.
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.
Inputs: project_path. Returns: The answer of `index_status`.
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.
Inputs: query (required), limit, languages, path_glob, +4 more. Returns: The answer of `search_code`.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 3 tools
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.
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.
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.
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
Related MCP Connectors
An MCP server for search and retrieval over a local LanceDB database
An MCP server that gives your AI access to the source code and docs of all public github repos
Capability registry for the agentic economy. Semantic search over verified MCP server listings.
Related MCP Servers
- FlicenseNot gradedqualityCmaintenanceA 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-
- AlicenseAqualityDmaintenanceMCP server for semantic code search that indexes your codebase and allows AI editors to search using natural language queries.99 npm52MIT
- FlicenseNot gradedqualityDmaintenanceA 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.-
- FlicenseBqualityDmaintenanceA local MCP server that indexes codebases using Tree-sitter AST parsing and LanceDB, enabling AI models to search across repositories with sub-second latency.5-