Agent mode
Every minc tool can answer in records instead of terminal text.
minc agent serves those records over the
Model Context Protocol, giving
a coding agent six tools to compile, run, test, debug, profile and query a
project.
The server keeps the source index warm between calls. When the agent asks about the code, the parser answers, not a text search. When a program crashes, the agent gets a backtrace, not a status code to chase.
Setup
With Claude Code:
claude mcp add minc -- minc agent
With any other client that can launch a stdio server:
{
"mcpServers": {
"minc": { "command": "minc", "args": ["agent"] }
}
}
One registration covers every project. If the client offers the MCP
roots capability, the server works in the client's first root
and follows it when it changes. Otherwise it works in the directory it
started in. Pass --root <dir> to pin it.
The install puts minc, minc-lsp and
minc-dbg side by side. The server itself runs inside
minc-lsp, and the debug and profile
tools call minc-dbg.
The tools
| tool | arguments | result |
|---|---|---|
query | what, name, target | definitions, references, callers, matching symbols, defining files, import closure |
compile | file, output, flags, hash | diagnostics with codes and fixes, then a summary or output record |
run | file, args, timeout, memory, flags | the program's output, then exit status, milliseconds, peak memory |
debug | file, args | the fault and a symbolized backtrace |
profile | file, args, seconds | the functions by share of samples |
test | scope, flags | one record per test, then the results |
A tool sets isError when its command exits nonzero. For
compile that means diagnostics. For run it means
the program failed or crashed. compile, run,
debug and profile work on the file you name.
test runs the whole project suite, through
build.mc if the project has one.
Diagnostics carry the repair
When the compiler knows the mechanical repair, it ships the fix with the diagnostic, and the agent can apply it without spending a reasoning turn.
{"kind":"error","file":"app.mc","line":5,"col":9,"code":"type-mismatch",
"message":"initializer type mismatch",
"fix":{"kind":"wrap","line":5,"col":13,"end_col":16,"before":"cast(i32, ","after":")"}}
| code | fix | effect |
|---|---|---|
type-mismatch, sign-mismatch | wrap | put before and after around the expression at line:col |
must-use | insert | put text at line:col, here ignore |
unused-variable, unreachable | delete_line | remove the line |
syntax | reported with position and message |
What comes back
Each tool returns one JSON object per line, and streams them as the run produces them.
| record | from | fields |
|---|---|---|
error, warning, note | every tool | file, line, col, code, message, fix |
summary, output | compile | errors; or file, bytes, lines |
hash | compile | the output's SHA-256; the same source and flags give the same digest |
run | run, debug | exit, ms, peak_kb, and signal, timeout or status on a failure |
crash | debug | signal, pc, frames of fn, file, line |
profile | profile | samples, ms, exit, functions of fn, samples, pct |
test, results | test | name, status, ms, exit, a failure's output or diagnostics; then passed, failed, unchanged |
leak | --track-alloc builds | live, total allocations at exit |
def, ref, caller, file | query | file, line, col, symbol, node; in and sites where they apply |
A crash record names the faulting instruction and the path that reached it. Inlined functions appear under their caller, each with its own line:
{"kind":"crash","signal":"access_violation","pc":"0x401042",
"frames":[{"fn":"depth","file":"rec.mc","line":2},{"fn":"depth","file":"rec.mc","line":3},
{"fn":"main","file":"rec.mc","line":7}]}
{"kind":"run","exit":-1073741819,"status":"0xc0000005"}
The profiler samples the instruction pointer about a thousand times a second and ranks the functions by share:
{"kind":"profile","samples":1095,"ms":1674,"exit":0,
"functions":[{"fn":"quick_sort","samples":867,"pct":79.1},
{"fn":"insertion_sort","samples":201,"pct":18.3},
{"fn":"main","samples":27,"pct":2.4}]}
The source index
The index only counts identifiers the parser resolved. So
query reports the sites that actually reach a declaration.
what | answers |
|---|---|
def | where a name is defined; Struct.field names a field |
refs | every reference site, with the declaration holding it |
callers | functions whose bodies reference it, with site counts |
symbols | definitions whose name contains the text |
defines | the files that define it |
closure | every file a compile of the named file reads |
$ minc query callers random_quat
sample_browser/sample_collision.mc:133:6 caller query_spawn_once 1
sample_browser/sample_collision.mc:321:6 caller create_wave_pile 1
sample_browser/sample_compound.mc:113:6 caller build_compound_hulls 1
sample_browser/sample_joint.mc:1826:6 caller gl_create_debris 1
The index covers top-level declarations across the tree, plus the
modules it imports from outside, standard library included. It indexes for
one target at a time, and target picks which when
arms it keeps. It skips whatever the root's .gitignore lists,
the build directory, and any directory starting with a dot.
The minc compiler's own source, 149,000 lines across 50 files, indexes from
cold in about 0.3 seconds. Later calls only re-index the files whose
modification time or size changed.
On the command line
The MCP tools can be used directly as verbs on the minc compiler. A harness that runs shell commands instead of MCP gets the same records.
| switch | shape |
|---|---|
--agent | one line per diagnostic, one summary line per run, sorted output, no prompts |
--agent=json | the same as JSON records, one JSON object per line |
MINC_AGENT=1, MINC_AGENT=json | agent mode flag for the process |
$ minc app.mc --agent
app.mc:5:9: error: initializer type mismatch [fix: wrap 5:13-16 in cast(i32, ...)]
1 error(s) found
Color and source excerpts switch off by themselves when stderr is a
pipe or a file. NO_COLOR switches off the color alone.
The --agent flag also sets MINC_AGENT for every process
the compiler starts. Compiles from a build.mc, the test
runner's processes and minc run inherits the setting.
The same tools by hand
| command | does |
|---|---|
minc app.mc | compile; --hash prints the output's SHA-256 |
minc run app.mc | build and run, with --timeout N and --memory N limits |
minc debug --batch app.mc | run to the end and report a crash with its backtrace |
minc profile app.mc | sample the run and list the functions by share |
minc test | compile and run test/*.mc; --filter, --changed, --timeout |
minc query refs name | ask the index |
minc agent | serve all of it over MCP |
The profiler prints the program's own output first, then the sample count and the functions by share. Example output from headless box3d benchmark:
$ minc profile live-demo/box3d_bench.mc
samples: 118
35.5% 42 b3SolveContacts_Convex
13.5% 16 b3CollideTask
6.7% 8 b3IntegrateVelocitiesTask
5.9% 7 b3PrepareContacts_Convex
5.0% 6 b3FinalizeBodiesTask
5.0% 6 <outside .text>
4.2% 5 b3WarmStartContacts_Convex
2.5% 3 b3ScatterBodies
The --track-alloc flag builds the program with allocation counters
and reports what is still live when main returns. That lets
run tell you whether a program leaked, without touching the
program's source. Note: current version only tracks if leaks occured, not
where they happened.
$ minc run --track-alloc list.mc
sum 6
leak: 4 live of 4 allocations
$ minc run --track-alloc --agent=json list.mc
sum 6
{"kind":"leak","live":4,"total":4}
{"kind":"run","exit":0,"ms":36,"peak_kb":3644}