Tool reference¶
Every tool Sankshep exposes over MCP, with its arguments and a real request/response captured from a
running v2.0.0 server. Long absolute paths in the captures are shortened to C:\...\sample-repo; nothing
else is edited. One contract changed in 3.0.0 and the capture below predates it: compose_task_prompt's
tokenBudget now bounds the code rather than being split with the conventions section, so the same
request returns more code than the capture shows.
Paths are relative to the repo the server was started against (serve --repo <root>).
Absolute paths are not a general escape hatch
On stdio, an absolute path inside the served root is accepted; one outside it is refused. On the
HTTP transport an absolute path is refused outright, whether or not it points inside the root, and
index_repo refuses one on both transports. Use repo-relative paths and they work everywhere. See
Security.
These fire in Agent mode, not Ask
MCP tools are only invoked when the client is in an agent/tool-using mode (e.g. Copilot Agent mode). In plain chat/Ask mode the model can't call them. See Troubleshooting.
get_context¶
Token-minimized, relevance-ranked context for the given paths: strips comments, collapses non-target method bodies, packs under a token budget, and leads with a header stating what was compressed and withheld.
| Argument | Type | Default | Notes |
|---|---|---|---|
query |
string | — | What you're looking for; ranks the files under paths (blended semantic + lexical relevance). Required. |
paths |
string[] | — | Files or directories, repo-relative. Required. (An absolute path is stdio-only and must be inside the root — see the note above.) |
tokenBudget |
integer | — | Max tokens of context to return. Required. |
level |
string | Balanced |
Conservative · Balanced · Aggressive. |
// request
{ "query": "how are numbers added", "paths": ["."],
"tokenBudget": 400, "level": "Balanced" }
// response — content[0].text (a single readable block)
// ---- Sankshep context ----------------------------------------------
// 329 original -> 255 delivered tokens (22.5% smaller).
// Missing something? Narrow `paths`, raise `tokenBudget`, or use level=Conservative.
// --------------------------------------------------------------------
// Calculator.cs:1-29
namespace SampleRepo.Math;
public class Calculator
{
private double _total;
public double Add(double a, double b)
{ /* … body elided (3 lines) */ }
public double Subtract(double a, double b)
{ /* … body elided (3 lines) */ }
}
Read the header as: original -> delivered (% smaller) is the compression of the files actually delivered.
Two more lines appear when they apply — a Searched N tokens line when the semantic index was consulted,
and a WITHHELD line naming the files that did not fit. Searched is context looked at, not a saving.
On very small inputs the locator header can make a file larger; that is reported honestly, not hidden.
tokenBudget is a ceiling on the whole response, the header included — not on the code alone.
An empty delivery returns isError: true, never a silent 0-token "success". Captured verbatim with
tokenBudget: 150 against a large tree:
// ---- Sankshep context ----------------------------------------------
// WITHHELD, did not fit tokenBudget: tests/…/ChunkQueryCoverageTests.cs, tests/…/IndexToolsTests.cs (+11 more)
// NO CONTEXT was returned: tokenBudget=150 was too small to admit any chunk.
// Re-run with corrected `paths` or a larger `tokenBudget` -- do not answer from guesswork.
// --------------------------------------------------------------------
search_code¶
Semantic (vector KNN) search over the local embedding index — pure nearest-neighbour similarity, no lexical
blend. Run index_repo first.
| Argument | Type | Default | Notes |
|---|---|---|---|
query |
string | — | Natural-language description of what you want. Required. |
topK |
integer | 10 |
Max chunks to return. |
Changed in 2.0.0 — the result is text, not JSON
Hits used to come back as one JSON line with every newline, quote and tab escaped. They are now the
same shape get_context uses: a header, then // path:start-end (Kind Symbol) above each block of
real code. Breaking for any caller that parsed the result as JSON. Everything a hit carried is
still present. See Upgrading to 2.0.0.
// response — content[0].text
// ---- Sankshep search: 2 hits, best first ----
// Pass any path below to get_context for the whole minimized file.
// Calculator.cs:6-28 (Type Calculator)
public class Calculator
{
// Running total, mutated by Add/Subtract.
private double _total;
public double Add(double a, double b)
{
_total = a + b;
return _total;
}
public double Subtract(double a, double b)
{
_total = a - b;
return _total;
}
}
// Inventory.cs:3-6 (Type IInventory)
public interface IInventory
{
int CountOf(string sku);
}
An empty index is an error, not an empty success — the change that matters most here, because the old
{count:0,results:[]} was indistinguishable from "this code does not exist". Captured verbatim:
search_code: the index is empty - nothing has been indexed for this repo yet, or the index was purged.
This is NOT evidence that the code does not exist. Run index_repo with path "." first (relative paths
resolve against the served --repo root, C:\...\sample-repo), then retry. Only index_repo discovers new
files; verify-on-read and SANKSHEP_WATCH refresh already-indexed files only.
If it returns nothing right after a successful index_repo, see
Troubleshooting → search returns nothing after indexing.
index_repo¶
Builds or refreshes the semantic index (chunks + local embeddings) that search_code uses. Downloads the
embedding model (~127 MiB, once) on first run.
| Argument | Type | Default | Notes |
|---|---|---|---|
path |
string | "." |
Directory to index. Optional — defaults to the whole served repo. Must be a directory, not a file. Repo-relative only: an absolute path is refused on both transports. |
force |
boolean | false |
Re-embed every chunk even if unchanged. |
// response — content[0].text
index_repo: indexed 3 file(s) under C:\...\sample-repo; the index now holds 4 chunk(s) in total.
The message names what this walk did and, separately, what the whole index holds. Those two numbers differ whenever you index a subdirectory, and the old message conflated them — it reported the index's total as if the walk had produced it.
When the client sends a progressToken, index_repo emits a progress notification per file, so a first
index of a large repository is distinguishable from a hang.
A path that doesn't exist, that names a file, or that holds no supported source files returns
isError: true with a clear message rather than a silent success.
Every index rebuilds once, on the first 2.0.0 start
Chunks are now sized by what the embedding model can actually read, so ChunkerVersion moved and an
existing index.db is discarded and rebuilt. It is derived data, so this is always safe — it just
takes minutes on a large repository. See Upgrading to 2.0.0.
summarize_repo¶
Walks a directory and returns namespaces, type names and public method signatures from every language Sankshep parses, not just C#.
| Argument | Type | Default | Notes |
|---|---|---|---|
path |
string | — | Directory to summarize. Required. |
maxTokens |
integer | 20000 |
Ceiling for the whole summary. The walk stops at a file boundary once reached. Minimum 500. |
// response — content[0].text
Repo summary: C:\...\sample-repo
## Calculator.cs
namespace SampleRepo.Math
type class Calculator
method public double Add(double a, double b)
method public double Subtract(double a, double b)
## Greeter.cs
namespace SampleRepo.Text
type class Greeter
method public string Greet(string name)
method public static string GreetFormal(string title, string name)
## Inventory.cs
namespace SampleRepo.Stock
type interface IInventory
type class Inventory
method public void Restock(string sku, int quantity)
method public int CountOf(string sku)
A truncated summary says so, on the second line. Before 2.0.0 this tool appended every declaration of
every file with no budget and no cap, which on a real repository could consume the whole context window.
Captured with maxTokens: 500 against src/Sankshep.Memory:
Repo summary: C:\...\src\Sankshep.Memory
TRUNCATED: 12 of 28 files shown, under a 500-token budget. This is NOT the whole repository. Raise
maxTokens, summarize a subdirectory, or use search_code to find what you are looking for directly.
A maxTokens below the floor is refused rather than silently raised:
A not-found path, or a tree with no supported source files, returns isError: true.
remember¶
Persists a fact (decision, convention, or note) to this repo's memory, tagged with the current git branch.
| Argument | Type | Default | Notes |
|---|---|---|---|
category |
string | — | e.g. decision, convention, gotcha. |
text |
string | — | The fact to remember. |
source |
string | null | — | Optional provenance (a file path or URL). |
// request
{ "category": "convention", "text": "All money values use decimal, never double.",
"source": "src/Calculator.cs" }
The current git branch is echoed back so you can see where the fact was tagged; off a git repo it is
recorded (and returned) as "global".
recall¶
Recalls remembered facts (current branch + global) whose text matches a query, optionally filtered by category.
| Argument | Type | Default | Notes |
|---|---|---|---|
query |
string | — | Words that must all appear in the fact's text, in any order, ignoring case. Required. % and _ are matched literally. |
category |
string | null | — | Optional filter, trimmed and matched case-insensitively. |
// response — content[0].text
{ "count": 1, "matched": 1, "truncated": false, "facts": [
{ "id": 3, "category": "convention",
"text": "Money is stored in minor units (cents) everywhere.",
"source": "docs/adr/0003-money.md", "createdAt": "2026-09-10T05:05:45+00:00" } ] }
Two fields are new in 2.0.0. matched is how many facts the query matched; count is how many were
returned. The result set is capped at 50, and when the cap bites, truncated is true and the two
numbers differ — so a caller can tell "these are all of them" from "these are the first fifty". Narrow the
query or pass a category to see the rest.
Matching is per-word, not substring: every whitespace-separated word in query has to appear somewhere in
the fact.
export_decisions¶
Writes all decision-category facts to a DECISIONS.md at the repo root, grouped by branch.
| Argument | Type | Default | Notes |
|---|---|---|---|
| (none) | Operates on the served repo. |
count is the number of decision-category facts exported. The category label is matched exactly — a
convention fact is not a decision, and neither is one filed under decisions or architecture-decision.
With none stored, the call is an error rather than a file with an empty section:
export_decisions: no facts are stored in the "decision" category, so nothing was exported and DECISIONS.md
was left untouched. Remember a fact with category "decision" first. Note the label is matched exactly:
"decisions" and "architecture-decision" are different categories.
Changed in 2.0.0 — it will not overwrite a file it did not write
The generated file now carries a marker on its third line:
# Decisions
<!-- Generated by sankshep export_decisions. Edits to this file will be overwritten. -->
A DECISIONS.md without that marker is refused rather than clobbered — which means the first
export after upgrading fails once if you already had one. Move or rename it and re-run.
export_decisions: DECISIONS.md already exists and was not written by export_decisions, so it was left
untouched rather than overwritten. Move or rename it first if you want it replaced.
Re-running over a file it did write succeeds and rewrites it, as it always did.
token_report¶
Cumulative token accounting for this repo — how much minimization compressed, per tool and in total. No dollar figure: Sankshep is never told which model you use or your rate, so any total would be an assumption, not a measurement.
| Argument | Type | Default | Notes |
|---|---|---|---|
| (none) | Reads the local stats.db. |
// response — content[0].text
{ "calls": 3, "scannedTokens": 987, "selectedTokens": 987,
"deliveredTokens": 769, "compressedTokens": 218, "compressionPct": 0.2208713272543060,
"byTool": [
{ "tool": "compose_task_prompt", "calls": 1, "scannedTokens": 329, "selectedTokens": 329, "deliveredTokens": 259 },
{ "tool": "get_context", "calls": 2, "scannedTokens": 658, "selectedTokens": 658, "deliveredTokens": 510 } ] }
On a fresh repo every number is 0 and byTool is [].
compressionPct is 1 − delivered/selected — measured against the original size of the files actually
delivered, never against scanned. On tiny inputs it can go negative (the locator headers cost more than
minimization saves); that is reported honestly. See Benchmarks for how savings and quality
are measured together.
compose_task_prompt (an MCP prompt, not a tool)¶
Composes a grounded, ready-to-use prompt from token-minimized code (get_context's engine) + remembered
conventions (recall). It returns a prompt to act on — never the answer itself.
| Argument | Type | Default | Notes |
|---|---|---|---|
task |
string | — | The coding task. |
paths |
string | — | Comma- or newline-separated paths to draw code from. |
tokenBudget |
string | 4000 |
Bounds the code. Conventions are additive (600 tokens), so the prompt is larger than this. Changed in 3.0.0. |
// request (prompts/get)
{ "task": "Add a Multiply method to the calculator", "paths": ".", "tokenBudget": "1500" }
// response — the composed prompt (4 labelled sections)
# Task
Add a Multiply method to the calculator
# Relevant code (minimized)
// Calculator.cs:1-29
namespace SampleRepo.Math;
public class Calculator
{
private double _total;
public double Add(double a, double b)
{
_total = a + b;
return _total;
}
public double Subtract(double a, double b)
{ /* … body elided (3 lines) */ }
}
# Project conventions
- Money is stored in minor units (cents) everywhere.
# Constraints
- Work within the code shown above; do not invent new packages or patterns.
- Follow the recalled conventions exactly. If information is missing, say so rather than guessing.
Note which body survived: Add is kept in full because the task names it, while Subtract is collapsed.
That is query-targeted body collapse, and it is the same engine get_context uses.
"Project conventions" reads the convention category only. A fact remembered under decision,
gotcha or anything else never appears here, however relevant it is. The section says so rather than
looking merely empty — with nothing stored at all it reads (none recorded), and when facts exist under
other labels it names them:
# Project conventions
(none recorded -- 1 fact(s) are stored under other categories (decision); compose_task_prompt injects only
facts remembered with category "convention")
See Prompt composer for the full anatomy and the prompt-not-answer boundary.
Resources¶
Alongside its tools, Sankshep exposes one MCP resource — data a client pulls in-context by URI rather than invoking with arguments.
sankshep://stats¶
Cumulative token accounting for this repo as application/json: how far minimization compressed the
delivered code, how much context was searched to find it, and a per-tool breakdown. It is the same
StatsSnapshot the dashboard feed serves, and is registered on both stdio and HTTP transports.