Skip to content

Serve the offline analysis to an assistant, read-only, over stdio - #33

Merged
Zenofex merged 1 commit into
mainfrom
feat/mcp-server
Sep 29, 2026
Merged

Zenofex merged 1 commit into
mainfrom
feat/mcp-server

Conversation

@Zenofex

@Zenofex Zenofex commented Sep 29, 2026

Copy link
Copy Markdown
Contributor

bootintel mcp speaks MCP over stdio, so an assistant does the reasoning half of reading a boot log while this binary does the reading half.

claude mcp add bootintel -- bootintel mcp

Here is a boot log from a router I am assessing. What should I look at first, and what can you not tell from this capture?

Why local is the point, not a detail

The model is the operator's own, under whatever agreement they already work under, and no capture leaves the machine. A hosted version would have to receive the log, which is the thing an NDA-bound consultancy cannot do and the reason the offline tooling exists at all. It also means this costs nothing to run: the reasoning is paid for by whoever already pays for the assistant.

Read-only, and tested as such

Tool
scan_log detectors over a capture
boot_chain_verdict what the boot chain permits, plus the kernel posture
list_detectors what this build recognises
sample_log an example, for trying it without a device

None opens a serial port, writes to a device, or reaches the network. the_tool_set_is_read_only asserts the list by name rather than trusting a policy, so adding a write is a deliberate edit somebody reviews. If that ever changes, the allowlist belongs in the process where the bytes are written, not in a prompt: a model can be argued into anything; a refusal in code cannot.

Captures are passed as text rather than a path, so the caller decides what is disclosed instead of the server reading whatever it likes off the disk.

The honesty has to survive a summariser

That is the part most at risk when a model sits between the tool and the reader:

  • an empty finding list says the capture was not recognised, not that the device is clean;
  • absence is reported as unknown, never as hardened;
  • every verdict carries the value it was read from.

Protocol details that break quietly

Notifications get no reply (answering one is an error some clients tolerate and others do not). An unrecognised protocol version falls back rather than failing the handshake. A malformed line does not end the session. A tool failure is a result carrying isError, not a transport error, so the model sees why rather than the client seeing a broken connection.

Verification

  • cargo test --workspace --features tui: 402 passed; without: 381 passed
  • clippy -D warnings and fmt --check: clean
  • A real handshake driven through the built binary: initialize → notifications/initialized → tools/list → tools/call, returning five verdicts and the KASLR reason from a synthetic capture

🤖 Generated with Claude Code

`bootintel mcp` speaks the Model Context Protocol over stdin and stdout, so an
assistant can do the reasoning half of reading a boot log while this binary does
the reading half. Given an inventory, a boot-chain verdict and a kernel posture,
what to look at first is exactly the question a model is good at; recognising
them in a capture is what this already does.

It runs locally, and that is the whole point rather than an implementation
detail. The model is the operator's own, under whatever agreement they already
work under, and no capture leaves the machine. A hosted version would have to
receive the log, which is the thing an NDA-bound consultancy cannot do and the
reason the offline tooling exists at all.

Every tool reads. None opens a serial port, writes to a device, or reaches the
network, and a test asserts the tool list by name rather than trusting a policy,
so adding a write is a deliberate edit somebody reviews. If that ever changes,
the allowlist belongs in this process where the bytes are written and not in a
prompt: a model can be argued into anything, a refusal in code cannot. Captures
arrive as text rather than as a path, so the caller decides what is disclosed
instead of the server reading whatever it likes off the disk.

The engine's honesty survives the trip through a summariser, which is the part
most at risk of being lost. An empty finding list says the capture was not
recognised rather than that the device is clean, absence is reported as unknown
rather than hardened, and each verdict carries the value it was read from.

Protocol details worth stating because they are the ones that break quietly:
notifications get no reply, an unrecognised protocol version falls back instead
of failing the handshake, a malformed line does not end the session, and a tool
failure is a result carrying isError rather than a transport error, so the model
sees why it failed rather than the client seeing a broken connection.

Verified against a real handshake, not only unit tests: initialize, initialized,
tools/list and a tools/call round trip driven through the built binary.

402 tests with --features tui, 381 without, clippy clean under -D warnings in
both, rustfmt clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Zenofex
Zenofex merged commit 1d0dc2d into main Sep 29, 2026
11 checks passed
@Zenofex
Zenofex deleted the feat/mcp-server branch September 29, 2026 21:21
Zenofex added a commit that referenced this pull request Sep 29, 2026
Version bump, lockfile, changelog. No code changes: everything here is
on `main` and was reviewed in #33.

## What ships

```sh
claude mcp add bootintel -- bootintel mcp
```

`bootintel mcp` serves the offline analysis over stdio: `scan_log`,
`boot_chain_verdict`, `list_detectors`, `sample_log`. The model is the
operator's own and no capture leaves the machine.

This is the release that makes it usable — the config line above needs
the installed binary to carry the subcommand.

## Why MINOR

New subcommand. Nothing renamed or removed, no exit-code policy change,
no output-format schema broken.

## Verification

- `cargo test --workspace --features tui`: 402 passed; without: 381
passed
- clippy `-D warnings` and `fmt --check`: clean
- Release binary reports `bootintel 0.13.0` and lists its four tools

Every tool is read-only and `the_tool_set_is_read_only` asserts the list
by name, so a write to a device cannot arrive with an upgrade.

Both version bumps landed first try, fifth release running for
`docs/releasing.md` step 1.

After merge: dispatch `cli-release` for `0.13.0` with `publish_crates`,
verify the draft against `SHA256SUMS`, publish and mark latest, then
move the tap (step 7) and sync the in-repo reference copy.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant