Repository navigation
Conversation
The MCP server needs it too, so it moves where both can share it.
Status, directory listing, file push, launch, reset and a DMA read of the 40-column screen, over USB serial or the TCP listener. Zero-dependency Node; the folder is also a Claude Code plugin.
A refusal at the menu is followed by ProcessCommand's two 32-bit stats, which ended up in the tool error text; keep only the refusal's line, and make the fake board send the trailer the firmware does. Screen codes 27-31 are [ £ ] ↑ ← in both ROM sets, not graphics.
The firmware reads a path into a fixed buffer and runs whatever is left over as commands, so a too-long path or one with a NUL is refused before anything is sent.
A path character above U+00FF reached the board as its low byte, so U+0100 ended the path early and the bytes after it ran as commands; refuse anything outside Latin-1.
Agents get fixed field names and enums instead of prose to parse, which costs fewer tokens and gives the same shape every call.
A plain TR's firmware has no DMA commands, so it ran the read's address and length bytes as further commands.
The TR BASIC handler reads USB serial itself, so after launching one of its items the board stops answering remote commands. A test pins the list to every IOH_TR_BASIC entry in MainMenuItems.h.
DMA right after a C128's reset can hang its boot, and over TCP the next tool call could land in that window. Each poll reconnects, since a reset from the minimal image restarts the board.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
tools/mcp, an MCP server that lets an AI agent (Claude Code, Claude Desktop, or any MCP client) drive a TeensyROM over the remote command protocol indocs/ControlComms.md. It is zero-dependency Node 24, like the rest oftools/, and the folder is also a Claude Code plugin.Tools
status: image (main, minimal, none), firmware, board type, machine, menu boot state, DMA availablelist_dir, with pagingread_screen: the 40-column text screen as textpush_file, replacing any file at that pathlaunchresetEvery tool declares an
outputSchemaand returns JSON matching it (structuredContent, plus the same JSON as text for clients before MCP 2025-06-18), so agents get fixed field names and enums instead of prose. The tests check each result against its schema.Each call opens its own connection, over USB serial (macOS/Linux) or the TCP listener (
TR_HOST), and closes it, so other TeensyROM apps can reach the board between calls. A refusal comes back as a tool error with the board's own words (Busy!and so on). Paths the firmware's path reader would overrun, or that would encode to a NUL, are refused before anything is sent.Some calls would leave the board in a bad state, so the server refuses or waits:
read_screensends no DMA command unless the board is a TR+ running its main image. A plain TR's firmware has no DMA commands and would run the address and length bytes as further commands.launchrefuses the built-in items run by the TR BASIC handler (both TR BASIC items, DMA Pause Check, the digital clock). That handler reads USB serial itself, so the board stops answering remote commands. A test checks the list against everyIOH_TR_BASICentry inMainMenuItems.h.resetreturns once the menu reportsBoot: complete, reconnecting for each poll. DMA right after a C128's reset can hang its boot. Firmware before v0.9 reports no boot state, and thereresetreturns at once.parseBannermoves fromtools/lib/trserial.mjstotools/mcp/lib/banner.mjs, so the flash tool and the server share it. It now also reads the machine, video standard andBoot:state;trserial.mjsre-exports it.Verification
npm testpasses on macOS, and innode:24on linux/amd64 in Docker. New tests run against a fake board on a local TCP port, driveserver.mjsover stdio, and pin every token the server sends to its constant inSource/Teensy.server.mjs:statusreported the main image, the machine andmenu: complete.read_screenreturned the TR menu as text.list_dirpaged the SD root.push_filewrote a BASIC PRG into a new directory, then again over the same file.launchran it, andread_screenshowed its output.list_diraway from the menu returnedBusy!.resetbrought the menu back.read_screen,launchandresetrefusals and waits above were tested against the fake board only.claude plugin validate tools/mcppasses.Review
The server commits got one review at medium. It fixed binary trailer bytes leaking into refusal messages, and the symbols after Z decoding as
..The path guard got one review at low; it found nothing.
A final branch-wide pass at medium closed a way around the guard: non-Latin-1 characters encoded to NUL or command bytes.
That fix got one low review.
The structured output got one review at medium. It made the test schema checker refuse keywords it doesn't enforce, and that fix got one low review.
The three board rails got a review each (low, low, medium) and one branch pass over all three at medium. The reviews made
read_screenrefuse the minimal image as well, maderesetreturn at once on firmware without a boot state, and added tests for a poll whose connection drops.Every fix landed with a test that failed first.