MCP tools
sidekit mcp serves ten tools over stdio. Every one answers with compact,
structured JSON, omits null and default fields, and caps long lists with an explicit count of
what was left out.
Every tool that takes a project defaults to the workspace's single project when
the workspace has exactly one. When it does not, the tool asks you to name a
.csproj rather than guessing. Paths may be absolute or workspace-relative, and
answers come back workspace-relative.
Reading an answer
A few fields appear across several tools and carry more meaning than their size suggests. An agent has to be able to tell a narrow answer from a wide one, or it will trust a precision it did not get.
resolution — how confident the answer is
| Value | Meaning |
|---|---|
line | Answered from the coverage map at line granularity — the narrowest answer. |
file | Answered from the coverage map for the whole file. |
project | A superset from the static project graph. Correct, but wider than necessary. |
full | A workspace-level build file changed; everything is in scope. |
none | Nothing to select. |
stale — the map predates your edit
stale: true means the file has uncommitted edits newer than the coverage map, so
the answer describes the code as it was when the map was built. Refresh it by re-running the
file's tests with withCoverage. The field is omitted entirely when false.
searched — how much of the workspace was looked at
find_symbol and
find_usages add searched when some
projects could not be loaded — “3 of 26 projects; 2 unsearchable: NETSDK1004: run dotnet
restore”. It is omitted when everything was searched, so its absence is the signal that
the answer is complete. An empty result over an incomplete workspace also carries a
diagnostic saying as much, because “no matches” and “not looked
yet” must never read the same.
The capability envelope
get_status and run_tests answers carry
capabilities — runner (mtp, vstest or
none), coverageResolution and instrumentation — so a
degraded repository reports reduced capability as data rather than as a failure you
discover by crashing.
get_status
Does this project compile right now, and what broke?
Blocks briefly while an in-flight evaluation settles, then returns the build state with shaped top-N diagnostics. The daemon keeps every queried project warm, so repeat calls do not pay a cold project load.
| Parameter | Default | Meaning |
|---|---|---|
project | workspace default | Path to the .csproj to evaluate. |
waitMs | 5000 | Milliseconds to wait for an in-flight evaluation to settle. |
sinceGeneration | none | The generation from your last answer. Still matching means nothing changed. |
{ "generation": 7, "unchanged": true }
That is the whole response — around 33 bytes. Passing sinceGeneration on every
poll is the single cheapest thing an agent can do.
{
"generation": 8,
"ready": true,
"state": "BuildFailed",
"capabilities": { "runner": "mtp", "coverageResolution": "none" },
"build": {
"items": [
{ "id": "CS0103", "severity": "Error", "file": "src/A.cs", "line": 3, "message": "…" }
],
"totalDistinct": 1, "remainder": 0,
"errorCount": 1, "warningCount": 0, "compiles": false
}
}
Possible state values: BuildPassed, BuildFailed, Building, LoadError, EngineError.
run_tests
Build incrementally and run a project's tests out of process.
Answers with counts, duration and the top failure summaries plus a remainder count — never the console output. When nothing in the project's reference closure has changed, the build is skipped entirely.
| Parameter | Default | Meaning |
|---|---|---|
project | workspace default | Path to the test project's .csproj. |
tests | whole suite | Fully-qualified test method names to run. |
maxFailures | 5 | Failure summaries to include; the rest becomes a count. |
withCoverage | false | Also refresh the per-test coverage map for the tests this run executes. |
retryFailed | 0 | Re-run failures up to N times (max 5); one that passes on retry is flagged flaky. |
{
"ran": true,
"total": 100, "passed": 0, "failed": 100, "skipped": 0,
"durationMs": 9000,
"failures": [
{ "test": "Acme.Tests.Feature0Tests.Case_000", "message": "Shared root cause 0", "affected": 20 }
],
"moreFailures": 0,
"capabilities": { "runner": "vstest", "coverageResolution": "none" }
}
A hundred failures stay inside 800 bytes because tests failing from one cause are reported once
with an affected count, biggest clusters first.
ran: false with a diagnostic saying it was superseded. Re-run for
current results. A failed build answers the same way — structured, never a transport error.
With withCoverage: true the answer adds a coverage node
(refreshed, skipped, current). See
Coverage map for what that costs.
get_failures
What failed in the last run — without running anything again.
Re-queries the recorded run. Summary mode gives clustered first-line messages; full messages and stack traces come only on request, so an agent never pays for stacks it will not read.
| Parameter | Default | Meaning |
|---|---|---|
project | workspace default | Path to the test project's .csproj. |
topN | 5 | Failures to include; the rest becomes a count. |
includeStacks | false | Full messages and stack traces instead of first-line summaries. |
tests | all | Scope to these tests (names or * wildcards) with full detail per match. |
Naming tests implies full detail — it is the cheap way to inspect one failure
without turning stacks on for all of them. When no run is recorded, the answer says so
structurally rather than erroring.
select_tests
Which tests should I run for this change?
Diff-first: with no arguments the git working tree is the change set. Answers a high-confidence superset from the static project dependency graph — changed file, to owning project, to transitively dependent test projects.
| Parameter | Default | Meaning |
|---|---|---|
files | git working tree | An explicit change set, absolute or workspace-relative. |
{
"resolution": "project",
"testProjects": [ "tests/Api.Tests/Api.Tests.csproj" ],
"changed": 1
}
resolution becomes file and
the answer names exact tests. One unmapped file keeps the honest project-level superset —
it never narrows on partial knowledge.
A workspace-level build file change escalates to resolution: "full" with
fullRun: true. Run whatever comes back with
run_tests.
get_tests_for_file
Which tests exercise this file, or this line?
Answers from the persistent per-test coverage map, degrading honestly: line, then file, then the project-level superset when the file was never mapped.
| Parameter | Default | Meaning |
|---|---|---|
file | required | The file to ask about. |
line | whole file | A specific line, 1-based. |
Line queries on a file with uncommitted edits are translated back through the diff to the map's original numbering, so your line numbers keep working between refreshes. A line inside an edited hunk degrades to file resolution instead of answering wrongly.
get_blast_radius
What could a change to this file break?
The owning project, the downstream projects that transitively depend on it, the test projects guarding it, and the tests known to exercise it — in one envelope.
| Parameter | Default | Meaning |
|---|---|---|
file | required | The file to ask about. |
line | whole file | Narrows the answer to the symbol declared there. |
{
"project": "src/Core/Core.csproj",
"impactedProjects": [ "src/Api/Api.csproj" ],
"testProjects": [ "tests/Core.Tests/Core.Tests.csproj" ],
"coveringTests": [ "Core.Tests.Adds" ],
"symbol": "Core.Calc.Add(int, int)"
}
Given a line, the answer covers that symbol's declaration, its overrides and
implementations, and its transitive callers — and coveringTests narrows from
everything touching the file to everything reaching that symbol.
symbol is omitted when the answer is file-granular, and that
case is never silent: if no symbol resolves at the line you gave, diagnostic
reads “No symbol resolved at that line — answered at file granularity.”
get_flaky_tests
Is this red real?
Answers from the persistent run history: tests whose recent outcomes flip between pass and fail, most confident first, with runs, failures, flips, confidence and last outcome per test.
| Parameter | Default | Meaning |
|---|---|---|
lastRuns | 20 | How many recent runs to consider. |
find_symbol
Where is this declared?
Searches the workspace's C# declarations by exact name or * pattern and answers
with kind, file:line and a signature — the replacement for grepping for
class Foo or interface IFoo. Every match is returned rather than one
being guessed at.
| Parameter | Default | Meaning |
|---|---|---|
name | required | Exact name, or a * pattern such as Sqlite*. |
kind | any | Restrict to type, interface, method, property, field or event. |
limit | 20 | Declarations to return; the rest becomes moreMatches. |
{
"matches": [
{ "symbol": "Acme.Persistence.SqliteIndex", "kind": "type",
"at": "src/Acme.Infrastructure/Persistence/SqliteIndex.cs:39" }
]
}
Measured on Sidekit's own repository, that answer costs 37 tokens. The
grep -rn an agent runs to answer the same question costs 2,496,
and hands back comments, strings and same-named members of unrelated types along with the
declaration.
signature is included only when it says something the name does not, and then
parameter types only — never a fully rendered signature with return types, parameter names and
default values, which measured larger than the answer it was decorating.
find_usages
Who uses this?
Every reference across the whole solution — including from the projects that depend on the one
declaring it — grouped by file with counts. Correct where grep is not: no
comments, no strings, no same-named members of unrelated types.
| Parameter | Default | Meaning |
|---|---|---|
name | none | Symbol name, optionally qualified — SqliteIndex or Persistence.SqliteIndex. |
file | none | File declaring the symbol, when a name alone is ambiguous. |
line | none | 1-based line of that declaration. |
limit | 20 | Candidate declarations offered when the name is ambiguous. |
Give it a name, or a file and line naming the
declaration — one or the other is required.
{
"symbol": "Acme.Persistence.SqliteIndex",
"declaration": "src/Acme.Infrastructure/Persistence/SqliteIndex.cs:39",
"uses": 60, "files": 12,
"byFile": [
{ "file": "tests/Acme.Infrastructure.Tests/Persistence/SqliteIndexTests.cs",
"count": 19, "lines": [6, 19, 29, 31, 39, 55] }
],
"moreFiles": 4, "moreUses": 4
}
Sixty uses across twelve files, in 264 tokens — against 2,496 for the
grep that answers the same question less accurately. Below roughly ten uses the
answer comes back as a flat sites list instead, because grouping is not free: both
shapes are built and the smaller one is sent. See
Token economy for the budgets.
RunAsync is declared nineteen times in Sidekit's own solution, so asking by that
name alone returns the candidate list and a diagnostic —
“'RunAsync' is declared 19 times — ask again with the file and line of the one you
mean.” Returning the first would hand back a complete-looking use list for a method
nobody asked about, with nothing in the answer saying so.
grep, not a language server.
Against grep -rn the answer is around five times smaller overall, and 8–19×
on heavily-used symbols. Against the rendered output of a host's own C# language server it
is 1.32×, and below ten references it is larger. Where your host already runs a
working one, what is left is addressing by name, one call instead of two, and an answer that
states its own coverage.
doctor
Why is something behaving oddly?
A structured pass / warn / fail report across the .NET SDK, workspace and solution discovery, NuGet restore state, which runner each test project uses, coverage-collector availability, the git working tree, index writability and file watching. Every check is probed rather than assumed, and every warning names what degrades and how to fix it.
| Parameter | Default | Meaning |
|---|---|---|
workspacePath | server workspace | Workspace to diagnose. |
The same report is available from the terminal as sidekit
doctor, which is how you tell a Sidekit problem from an agent-host environment
problem.