MCP integration¶
ntoseye can run as an MCP server. Agents may use either this, for WinDbg syntax with bounded run-control, or the Python SDK, for structured values and scripted loops (conditional stops, bulk enumeration); both drive the same debugger.
Tool |
Purpose |
|---|---|
|
Run one REPL line in ntoseye’s WinDbg-style syntax ( |
|
Attach to a target ( |
Run control¶
The command tool behaves like a WinDbg prompt, with one difference: it never blocks longer than timeout_ms.
A resuming command (
g,gh,gn,p,t,gu,pa,wt,.reboot, …) resumes and waits up totimeout_msfor the next stop. A stop is rendered the way the REPL renders it (breakpoint banner, registers, stack). If nothing stops in time the result ends with[target running]; the target keeps running and nothing is lost.A command that needs a halted target (
k,r,bp,t, …) sent while the target runs waits up totimeout_msfor the stop, renders it, then runs, the way WinDbg queues input typed at a running debuggee. If the target is still running when the budget ends, the result says so and the command was not run; re-issue it to keep waiting. Memory, process, module, and struct commands do not wait: they work live when memory comes from the host (the default--memory-source autowith a local VM, and the memory/gdb backends), and fail with a message saying why on a session that reads memory over KD.A resuming command sent while the target runs also waits, but is then refused once so the stop is seen before it is continued past.
Each command on a
;line is admitted against the target’s state at that point, not the state when the line arrived.break; bp nt!NtCreateFile; gtherefore works as one call:breakhalts the guest,bpruns on the halted target,gresumes. A barebpon a guest that nothing will stop only waits outtimeout_msand reports that it was not run.breakinterrupts a running target.A stop that arrived between calls (the guest hit a breakpoint while the agent was thinking) is rendered at the top of the next result. If that next call was itself a resuming command it is refused once, so the agent sees the stop before continuing past it.
A multi-step command (
pa,pt,gu,wt) that overruns the budget leaves the target running toward its next stop; the next halted-only command collects it. An emptylineruns nothing and only waits.
The trailer reads [target running] or [target halted @ <vcpu> <rip> <symbol> | process <name> (<pid>) | scope <name> (<pid>)], where process is the process whose page tables the stopped vCPU has loaded and scope is the .process inspection scope memory commands read through (it survives resumes, so the two can differ). After a reboot the target stops (over KD, at the new kernel’s first boot notification), and the trailer adds boot in progress until the kernel’s module list exists: kernel symbols and bp nt!... work there, so it is the place to set early-boot breakpoints, but process and module lists do not yet. g lets boot continue; while running, wait rather than enumerating stale state.
Guest debug output (DbgPrint) captured since the previous call is appended as [dbgprint] ... lines.
A typical breakpoint flow: break; bp nt!NtCreateFile; g, then k (which waits for the breakpoint if g returned with the target still running). For a user-mode breakpoint name the process and the symbol resolves in it: break; bu /p <pid> user32!PeekMessageW; g. No prior .process /p is needed, because the debugger loads that module’s symbols itself (see symbols).
A backtrace through a module whose PDB is not cached yet renders those frames as module+offset and fetches the PDB in the background rather than holding the call open; a later k shows the names.
Structured results¶
format: "json" returns {ok, output, result, target, debug_output} as structured content: output is the text the command printed, target is the run-state snapshot ({running, current_thread, rip, symbol, attached_process, stopped_process, stopped_thread, coherent, kernel_base}), debug_output the captured DbgPrint lines, and result the typed decoding for commands that have one, else null. The decodings are the same ones the Python SDK exposes as methods (the ! inspectors, lm, !process, k, bl, dt, ?, r, !analyze, …); see the SDK surface table for the set.
The server reads --backend/--connect/--kdnet-key/--dump to attach at launch, so the VM and its debug transport must be set up exactly as for the REPL (see Choosing a backend). Without those flags it starts empty and the client attaches with open. The guest runs freely between calls; wrong-process hits on shared-page breakpoints are absorbed in the background so it is never left frozen.
stdio (default)¶
The MCP client launches ntoseye mcp as a subprocess and talks to it over stdin/stdout. Most desktop MCP clients are configured with a JSON file listing the command to spawn:
{
"mcpServers": {
"ntoseye": {
"command": "ntoseye",
"args": ["mcp"]
}
}
}
Target options follow the subcommand, e.g. to pin the backend and socket:
{
"mcpServers": {
"ntoseye": {
"command": "ntoseye",
"args": ["mcp", "--backend", "kd", "--connect", "/tmp/ntoseye-kd.sock"]
}
}
}
For KDNET, use ["mcp", "--backend", "kdnet", "--kdnet-key", "1.2.3.4"]; add ["--memory-source", "kd"] to force target-mediated memory and add --connect only when changing the default 0.0.0.0:50000 listener. The open tool takes the same backend/connect/key choices; when the server starts without a target the agent is instructed to ask the user how the VM is exposed rather than guess.
Use an absolute path for command (e.g. ../target/release/ntoseye) if ntoseye isn’t within PATH.
Closing stdin, SIGINT, SIGTERM, and SIGHUP detach like close: installed breakpoints are removed and the guest resumes before the server exits. SIGKILL prevents cleanup and leaves breakpoint entries installed; see breakpoint recovery.
Streamable HTTP¶
For web MCP clients that connect over the network instead of spawning a subprocess, use --http:
ntoseye mcp --http 127.0.0.1:8080
The service is mounted at http://127.0.0.1:8080/mcp. HTTP binds are loopback-only by default. The command tool exposes the full REPL, including execution control, guest memory writes (eb, !eb), host filesystem writes (.dump, .logopen), and host files served to the guest (.kdfiles). Use --unsafe-http to bind a non-loopback address only on trusted networks.