semlith
OfficialSemlith's MCP server provides local hybrid search over indexed code/documents plus code-graph lookups for AI agents.
semlith_search: rank file:line spans for a query; supports exact/regex grep, path globs, language, code/docs preference, and store selection.semlith_brief: return spans, top-span text, and one-hop callers/callees in one call.semlith_read: read a path span, single line, or whole symbol.semlith_stats: show what is indexed per store.semlith_files: list folder contents or a directory tree with symbols.semlith_symbol: locate definitions with callers/callees, including up to 20 names at once.semlith_neighbors: show who calls a symbol and what it calls.semlith_impact: find every transitive call site affected by changing a symbol.
Semlith
Local search over your code and documents for AI agents.
Install · Quick start · Agents · Portal · Benchmarks · Docs
Why Semlith
An agent working in a large codebase either reads too much, which is slow and expensive, or guesses which file to open, which is often wrong. What it needs is the few spans that answer the question. Semlith finds them.
Hybrid search. Every query runs against a vector index (meaning) and a keyword index (exact names), and the two rankings are fused. How does retry backoff work and
EMBED_BATCHboth land on the right chunk.A code graph built on the same pass. Definitions become nodes, and calls, imports and re-exports become edges, each labelled with how well it is supported. Ask what calls a function, what breaks if it changes, or how two symbols connect.
Always current. A watcher re-embeds a file when you save it. There is no build step and nothing to go stale against your working tree.
Local by design. No API keys, and no network at query time unless you sign in to Semlith Cloud, a separate hosted service, with
semlith cloud login. The portal binds to127.0.0.1only. Model weights and acceleration packs are the only downloads, each pinned by digest, and--airgaprefuses every outbound request before a connection opens.Made for agents. An MCP server with eight tools listed by default and sixteen in all, registered in your agent clients by
semlith setup, and a ledger of every retrieval with the tokens it saved.More than code. PDFs, Office documents, notebooks and Markdown are read as text, and images are searchable by what they show.
Related MCP server: mcp-local-rag
Install
One command, no Rust toolchain needed.
macOS and Linux
curl --proto '=https' --tlsv1.2 -fsSL https://raw.githubusercontent.com/semlith/semlith/main/install.sh | shWindows
irm https://raw.githubusercontent.com/semlith/semlith/main/install.ps1 | iexThe installer picks the release for your machine, verifies the ed25519
signature on the release's SHA256SUMS, checks the archive against it,
unpacks it into ~/.semlith/bin and runs semlith setup. Setup puts Semlith
on your PATH, downloads the embedding model, registers Semlith with each agent
client that has a registration CLI, and installs the daemon as a login service.
Run semlith setup again at any time to repair an install, and --yes takes
every default.
Variable | Effect |
| Install a specific release tag. |
| Install somewhere other than |
| Skip the login service. |
You can also run cargo install semlith, or take an archive from
the releases page.
semlith upgrade replaces the binary with the newest release, and Semlith never
checks for updates on its own.
Requirements. A 64-bit machine running macOS on Apple silicon, Linux on
x86_64 or aarch64, or Windows on x86_64 (Windows PowerShell 5.1 is enough).
The Linux archives ship libonnxruntime.so beside the binary and need only
glibc 2.34 or later and libstdc++ (GLIBCXX_3.4.22). Debian 12, Ubuntu 22.04,
RHEL 9 and Amazon Linux 2023 all qualify. Intel macOS is not supported, because
ONNX Runtime no longer ships for it.
Verifying the installer
The installer writes the binary to ~/.semlith/bin (mode 700 on macOS and
Linux) and, on Linux, libonnxruntime.so beside it. Nothing is written until
the archive passes three checks. SHA256SUMS must carry the project's ed25519
signature, checked with ssh-keygen -Y verify (OpenSSH 8.1 or later, which
macOS, current Linux distributions and Windows 10 and later have). The
archive's SHA-256 must match it. When gh is on your PATH, its GitHub
artifact attestation, signed by this repository's release workflow, is checked
too. A missing or wrong signature stops the install, and semlith upgrade holds
a release to the same key, built into the binary, which semlith --version
names. curl is held to https and TLS 1.2 or later, and the script runs only
once it has fully downloaded.
Releases before v0.40.0 are not signed, so these installers do not install
them.
To read the installer before running it:
curl --proto '=https' --tlsv1.2 -fsSLO https://github.com/semlith/semlith/releases/latest/download/install.sh
less install.sh
gh attestation verify install.sh --repo semlith/semlith
sh install.shOn Windows, run irm https://github.com/semlith/semlith/releases/latest/download/install.ps1 -OutFile install.ps1,
read it, then run powershell -ExecutionPolicy Bypass -File install.ps1.
To verify an archive by hand:
echo 'semlith-release ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICMTHiHMzeSHzzc+8UDZHUzJcvb6XGUBb+TJdFes91xm' > allowed_signers
ssh-keygen -Y verify -f allowed_signers -I semlith-release -n file -s SHA256SUMS.sig < SHA256SUMS
grep " semlith-<tag>-<target>.tar.gz$" SHA256SUMS | shasum -a 256 -c
gh attestation verify semlith-<tag>-<target>.tar.gz --repo semlith/semlithQuick start
cd ~/src/ripgrep
semlith index . # one store, and the model (~52 MB) downloads once
semlith search "where is a file detected as binary"
semlith neighbors search_parallel # callers and callees
semlith start # the daemon and the portal$ semlith search "where is a file detected as binary" -k 1
1. 0.083 vf GUIDE.md:735-746
binary files:
1. The default mode is to attempt to remove binary files from a search
completely. ...
$ semlith neighbors search_parallel
callers (2)
main via defines (extracted) crates/core/main.rs:44 · call at line 166
run via calls (resolved) crates/core/main.rs:78 · call at line 90
callees (25)
matcher via calls (resolved) crates/core/flags/hiargs.rs:379 · called at crates/core/main.rs:177
search_worker via calls (resolved) crates/core/flags/hiargs.rs:705 · called at crates/core/main.rs:176
...Every hit is a path:start-end locator you can hand to an editor or an agent,
and vf says the vector and keyword lists both found it. Documents answer the
same way.
Use it from an agent
semlith setup # registers each agent client that has a registration CLI
semlith doctor # checks each client can actually reach it, and says how to fix it if notsemlith setup registers the MCP server at user scope in each installed client
that has a registration CLI, which is six of the 12 documented clients. The
other six are a config file, which semlith setup --register-all writes for
you, and semlith doctor reports on every one. Each stanza is run against the
binary in tests/clients.rs, and docs/clients.md has them
all with the HTTP transport. Setup also installs the Semlith Agent Skill, a
steering hook that suggests a Semlith call when an agent is about to grep, and
a read-only research agent.
semlith mcp serves MCP over stdio, and the daemon serves the same tools over
HTTP at /mcp, behind an agent key (semlith key show). Eight tools are listed
by default, because every definition costs tokens on every request, and
SEMLITH_MCP_TOOLS=all lists all sixteen.
Tool | Answers |
| Where is the answer? Path, span, enclosing symbol and how it was found. |
| Everything one question needs in one call, with spans, text and one-hop callers and callees, under a token budget. |
| One span or one symbol, nothing around it. |
| Where is this defined? |
| What calls it, and what it calls. |
| Everything that reaches it, and the files involved. |
| Which files are indexed. |
| What each store holds. |
| How two symbols connect, as a chain or as evidence. |
| A tree-sitter structural pattern over one language. |
| Change a store from inside a conversation. |
| One of five reports from the ledger, index and graph. |
| The language names |
A bare semlith mcp opens every registered store, so one question can span
repositories. It speaks MCP 2026-07-28, 2025-11-25, 2025-06-18 and
2024-11-05, each proven by a session in tests/mcp.rs. In the official
MCP registry it is listed as
mcp-name: io.github.semlith/semlith
The portal
semlith start runs one daemon that owns every store. It holds each store's
write lock, watches its folders, re-embeds what you save, and serves a portal at
http://127.0.0.1:7365. The URL with its session token is printed once, on
stdout. docs/portal.md documents every page.
It is not on the network. The daemon binds 127.0.0.1 only, with no flag
to change it. Every page and /api/ route needs the per-run session token,
sent in a Semlith-Token request header, and without it the answer is 401. The
agent key opens /mcp and nothing else. Responses carry a
Content-Security-Policy of 'self', no CORS header is sent, and every byte
the portal loads is compiled into the binary. --airgap refuses every outbound
request before a connection opens, which covers model and pack downloads that
are not cached, semlith upgrade, the price table, Semlith Cloud and
semlith add. The Privacy page checks these claims against the running daemon,
and docs/security.md is the full account.
How it works
files ──chunk──> text ──embed──> vectors ──quantize──> index/*.tvim (turbovec)
│
└──parse──> symbols, edges ─────────> store.db (SQLite)
query ──embed──> vector ──search shards──┐
──keywords──> FTS5 ────────────────┴──fuse──> ranked spans ──> enclosing symbolReading. Files are read as the text a person would see. Thirteen formats have their own reader (PDF, Word, PowerPoint, Excel, notebooks and more). Everything else is read as UTF-8 and cut into line-aligned chunks of up to 800 characters. A Markdown chunk is embedded with its heading path, a code chunk with its enclosing definition.
Embedding.
granite-embedding-small-english-r2(int8, 384 dimensions, ~52 MB) through ONNX Runtime, fixed per store. The daemon also embeds on the Neural Engine on Apple silicon and on the GPU (Core ML, WebGPU or CUDA), each lane checked against known answers before its first batch. Images go through CLIP ViT-B/32 into a second vector space and match by what an image shows, not by OCR.Storing.
index/holds turbovec shards of 65 536 quantized vectors, andstore.dbholds chunk text, spans, symbols, edges, the ledger and the content hashes that make re-indexing incremental. A save rewrites one shard.Graph. All 46 languages carry edges. Each edge is
extracted(settled by the file itself),resolved(one candidate),ambiguous(several, and it says how many) orinferred(a bare name match).semlith pathrefuses to cross a name it cannot pin down unless you pass--all-edges.
docs/architecture.md is the full account.
Commands
Command | Does |
| Index paths into a store; re-running embeds only what changed. |
| Re-embed files as they are saved. |
| The daemon: every store kept current, the portal and |
| Hybrid search. |
| Spans, their text and one-hop callers and callees, under |
| One span ( |
| The graph: definitions, callers and callees, blast radius, chains, evidence. |
| A tree-sitter structural pattern. |
| What a store holds; |
| Files not indexed and why; files held that today's rules would refuse. |
| Fetch one https URL into a store. No crawling, no credentials, no private addresses. |
| Remove a file, reclaim dead bytes without re-embedding, delete a store. |
| Move a store into the store home; allow one outside it. |
| Retrieval history, five reports in five formats, scheduled reports, the price table. |
| MCP over stdio, the steering hook, the HTTP agent key. |
| Install and repair, diagnose each client, replace the binary. |
| Which devices embed: CPU (always on, |
| Connects to Semlith Cloud, a separate hosted service. Nothing contacts the network until |
semlith <command> --help documents every flag.
Where stores live. Each store lives in ~/.semlith/stores/<name> and is
listed in ~/.semlith/registry.json, so semlith mcp and semlith start find
every store with no flags. A command picks its store from --store or
SEMLITH_STORE, then a .semlith beside the corpus, then the registered store covering the current
directory, and otherwise creates one.
What gets indexed. Everything under the given paths except .gitignored
and hidden files, binaries, files over 8 MiB, archives that expand past 32 MiB,
and generated or vendored directories. node_modules is always skipped, and
target, build, dist and vendor are skipped beside their manifest unless
SEMLITH_DEFAULT_IGNORES=0 is set. Credentials are never indexed, and the grey
zone is yours to decide.
The retrieval ledger
Every retrieval is recorded in its store, whether it came from an agent over
MCP, the CLI or the portal. A row holds the query, the client, the session, the
hits and what was read.
Each row carries the hash of the row before it, so semlith ledger --verify
finds an edited or deleted row. It is local, on by default, and off with
--no-ledger or SEMLITH_LEDGER=0.
What it saved on this repository. Ask semlith brief all 107 questions of
this repository's retrieval harness against a store of src/, then run
semlith ledger --verify. An answered question sent 3 982 tokens to the
agent on average, against 105 132 for reading the 4.1 files those answers
named, whole. That is 26× less, at 100 % coverage, counted with the store's own
tokenizer. It is an upper bound, because a file named by two answers is counted
twice.
Benchmarks
Asked 127 questions about 53 public documents, an Opus agent with Grep, Glob and Read found the passage 63 % of the time, and 41 of its 127 top quotes appear nowhere in the document it named. It found the file by name, could not read it, and wrote something plausible. With Semlith installed it found 98 %, and 0 of 126 quotes were made up. On single-repo code with a strong model it does not help, and Opus scores 92 % with or without it.
Public code-retrieval benchmarks, run by bench/scorecard/
on one M1 MacBook Air in October 2026 and published as measured, including where
another tool wins. Every table, every chart and the command that reproduces each
number are on the scorecard page.
Benchmark | semlith | baseline | instances |
SWE-bench Verified, right file in the top 10 | 76.8 % | 47.0 % grep then read | 500 |
SWE-bench Lite, right file in the top 10 | 72.3 % | 47.3 % grep then read | 300 |
SWE-bench Verified, median tokens to the first right file | 224 | 15 491 grep then read | 500 |
CodeRAG-Bench DS-1000, NDCG@10 | 0.260 | 0.158 BM25 | 513 |
Agents (Opus), cost per correct answer | $0.065 | $0.072 grep only | 50 × 3 runs |
Documents (PDF, Office, EPUB, …), passage found within 4k tokens | 116/127 | 34/127 rga | 127 |
Agents (Opus) on those documents, passage found | 124/127 | 80/127 grep only | 127 |
Real sessions, tokens sent against whole files read | 75.2× fewer | 1,575 retrievals |
Semlith does not win everywhere. Semble edges it at hit@1 and hit@10 on the 20-instance competitor sample, BM25 beats it on CodeRAG-Bench's RepoEval, and with Grep built in, Opus rarely reaches for Semlith on its own. The scorecard page has these rows and RepoBench-R.
The numbers
Coverage, read out of the code. tests/readme.rs fails the build if any of
these drifts from its source.
languages searchable and graphed | 46 |
edge kinds | 6 |
document formats with a reader | 13 |
image types | 5 |
MCP tools | 16 |
CLI commands | 36 |
agent clients, each setup stanza run against the binary in | 12 |
prebuilt targets | 4 |
Measured on a 4P+4E Apple silicon laptop, with the command that reproduces each. docs/performance.md has the full tables.
warm search at 700 / 7 000 / 70 000 chunks, peak RSS under 240 MB | 16.2 / 37.9 / 159.1 ms |
|
edit on disk to searchable, watcher running | 1.24 s | |
indexing on an M1 Air: Neural Engine / CPU and GPU through Core ML | 229.8 / 62.7 chunks/s p10 | |
daemon memory 60 s after a run, seven stores open | 465 MB |
|
idle watcher CPU over 60 s | under 1.0 s | the same |
one changed file | 1 shard rewritten |
|
| 2 633 bytes, eight tools; 1 323 tokens added to an Opus request |
|
retrieval, 30 sealed questions of 107 | hit@1 24/30, hit@3 27/30, hit@8 29/30, zero spread; identifiers 11 of 11 in the top three | the same |
one search, rescoring off / on | 8.2 ms / 132.2 ms on a 300-file store | the same |
one answered question, | 1.00 calls vs 2.54, 1 112 tokens vs 725 | the same |
call-edge resolution | 62 % settled | the same |
the macOS arm64 binary | 125 435 312 bytes (119.6 MiB) |
|
the Linux glibc floor | GLIBC_2.34 |
|
Peak memory does not grow with the corpus, so plan for about 600 MB whatever you index. Query latency does, because the vector scan is linear.
Known limits
One writer per store. While the daemon or
semlith watchholds a store, a secondsemlith indexagainst it exits and names the holder.First indexing is bound by the model, at about 230 chunks/s on an M1's Neural Engine, 63 on its CPU and GPU, 30 on the CPU alone. Later runs embed only what changed. On macOS the CPU lane alone runs at about 60 % of a terminal's speed inside the login service, because of how launchd schedules agent threads.
On a Mac the GPU does not embed beside the Neural Engine unless you run
semlith accel on gpu-beside-ane. CUDA runs on Linux x86_64 only, and an NVIDIA card on Windows embeds through WebGPU.A new binary on macOS asks again for access to folders such as Documents, and a store there waits, showing
running, until the prompt is answered.A store larger than
SEMLITH_INDEX_MEMORYreads shards from disk on every query, so raise the budget if the store fits in memory.The default model is English-only and fixed when a store is created. Image search is not OCR.
Search filters are SQLite
GLOB(no regex), and--langmaps extensions and never reads contents. Multi-store search merges rankings rather than ranking jointly. Cross-encoder rescoring (SEMLITH_RERANK=on) is off by default, because it costs about 16× the search time for two more answers in 77 at k=1.Binaries are not notarised by Apple or Authenticode-signed for Windows. Each release carries an ed25519-signed
SHA256SUMSand a GitHub artifact attestation instead. There is no ARM64 Windows or Intel macOS build, andsemlith upgradeonly replaces a binary in~/.semlith/bin.
Documentation
Document | Covers |
Every page and control of the portal. | |
Setup stanzas for 12 agent clients and the HTTP transport. | |
How the pieces fit together, and why. | |
Every measured number, with its command and date. | |
The embedding models, and how to choose one. | |
The threat model and what is on the wire. | |
What is a contract, and what a 0.x version promises. | |
What changed in each release. |
Contributing
Contributions are welcome. CONTRIBUTING.md covers building, the checks CI runs and what is out of scope, and AGENTS.md is the same for a coding agent. Everyone follows the Code of Conduct. To report a security problem, see SECURITY.md rather than opening an issue.
Prior art
Semlith's vector index is turbovec by Ryan Codrai (MIT), an implementation of TurboQuant from "TurboQuant: Online Vector Quantization with Near-optimal Distortion Rate" by Amir Zandieh, Majid Daliri, Majid Hadian and Vahab Mirrokni. TurboQuant is data-oblivious. It rotates each vector randomly and quantizes each coordinate on its own, with no training pass. That is what an index refreshed on every file save needs. There is nothing to gather before the first file and nothing to rebuild as the corpus grows, and a vector added is a vector searchable.
License
Apache-2.0. See LICENSE and NOTICE.
Model weights are downloaded at runtime under their own licenses. The default, ibm-granite/granite-embedding-small-english-r2, is Apache-2.0, and the CLIP ViT-B/32 pair, the WebGPU plugin (MIT) and the NVIDIA libraries the CUDA pack fetches carry theirs. See docs/models.md.
Available Tools
8 toolssemlith_briefDRead-only
Spans, the top span's text and its callers/callees, in one call.
| Name | Required | Description | Default |
|---|---|---|---|
| path | No | ||
| store | No | ||
| question | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, so the safety profile is covered. The description adds only that results include spans, top span text, and callers/callees, but it does not explain the tool's behavior, aggregation logic, or response shape beyond that fragment.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is very short but under-specified rather than concise. It is not front-loaded with a clear action, and its noun-phrase fragment leaves essential information unstated.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, 0% parameter coverage, and only a readOnly annotation, the description is far too thin to support correct invocation. It does not explain what a "brief" contains, how question/path/store affect results, or when this tool should be chosen.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, and the description does not mention any of the three parameters (question, path, store). It therefore provides no meaning for the required question parameter or the optional path/store filters.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names some returned content (spans, top span text, callers/callees) but uses a fragment rather than a clear verb and does not explain what the tool actually does. It does not distinguish its purpose from siblings like semlith_symbol or semlith_neighbors.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no guidance on when to use semlith_brief versus alternatives such as semlith_symbol, semlith_neighbors, or semlith_impact. The phrase "in one call" hints at aggregation but does not state when that is preferable.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
semlith_filesBRead-only
What is in this folder; tree: true for a directory view with symbols.
| Name | Required | Description | Default |
|---|---|---|---|
| path | No | ||
| tree | No | ||
| store | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The readOnlyHint annotation already establishes that this is a safe read operation. The description adds that `tree: true` produces a directory view with symbols, but it does not disclose return shape, recursion behavior, or how `path` and `store` affect results.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single compact sentence with no filler. It front-loads the core resource question and appends the `tree` behavior efficiently.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The tool has three parameters with no schema descriptions and no output schema. The description covers only the `tree` flag, leaving the meaning of `path`, `store`, and the expected return values unclear.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, and the description only explains the `tree` parameter. The `path` and `store` parameters are undocumented in both the schema and the description, leaving significant ambiguity for a 3-parameter tool.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly identifies the resource as folder contents and hints at a directory-tree view. It does not explicitly distinguish the tool from siblings like semlith_search or semlith_read, so it falls short of a 5.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The only guidance is the note that `tree: true` gives a directory view with symbols. There is no explanation of when to use this tool versus semlith_search, semlith_read, or other siblings, and no exclusions or prerequisites.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
semlith_impactBRead-only
What breaks if X changes: every call site, transitively. Type::method narrows.
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | ||
| path | No | ||
| store | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
readOnlyHint=true is consistent with a pure analysis tool, and the description adds useful scope information (transitive traversal, Type::method narrowing syntax) beyond the annotation. It says nothing about which store/index is consulted, whether results include indirect dynamic dispatch, or how missing symbols behave.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two short clauses, front-loaded with the core question the tool answers, with no filler. It is arguably over-compressed to the point of being telegraphic, but nothing is wasted.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 3-parameter tool with no output schema, the description does not say what the result looks like (list of call sites? edges? grouped by file?), how store/path change the search space, or what happens when the symbol is ambiguous. An agent would have to guess on several call-shaping details.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0% for 3 parameters. The description only implicitly maps "X" to the required name argument and hints at the "Type::method" narrowing format; path and store are completely unexplained in both the schema and the description.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The phrase "What breaks if X changes: every call site, transitively" names a concrete analysis (reverse-dependency / impact) and its scope (transitive call sites), which separates it from semlith_read and semlith_symbol. It never names a sibling explicitly, so differentiation is inferred from the word "breaks" rather than stated.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The framing "What breaks if X changes" implies a pre-refactor / before-you-change-something usage context, which is real guidance. There is no explicit statement of when to use this over semlith_neighbors or semlith_symbol, and no exclusions or prerequisites.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
semlith_neighborsCRead-only
Who calls X, and what X calls. Use for caller questions.
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | ||
| path | No | ||
| store | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The readOnlyHint annotation already declares this is a safe read, so the description carries no safety burden. Beyond that the description discloses nothing useful: no traversal depth, no directionality handling, no behavior when the symbol is ambiguous or missing, and no indication of result size.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two short sentences, front-loaded with the core semantic relation and followed by the usage cue. No filler and nothing buried; brevity is appropriate here, though it tips into under-specification.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, no annotation detail beyond readOnly, zero parameter documentation, and several graph-traversal siblings, this description does not supply enough for an agent to call correctly. The critical unknowns — what `path` and `store` mean and what the response contains — are unaddressed.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0% across three parameters, so the description must compensate and does not. 'X' loosely implies the required `name`, but `path` and `store` are entirely unexplained in both schema and description, leaving two-thirds of the input surface opaque.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a recognizable resource relation — 'Who calls X, and what X calls' — which conveys a bidirectional call-graph neighborhood lookup. However, it never differentiates from the closely related sibling semlith_impact (also a graph traversal) or semlith_symbol, so an agent must guess which one to reach for.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
'Use for caller questions' gives one implied trigger, which is more than nothing. But there is no exclusions guidance, no mention of when to prefer semlith_impact (transitive impact) or semlith_symbol, and no prerequisites or scope limits.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
semlith_readCRead-only
Read path:start-end, path:line, or a symbol (Type::method) whole.
| Name | Required | Description | Default |
|---|---|---|---|
| store | No | ||
| target | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The readOnlyHint annotation already establishes that this is a safe read operation, and the description is consistent with that. However, it adds no behavioral context about return format, error handling, truncation, or what 'whole' means for a symbol, leaving gaps beyond the annotation.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single front-loaded sentence with no wasted words. The compressed syntax is efficient, though the phrase 'or a symbol (Type::method) whole' is slightly awkward to parse.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema and no explanation of what is returned, such as source text, numbered lines, or symbol bodies. For a tool with two parameters and zero schema coverage, the description should also clarify the store parameter and distinguish this read from semlith_symbol.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description carries the full burden, and it does explain the accepted syntax for the required target parameter. It never mentions the optional store parameter or its meaning, leaving a significant undocumented input.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a clear read operation with the specific target forms it accepts: path:start-end, path:line, and Type::method. It does not differentiate itself from sibling tools such as semlith_symbol, leaving ambiguity about when a symbol should be read here versus via the symbol-specific tool.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It lists accepted target formats but gives no explicit guidance on when to use this tool instead of siblings like semlith_search, semlith_symbol, or semlith_files. The agent must infer usage from the input syntax alone.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
semlith_searchBRead-only
Where is X / how does X work: ranked file:line spans. exact: true = every matching line (grep -E). Also ext, lang, prefer (code|docs), format: excerpt.
| Name | Required | Description | Default |
|---|---|---|---|
| k | No | Default 8. | |
| path | No | Globs; ! excludes. | |
| exact | No | ||
| local | No | ||
| query | Yes | ||
| store | No | Open: . |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
readOnlyHint=true already covers the safety profile, and the description adds genuine behavioral context: results are rank-ordered spans, and exact mode escalates from ranking to a full grep -E line match. It says nothing about result count behavior, latency, or what the excerpt/span actually contains, so the added context is real but thin.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
It is small and front-loads the purpose, which is good. The trailing clause is a dense fragment ('Also ext, lang, prefer (code|docs), format: excerpt') that is hard to parse and, worse, spends its budget on parameters the schema does not have.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With six parameters, 50% schema coverage, and no output schema, the description should at least fix the result shape and clarify path/store/local. It gives the return shape (ranked file:line spans) but leaves path globs, store scoping, and local semantics entirely to the schema, and its phantom parameter list undermines completeness.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
exact is well explained (grep -E, every matching line), which is real meaning beyond the bare boolean in the schema. But the tail 'Also ext, lang, prefer (code|docs), format: excerpt' names four parameters that do not exist in the input schema, actively inviting invalid arguments, while the actual params path, store, local, and k get no attention at 50% coverage.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description gives a recognizable job: answering 'where is X / how does X work' queries with ranked file:line spans, which separates it from structural siblings like semlith_symbol or semlith_neighbors. The verb is implied rather than stated ('search' never appears), and no sibling is named explicitly, so differentiation relies on the agent inferring from 'ranked spans' vs. symbol/impact tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It implies the usage context ('Where is X / how does X work') and gives one real routing rule: exact: true when you want every matching line, grep -E style. There is no when-not guidance and no explicit alternative named among the seven siblings, so an agent must guess whether to reach for this or semlith_symbol.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
semlith_statsCRead-only
What is indexed, per store.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
readOnlyHint=true covers the safety profile, so the bar is lower, but the description adds essentially nothing beyond it: no output shape, no granularity, no note on cost or freshness of the index data. The one clause provided is a vague pointer at the return value rather than context an agent couldn't guess.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single short fragment with no padding, so nothing is wasted, but the brevity comes from under-specification rather than economy. It is front-loaded only in the sense that it's a sentence fragment.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description carries the full burden of explaining what comes back, and "per store" plus "what is indexed" is far too thin to tell an agent the shape, units, or meaning of 'store'. For an introspection tool whose whole value is its return value, this is a real gap.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so the baseline is 4; the description has no parameters to explain and correctly doesn't invent any. Nothing here misleads about inputs.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The fragment "What is indexed, per store" hints at a stats/coverage report grouped by store, but there's no verb and no named resource, so it reads close to a restatement of the tool name 'stats'. It does not distinguish this from siblings like semlith_files or semlith_symbol, which also concern indexed content.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no when-to-use guidance, no statement of alternatives, and no indication of what question this tool answers that the other semlith_* tools don't. The agent must infer everything from the name.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
semlith_symbolCRead-only
Where X is defined, with callers and callees. names: up to 20, a row each.
| Name | Required | Description | Default |
|---|---|---|---|
| name | No | ||
| path | No | ||
| names | No | ||
| store | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare readOnlyHint=true, so safety is covered, but the description adds little beyond it: no statement of what is returned when a symbol is missing, whether callers/callees are scoped to a workspace or store, or how the store/path interplay affects lookup. "A row each" hints at output shape but is unexplained.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
It is two short sentences with no padding, so length is fine, but the structure is telegraphic and the most important element (what is being located) is abbreviated to "X," which front-loads ambiguity rather than clarity.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 4-parameter tool with zero schema coverage and no output schema, the description should carry nearly everything, yet it omits parameter meaning, return shape (beyond "a row each"), and behavior when a symbol is absent or ambiguous. An agent could not reliably call this without guessing at path/store semantics.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0% for all four parameters (name, path, names, store), so the description must compensate and largely fails. It clarifies that names takes up to 20 entries and that the pair is singular/plural, but says nothing about path vs store or how they scope the lookup, leaving the two most ambiguous parameters undocumented.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The phrase "Where X is defined, with callers and callees" does convey a specific capability (definition lookup plus call graph), but the tool name is opaque (semlith_symbol) and the description never states the resource plainly (symbol/function definition). It is distinguishable from semlith_search or semlith_read only by inference, so it sits at vague-but-inferable rather than clear.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no explicit when-to-use guidance and no mention of alternatives, despite siblings like semlith_neighbors and semlith_impact that plausibly overlap with caller/callee information. The only usage hint is buried in the fragment "names: up to 20, a row each," which tells batch size, not selection criteria.
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.
8 tool updates
- First observed
semlith_brief - First observed
semlith_files - First observed
semlith_impact - First observed
semlith_neighbors - First observed
semlith_read - First observed
semlith_search - First observed
semlith_stats - First observed
semlith_symbol
TDQS
Scored across 8 tools
Three tools — semlith_brief, semlith_neighbors, and semlith_symbol — all return callers/callees, and an agent could easily misselect among them; the descriptions distinguish them only subtly (summary vs. caller questions vs. batch definitions). semlith_search and semlith_read also overlap on 'where is X / show me X', and semlith_impact is close to a transitive version of neighbors. The boundaries are inferable but require careful reading of every description.
All eight tools share a uniform semlith_ prefix with lowercase single-word snake_case names, so there is no stylistic mixing. The pattern is noun/topic-based (brief, impact, neighbors, symbol) rather than a strict verb_noun scheme, and a couple of names (brief, impact) are abstract enough that the name alone doesn't convey the operation.
Eight tools is well within the ideal range for a code-intelligence server, and each maps to a recognizable query type: search, read, structure, definitions, callers, impact, and index metadata. Nothing appears redundant enough to cut, and no obvious capability is missing a slot.
The surface covers the read-side lifecycle well — discovery (search), structure (files), navigation (read, symbol), analysis (neighbors, impact), and introspection (stats). Minor gaps exist: no explicit list of all symbols/top-level entities in a store, no cross-store or repo-level overview, and no history/diff-style view, though an agent can work around these via search and files.
Maintenance
Related MCP Connectors
Code intelligence for coding agents: semantic, AST, graph, and full-text search. 279+ languages.
Self-hosted AI workspace: semantic RAG document search, long-term memory, and AI chat via MCP.
Ask a codebase what calls what: search, blast radius, paths between symbols, and diffs.
Cloud or self-hosted knowledge for AI agents: hybrid search, reranking, GraphRAG, scoped MCP tools.
Related MCP Servers
- FlicenseAqualityDmaintenanceSelf-hosted hybrid code search MCP server with text, symbol, and semantic search layers. Runs locally, no third-party MCP servers, LSP, or SaaS.8-
- AlicenseAqualityCmaintenanceA fully local RAG MCP server for semantic code search and code intelligence, using AST-level chunking and hybrid search to pinpoint functions, classes, and APIs. No cloud, no API keys, zero setup.1647 npm13MIT
- AlicenseNot gradedqualityAmaintenanceLocal-first code intelligence MCP server that enables coding agents to search code, inspect structure, read exact ranges, and explore Git history with explicit token budgets.121Apache 2.0
- AlicenseAqualityAmaintenanceLocal code search for AI agents over a tree-sitter index of your repo: exact symbol lookup, scope-aware usages, call graph and impact analysis, AST pattern search, indexed grep and semantic search. 28 MCP tools, 19 languages, runs fully offline.32812MIT