Capturing
A DevTools Network tab for your local Ollama.
It watches the HTTP traffic between your tools and a local Ollama and shows it like a request inspector. Any MCP client reads that capture, read-only, so an LLM can answer questions about your inference.
Per request
It reads the traffic itself, so an editor extension, a closed-source client and your own script all show up the same way. Every one of them keeps the configuration it already has.
Capture is packet inspection through WinDivert, at the network layer, below the application that made the request. The app reassembles the TCP stream, parses the HTTP exchange and derives its metrics from the response it actually saw.
Your project keeps its own dependencies and imports untouched, so any tool on your machine is observable exactly as it ships. An editor extension or a closed-source client shows up on the same terms as your own code.
Capture reads a copy off the wire, outside the request path. Your request reaches Ollama on the route it always took, at the speed it always had, and an inspector that stops running leaves the inference running.
Your client keeps pointing at 127.0.0.1:11434. Open the app and traffic appears; close it and your setup is exactly as you left it.
What you inspect is what Ollama received. The request object your code built travels untouched, and the payload on screen is the one that crossed the wire, down to the streamed chunks, reassembled in order.
Every figure here is derived from the response the app captured, so two runs can be compared and the difference between them is real. A field with nothing to read from shows a dash until the response lands.
One request, thirty-one seconds apart. The four fields wait until there is something to read, so a number on screen tells you the measurement exists.
| Field | Where the value comes from |
|---|---|
| Request latency | Measured across the captured exchange |
| Token counts | Read out of the response body |
| Tokens per second | Derived from those two, never estimated |
| HTTP status code | Observed on the wire |
| Request and response bodies | Stored verbatim, up to 16 MiB |
| Streaming chunks | Reassembled in order: 1017 of them in the capture above |
| Loaded models | Confirmed against the Ollama API |
The contract is staged, so a client spends context only on what it asked for. Each call narrows the question the next one answers, and a body arrives once the model has picked a single stable id.
resolve_inference_contextOrientation. What models, endpoints and statuses exist at all, and over what time range.
Inputs
Outputs
Cheap because: Aggregates only. It never includes a body, a header or any per-inference detail.
search_inferencesNarrowing. Page through candidates under filters combined with AND.
Inputs
Outputs
Cheap because: Summaries carry stable fields only; heavy headers and bodies are omitted by construction.
get_inference_contextDetail, bounded. One known id, only the sections asked for, and a body read in slices.
Inputs
Outputs
Cheap because: The client sets the byte window, so a large body arrives across several calls, in the slices it asked for.
at DESCThe most recent inference first.
id DESCSeveral inferences can share a timestamp, so this holds a page boundary steady across two identical queries.
The cursor carries
A cursor replayed under different filters is rejected.
{
"mcpServers": {
"dllm-network": {
"command": "C:\\path\\to\\dllm-network-mcp.exe"
}
}
}The sidecar resolves the database location itself. Register it by absolute path and restart the client.
The GUI and the MCP sidecar are two separate OS processes with separate lifetimes: you start the GUI, and your MCP client starts the sidecar whenever it needs it. They meet at one SQLite file on disk, which is what lets a single store span that gap.
The file both processes open
%LOCALAPPDATA%\dllm-network\telemetry.dbdllm-networkDSN options
Opened once per session through sqlite.Open. It is the only connection allowed to write, and writes arrive batched from a separate drain goroutine so the capture loop never waits on disk.
dllm-network-mcpDSN options
A standalone stdio binary with no flags, launched by your MCP client. It resolves the database path through the same shared resolver the GUI uses, so the two can never disagree about where the file is.
Anything you connect to the sidecar (Claude Desktop, Claude Code, your own client) reaches your telemetry through a connection SQLite itself refuses to accept a write statement on, and a regression test keeps it that way. Point an LLM at your inference history knowing the worst it can do is read it.
Each one is a property of the shipped binary, because the linter stops a build that would break it.
The read side depends on a reader port, so "read-only" is a shape of the program. That is what makes the sidecar safe to hand to an LLM.
mcp-not-captureinternal/mcp/**The MCP SDK exists in exactly one place, so a breaking change upstream has one package to land in. Every other package builds and tests without the SDK present at all.
sdk-confined-to-mcpeverywhere except internal/mcp/**The inference type stays free of any storage driver, so a test can exercise the domain with nothing running behind it. How an inference is stored is entirely the sqlite package’s business.
inference-domain-purityinternal/telemetry/inference/**The same type is kept free of the MCP SDK, so what a protocol wants an inference to look like never reaches the model. Adding an HTTP transport beside the stdio one touches no domain code.
inference-domain-purityinternal/telemetry/inference/**Every rule name above is a line in the repository’s linter configuration, so each boundary here is one you can go and check.
One repository, two artefacts: the tray app that captures, and the stdio binary that serves what it captured.
$ go build -o dllm-network-mcp.exe ./cmd/dllm-network-mcpThe tray app, the MCP sidecar and the capture pipeline. Apache 2.0.
The data flow, the WAL seam and the four enforced boundaries, in the repo’s own words.
Build, register and use the MCP server; the three-tool reference and troubleshooting.
Confirmed-versus-inferred semantics, and how each field is labelled in passive mode.