Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

MCP server and tools

semanticastindexer mcp runs an MCP server over stdio, exposing SAI’s semantic code search to agentic coding tools (Claude Code, Cursor, Windsurf, Codex, and others). It is built on the official Rust MCP SDK (rmcp, Cargo feature mcp).

This page is the authoritative reference for the server and every tool it exposes. The .agents/skills/sai/SKILL.md agent skill defers to this page for the canonical tool contracts.

For per-client wiring (Claude Code, Cursor, Windsurf, Codex, …) see MCP clients. For the exact JSON shapes each tool returns, see Output schemas.

Server behavior

  • Read-only by default. Search exposes no index/upsert/flush tools. Only sai_refresh and sai_sync write, and only when explicitly enabled (see below).
  • Backend, embedder, and collection come from sai-cfg.yml. See Backends and embedders.
  • Backend built once at startup. The backend and embedder are constructed a single time and reused across every tool call.
  • Tools are sai_-prefixed so they stand apart from other MCP servers’ tools in the agent’s tool list.

Worker-thread model

The DuckDB backend is !Send/!Sync, but rmcp’s tool-handler futures must be Send. So the backend lives on a dedicated worker thread, and the server holds only a Send + Sync channel handle (BackendHandle). Each tool handler builds a request, sends it to the worker, and awaits a oneshot reply — keeping handler futures Send while serializing all backend access through the single-threaded connection. For the full rationale see how it works.

Concurrency and the index lock

The server opens the DuckDB index once and holds it for the life of the connection — read-only by default, or read-write under --allow-write. DuckDB enforces single-writer / multi-reader access at the file level across processes, so while an MCP server is connected:

  • A separate CLI sync, full re-index, or flush (all open the database read-write) against the same .index will fail or block on the lock — even a read-only MCP connection blocks a CLI writer.
  • Two read-only consumers (e.g. an MCP server plus a CLI duplicates/similar) coexist fine.

So the in-process write tools are the preferred way to keep the index fresh without shelling out: call sai_sync (reconcile the git-changed set) or sai_refresh (specific paths) on the running --allow-write server — they mutate through the same connection, so there is no second handle and no lock contention. If you do need heavy CLI maintenance, disable/disconnect the MCP server first (via your client’s MCP toggle), run the CLI command, then reconnect.

Threshold and limit resolution

Two resolution rules apply throughout the tools:

  • Threshold resolution (per knob): MCP tool arg > config value > built-in default. The config values come from the similarity: block of sai-cfg.yml; the built-in defaults are find_similar_min_score = 0.85, duplicate_min_score = 0.93, duplicate_min_cluster_size = 2, and top_k = 10. These cutoffs are model-specific — tune them per embedding model. See Tuning similarity.
  • Limit clamping: any caller-supplied limit / top_k is clamped to [1, 50] so a single call can’t request the world.

Tools

The server exposes seven tools. Four are read-only; sai_refresh and sai_sync write and sai_prepare_mcp_setup can execute a setup script — all gated behind explicit flags.

ToolPurposeGating
sai_search_codeGeneral semantic search (query embedded as a query)none (read-only)
sai_find_similarNeighbours of one snippet or one stored chunknone (read-only)
sai_find_duplicatesCodebase-wide near-duplicate clustersnone (read-only)
sai_index_statusIndex metadata (backend, model, dim, count, …)none (read-only)
sai_prepare_mcp_setupReturn setup commands; optionally run the setup scriptexecution requires --allow-setup
sai_refreshRe-index specific files in place (delete + re-embed)requires --allow-write
sai_syncReconcile the index with the working tree (git-changed set), like CLI syncrequires --allow-write

Skills & subagents that drive these tools

The MCP server gives an agent the capability; two companion artifacts (both installed by mcp-setup/setup.sh) give it the judgment to use the tools well:

  • sai-deslop (.agents/skills/sai-deslop/SKILL.md) — a portable Agent Skill (Claude Code, Cursor, Windsurf, …) covering when to call each tool while coding and a triage protocol that forces the agent to read each finding’s real source and classify it (real / boilerplate / intentional / fragment) before proposing a verified consolidation. It treats a cluster as a hypothesis, not a conclusion.
  • dedup-auditor (.claude/agents/dedup-auditor.md) — a Claude Code subagent that runs the heavyweight repo-wide sai_find_duplicates sweep and per-member reads in an isolated context, applies the same triage protocol, and returns a classified digest instead of a raw cluster dump.

Skills are the portable, behavior-shaping layer; the subagent is the context-isolating worker for repo-wide audits. Both ultimately call the sai_ tools below.

sai_search_code

General semantic search over the indexed code. The query is embedded as a query and the nearest indexed chunks are returned. The server over-fetches (about limit × 4, still clamped) so the language / path_glob post-filters can still return up to limit rows. When include_text is false, each snippet is capped to the first ~8 lines and ~800 chars.

ArgTypeRequiredDefault
querystringrequired
limitintegeroptional8 (clamped to [1, 50])
languagestringoptionalunset (no language filter; e.g. "ts")
path_globstringoptionalunset (e.g. "src/**")
include_textbooleanoptionalfalse (return capped snippet)

sai_find_similar

Find code similar to either an inline code snippet or an existing indexed chunk addressed by path + line. Provide either code OR both path and line — not a mix:

  • code is embedded as a passage (code-vs-code space) and requires a local embedder (the duckdb backend); calling it against a non-local-embedding backend returns an invalid_params error.
  • path + line looks up the exact stored vector for that chunk (no re-embed) and excludes the chunk itself from its own results. If no indexed chunk exists at that location the call returns no indexed chunk at <path>:<line>.

min_score resolves as arg > config > built-in default 0.85. Omitting it still applies the configured (model-tuned) cut; pass an explicit 0.0 to see the raw score distribution.

ArgTypeRequiredDefault
codestringone of code or path+lineunset
pathstringuse together with lineunset
lineinteger1-based start line; use with pathunset
limitintegeroptional8 (clamped to [1, 50])
min_scorenumberoptionalconfig find_similar_min_score, else 0.85

sai_find_duplicates

Find near-duplicate clusters across the index. For each chunk it takes the chunk’s top_k nearest neighbours, keeps the edges whose similarity is >= min_score, and unions them into clusters via union-find. Clusters with size >= min_cluster_size are returned, largest first.

ArgTypeRequiredDefault
min_scorenumberoptionalconfig duplicate_min_score, else 0.93
min_cluster_sizeintegeroptionalconfig duplicate_min_cluster_size (else 2), then floored at max(…, 1)
path_globstringoptionalunset (restrict the scan to matching paths)
max_clustersintegeroptional50 (local constant; not configurable via similarity:)
top_kintegeroptionalconfig top_k (else 10), clamped to [1, 50]

Note: min_score, min_cluster_size, and top_k follow arg > config > built-in default, while max_clusters is a fixed local default of 50 and has no config knob.

sai_index_status

Report index metadata for freshness and sanity checks. Takes no arguments. Returns the backend, collection, embedding model, vector dimension, total chunk count, and chunker.

ArgTypeRequiredDefault
(none)

sai_prepare_mcp_setup

Help an agent set up SAI as an MCP server for a project. By default it only returns the exact commands and an MCP config snippet to run; it executes the setup script only when execute: true and the server was started with --allow-setup. Without --allow-setup, an execute: true call is reported as blocked (Server not started with --allow-setup) and no script runs.

The recommended_command includes --target-dir and an explicitly-derived --features list. For a prebuilt/release binary — where mcp-setup/setup.sh is not on disk beside it — the command falls back to the install.sh one-liner instead, and an execute: true call is reported as blocked (building from source needs a source checkout).

ArgTypeRequiredDefault
target_directorystringoptionalcurrent working directory
backendstringoptional"duckdb" (or "qdrant")
embedderstringoptional"ollama" (or "ort" for fully offline)
use_ast_chunkerbooleanoptionalfalse (requires a binary built with --features ast)
install_globallybooleanoptionalfalse (installs into ~/.local/bin as a sai wrapper)
executebooleanoptionalfalse (only runs the script when also started with --allow-setup)

The response includes the recommended setup command, an mcp_server_config_example, and a numbered next_steps list. The first build can take several minutes (much longer for ort).

sai_refresh

Write tool. Re-index specific files in place: for each path it deletes that path’s existing points, then re-chunks, re-embeds, and re-upserts the files that still exist and pass the index filters (extension, globs, not generated). Paths that are gone or excluded are removed. The whole batch runs in one bulk window (HNSW drop → per-path delete + re-embed + upsert → rebuild), reusing the same per-file logic as the sync command.

ArgTypeRequiredDefault
pathsarray of stringrequired— (non-empty; max 200 per call)

Gating: the server must be started with --allow-write. Without it the backend is opened read-only and any call returns:

server is read-only; restart with --allow-write to enable refresh

An empty paths array returns refresh requires at least one path; more than 200 paths returns too many paths (max 200). On success the tool returns the refreshed paths (with chunk counts) and the removed paths.

sai_sync

Write tool. Reconcile the index with the working tree, the MCP analog of the CLI sync command: it resolves the git-changed file set and runs them through the same per-file reconcile as sai_refresh (survivors re-chunked/re-embedded, deleted or now-excluded paths removed).

ArgTypeRequiredDefault
sincestringoptionalHEAD~1 (changed set = working tree vs <since>)
stagedbooleanoptionalfalse (use git diff --cached instead of --since)
pathsarray of stringoptional— (explicit set; overrides git detection)

Gating: like sai_refresh, requires --allow-write (else server is read-only; restart with --allow-write to enable sync). When git finds nothing the tool returns an empty result with a note; more than 200 changed files returns an error suggesting a narrower since or explicit paths. On success it returns the same {refreshed, removed} shape as sai_refresh.

Wiring (.mcp.json)

Point command at the built binary (an absolute path is safest) and set cwd to the indexed project root so the server finds that project’s index and sai-cfg.yml:

{
  "mcpServers": {
    "sai": {
      "command": "/path/to/semanticastindexer/target/release/semanticastindexer",
      "args": ["mcp", "--config", "sai-cfg.yml"],
      "cwd": "/path/to/your/project"
    }
  }
}

Build with the needed features and index the project once before starting the server. To enable the write tool, add --allow-write to args; to allow sai_prepare_mcp_setup to execute its script, add --allow-setup:

{
  "mcpServers": {
    "sai": {
      "command": "/path/to/semanticastindexer/target/release/semanticastindexer",
      "args": ["mcp", "--config", "sai-cfg.yml", "--allow-write"],
      "cwd": "/path/to/your/project"
    }
  }
}

See MCP clients for per-client config locations and the CLI reference for the full mcp flag list. For the response shape of each tool, see Output schemas.