Most of debugging hardware is reading. You have a UART log, a current trace, a couple of GPIO lines and an I2C bus, and the question is almost always some version of what happened around the moment things went wrong. Scryglass exists to put all of that on one timeline so a person can read it. The Model Context Protocol server inside it exists so a language model can read it too.
MCP is the open protocol most assistants now speak for connecting to tools — Claude, Cursor, Codex, VS Code, and a growing set of local-model front ends. We chose it precisely because it isn't tied to a vendor. The server runs inside the app on your machine, on localhost, and whatever client you point at it gets the same tools. If your workflow is a local model on your own GPU because the firmware is confidential, that works. If it's a hosted model because you want the best reasoning available, that works too. We don't have an opinion about which model you use; we have opinions about what it should be able to do.
What it can see
The server exposes twenty-seven tools. Nineteen of them are read-only, and they are the point.
Finding your way around. list_channels returns every channel in the active project — its type, its user-facing alias, and which physical device is currently backing it. current_time_range says what the local cache actually covers, so a question about "the last ten minutes" can be checked against reality before it is answered.
Text channels. search_channel_text and read_channel_text work on UART output, annotations and recipe events. Search takes one or more patterns — literal or regex — over a time range that can be written as {start, end}, last 5 minutes, or all. Read gives you the lines in order, or newest first.
GPIO. query_transitions lists level changes. query_pulses finds runs where a pin held a value for at least some duration. gpio_stats gives edge counts, frequency, duty cycle, time-in-high and low, pulse-width statistics, and a count of runts below a threshold — in one call rather than a model hand-deriving it from a list of edges.
I2C and SPI. query_i2c_transactions returns transactions with address, direction, register pointer and raw bytes, filterable by any of those. query_spi_transfers does the same for full-duplex transfers with MOSI/MISO hex filters. i2c_register_state_at_time replays every write up to a timestamp to reconstruct what a target's register file looked like at that instant. bus_stats gives rates, byte totals and per-address and per-register histograms.
Power. value_at_time and aggregate — the latter bins a current channel into fixed windows with count, min, max or mean, and widens the bin automatically rather than return ten thousand rows.
Across channels. These are the ones we're proudest of. timeline merges discrete events from any set of channels — GPIO edges, bus transactions, UART lines — into one time-ordered stream, which is the tool a model reaches for when asked "show me everything around 22:40:33". snapshot_at_time gives the instantaneous state of several channels at one moment. And measure_latency takes a trigger channel and a response channel and measures the time from each event on the first to the next event on the second: I2C write to interrupt edge, GPIO edge to UART line, SPI command to data-ready. That is a question people answer with cursors and a notepad; here it's a function call with min, max and mean.
The tool descriptions are written for the model, not for us. Each one says what it returns, what the limits are, which sibling to use instead for a different question, and where the traps are — that SPI capture has no chip-select line, that register replay assumes auto-increment addressing, that pulses never merge across a session boundary. A model that reads the descriptions makes fewer wrong calls, and a description that admits what the data doesn't contain is worth more than one that oversells.
What it can do, and why that's off by default
Eight tools actuate hardware: gpio_set, uart_write, i2c_read, i2c_write, i2c_scan, spi_transfer, run_macro and power_set. They let a model drive a pin, type into a device's shell, poke a register, run a saved bus macro, or set a rail's voltage and current limit.
They are all disabled until you turn on Allow direct hardware control in Settings. We thought about this more than about anything else in the server. A model that can read your capture can, at worst, tell you something wrong. A model that can write to an I2C register can brick a part, and an active read on a clear-on-read register changes the very state it was asked about. So:
- The gate is per-installation, off by default, and lives in the app's own settings — never in the client.
- A gated call made while the gate is closed doesn't fail silently. It returns an error explaining exactly which setting to change, and marks the result
direct_hardware_enabled: falseso the model can tell you rather than guess. - Every actuating tool accepts
dry_run: true, which bypasses the gate and echoes exactly what would have been sent — the bytes, the pin state, the parsed macro steps — without touching anything. A model can plan a whole sequence and show it to you before you decide to open the gate. - Power rails stay inside the limits you configured in the UI. The tool operates within that envelope and cannot raise it.
- Devices you've marked sensitive refuse MCP writes outright.
write_annotationis the one write that is not gated: it writes a note, attributed to the assistant, into the session's annotation stream. Non-destructive, and useful — the model's own findings end up on the timeline next to the evidence.
Where it listens
Today the server listens on 127.0.0.1 only, over plain HTTP, with no authentication — and those three facts go together. Bound to loopback, the machine is the trust boundary: anything on your computer that can reach localhost already has more interesting things it could do, and a token would add ceremony without adding protection. That scope is deliberate for now, not permanent. When we open the server to other machines — a bench PC driven from a laptop, a shared lab rig — it will arrive with authentication and TLS as part of the same change, because at that point the boundary moves and the rules have to move with it.
What a session looks like
Here is the shape of a session we run ourselves. (The wording is the model's; the tool calls are what matters.)
You ask: the board rebooted sometime overnight — when, and what was the current doing?
The model calls list_channels and finds a UART aliased nrf5340 console, a current channel vdd main, and a GPIO reset. It calls search_channel_text on the console for boot|reset|\*\*\* with regex: true over all, and gets back three lines at 03:12:41.8. It calls timeline on all three channels for the twenty seconds either side, and sees the reset pin fall 40 ms before the first boot banner, and no I2C activity in the gap. It calls aggregate on vdd main with one-second bins over the minute before, and reports that current climbed from a 3 mA idle to 210 mA over the last four seconds before the reset — then, because it read the description, notes that a four-second ramp at that magnitude is consistent with a brown-out on the regulator rather than a watchdog, and suggests you look at what the firmware was doing at 03:12:37.
Then it calls write_annotation and leaves a note at 03:12:41.8 saying so, and you see it on the chart the next morning.
Every one of those calls is read-only. Nothing was driven. The gate stayed closed. If you then wanted it to i2c_read the regulator's status register, you'd open the gate — or ask for a dry_run first and see the exact transaction it intends. And soon, with the gate open, it could halt the core and read the reset-reason register directly — see below.
Connecting
The server is on by default on port 8765 and needs Scryglass to be running. It's included with the Professional and Commercial tiers.
For Claude Code:
claude mcp add --transport http scryglass http://127.0.0.1:8765/mcp
For clients that take a JSON config — Cursor, VS Code and most others — the server entry is just the URL:
{ "mcpServers": { "scryglass": { "url": "http://127.0.0.1:8765/mcp" } } }
Clients that only speak stdio can bridge to it with the community mcp-remote proxy. The docs have the exact snippet for each client.
Coming next: the debug probe
Everything above is about the channels — what the device said and did. The next set of tools reaches into the device itself, through the same debug probes Scryglass already drives: J-Link, ST-Link, CMSIS-DAP, the ESP32's native USB-JTAG, and the rest of the probe-rs family. Halt, single-step, set a breakpoint, read a variable or a register, dump a memory range, resume — the things an IDE's debugger pane does with a mouse.
The reasoning is simple: if an IDE can do it, so can the MCP. A model that has just watched the current ramp and the reset pin fall should be able to halt at the reset vector, read the reset-reason register, and tell you whether it was a brown-out or a watchdog — instead of telling you where to look. Same gate, same dry_run, same rule that nothing touches the device until you say so.
It isn't an agent that debugs your board for you, and it isn't a replacement for the in-app assistant, which uses the same tools with the same gate. It's a well-described set of questions you can ask about your data, from whichever assistant you'd rather ask them in.