mcp-server-misterfpga
Provides tools for managing NFC cards and readers on MiSTer devices, including listing NFC readers, card mappings, and the last card scanned; writing a game onto an NFC card; and mapping a card's UID to launch a game without writing to it.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcp-server-misterfpgalaunch Super Mario World on the Family Room"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mcp-server-misterfpga
An MCP server for MiSTer FPGA
devices: find and launch games, see what's being played and what's been played most, take
screenshots, make NFC cards, change MiSTer.ini settings (globally or per core), back up saves,
and start Update All, from Claude Code or Claude Desktop. It talks to each MiSTer through
Zaparoo Core's API and, for what Zaparoo doesn't cover, SSH. One server
drives several MiSTers.
A sibling of mcp-server-harmony, mcp-server-sofabaton, mcp-server-webos and mcp-server-batocera, with the same conventions, and sharing code with them through mcp-server-shared. docs/DESIGN.md is the design, the research behind it, and the decisions.
Status: verified against the simulator only. HARDWARE_VALIDATION.md is the checklist for a first session with real MiSTers.
Set up your MiSTer
1. Zaparoo Core
Zaparoo isn't on a MiSTer by default. From Zaparoo's MiSTer page:
Run Update All. During its countdown, press UP to open its settings, turn on Zaparoo under Tools & Scripts, choose SAVE, and let it run.
On the MiSTer, open Scripts → zaparoo and accept the prompt to start it as a service at boot.
From another machine, open
http://<mister-ip>:7497/app/: that's Zaparoo's web UI. Let it build its media database (that's whatsearch_gamessearches).For accurate tracking of games launched from the MiSTer's own menu, Zaparoo asks for
recents=1andlog_file_entry=1in[MiSTer]ofMiSTer.ini. Once SSH works (below),set_inican do it;doctortells you when they're off.
This server talks to Zaparoo without a key or pairing: on MiSTer, Zaparoo admits unauthenticated
clients from the LAN (Z-LEGACY below). Don't configure a Zaparoo API key for it: once any key is
configured, Zaparoo refuses every client without one, the Zaparoo phone app included.
2. SSH (for MiSTer.ini, save backups, attract-mode detection and the running core)
MiSTer's root password is 1. Give it a key once, from the machine this server runs on:
ssh-keygen -t ed25519 -N "" -f ~/.ssh/mister_ed25519
ssh-copy-id -i ~/.ssh/mister_ed25519.pub root@<mister-ip>SSH is optional per MiSTer; without it, get_status lists what's unavailable.
3. Tell the server about it
mcp-server-misterfpga check --host <mister-ip> --name "Family Room" --model "SuperStation One" \
--ssh --ssh-key ~/.ssh/mister_ed25519check confirms Zaparoo answers, shows the MiSTer's SSH host key fingerprint and pins it once
you say yes, tests the login, and saves the MiSTer to config.json. Name MiSTers by where they
are; --model says what each one is (a MiSTer Pi, a SuperStation One, a DE10-Nano), because
nothing on the network says so reliably. Then:
mcp-server-misterfpga doctorRelated MCP server: hass-mcp
Install
git clone https://github.com/SDNick484/mcp-server-misterfpga.git && cd mcp-server-misterfpga
python -m venv .venv && . .venv/bin/activate && pip install -e .Python 3.11 or newer. Every compiled dependency ships a musl wheel, so Alpine needs no compiler.
Use it from Claude
claude mcp add misterfpga -- /path/to/.venv/bin/mcp-server-misterfpga # Claude Code, stdioFor Claude Desktop, add it to claude_desktop_config.json under mcpServers with the same
command. This server is meant to stay on your LAN (see Local only).
Tools
Tool | Does | Annotations |
| each MiSTer: model, what's playing, running core, attract mode, library size, free space, capabilities and limitations | read-only |
| systems each MiSTer can launch, with game counts and the core that runs each | read-only |
| find games by name, optionally in one system; each result has a | read-only |
| recently played, or most played by total time, from Zaparoo's own records | read-only |
|
| read-only |
| NFC readers, card mappings, the last card scanned | read-only |
| a game by | idempotent |
| end what's running (unsaved progress is lost) | destructive |
| what the MiSTer shows, as an image | — |
| one | idempotent |
| saves and save states into a dated snapshot in your backup folder | idempotent |
| start Update All on the MiSTer's screen (it may reboot); needs | destructive |
| write a game onto the card on the reader (overwrites the card) | destructive |
| make a card's UID launch a game, without writing to it | idempotent |
A ref is <MiSTer>/<id>, e.g. Family Room/4812: the same game on two MiSTers has two
refs, and the ref decides which TV it launches on. Actions return
{"host", "outcome": "done" | "unchanged" | "dry_run", "detail", "warnings"}; failures are tool
errors that say what to do ("Family Room is running Super Metroid. Call return_to_menu ...",
"Which MiSTer should open the core? Pass host: Office, Family Room.").
Resource misterfpga://ini-keys: every key set_ini accepts, with type and range, and the
refused ones with the reason. Prompt make_nfc_cards: one card at a time, for a list of games.
What the model can't do
No paths, no ZapScript, no shell. Games launch by
ref; the server asks Zaparoo for that game's own launch script. A system launch is built from a validated system id. SSH runs five fixed commands, nothing else.SFTP stays in its lane: reads of
MiSTer*.iniandsaves/,savestates/; writes ofMiSTer*.ini(with a backup); deletes of this server's own old INI backups only.No Zaparoo settings, profiles, playtime limits, device power or Zaparoo updates: the methods it may send are an allow-list, pinned by a test.
set_inirefusesmain(another Main binary),bootcore,shared_folderanddebug, plus array keys (player_1_controller, ...); values are checked against MiSTer's own table.Never guess a MiSTer. An action that could mean either MiSTer is refused, naming both.
Never replace a running game.
launchrefuses; ending a game is its own call.Rate limits per MiSTer: launches and returns to the menu (4, then 1 per 15 s), screenshots, INI writes, NFC, and Update All (once an hour).
Dry run
serve --dry-run (or MISTERFPGA_DRY_RUN=1) reads the MiSTers but changes nothing: every action
returns outcome: "dry_run" with what it would do. set_ini and backup_saves also take a
per-call dry_run.
config.json
~/.config/mcp-server-misterfpga/config.json (or $MISTERFPGA_CONFIG_DIR); check writes it.
{
"hosts": [
{"host": "192.168.1.70", "name": "Office", "model": "MiSTer Pi",
"ssh": {"key": "~/.ssh/mister_ed25519", "host_key": "ssh-ed25519 AAAA..."}},
{"host": "192.168.1.71", "name": "Family Room", "model": "SuperStation One",
"manufacturer": "Retro Remake"}
],
"backup_dir": "/mnt/backups/mister-saves",
"backup_keep": 30
}ssh:key(no passwords are stored),host_key(pinned bycheck --ssh; a host without one is never connected to), optionaluser(root) andport(22).zaparoo_port: 7497 unless you changed Zaparoo'sapi_port.backup_dir(or$MISTERFPGA_BACKUP_DIR) has no default: any folder this server can write to (a local disk, NFS, SMB, an OpenMediaVault share mounted into the LXC). Until it's set,backup_savessays how to set it.MISTERFPGA_HOSTS(comma-separated addresses) replaces the list, keeping names from the file.
Backups
backup_saves reads /media/fat/saves and /media/fat/savestates over SFTP into
<backup_dir>/<host>/<date_time>/. Files unchanged since the last snapshot (same size and
modification time) are hard-linked to it, as rsync --link-dest does: every snapshot is
complete on its own, and only changes cost space. A snapshot is written as .partial and renamed
when complete. The newest backup_keep (30) are kept. The MiSTer is only read.
Local only: stdio, or HTTP on loopback
This server is meant to stay on your LAN. Unlike its AV siblings (Onkyo, Shield TV, Harmony, Sofabaton, webOS), it isn't published through a Cloudflare Tunnel, so it isn't reachable from claude.ai or the Claude mobile app. That's on purpose: it can start an update that reboots a MiSTer, and its SSH key is root on them.
There are two ways to run it:
stdio, the usual way: Claude Code or Claude Desktop launches it on a machine on your LAN.
serve --http: Streamable HTTP on127.0.0.1:8716/misterfpga/mcp, through theremote.pyit shares with its siblings (Onkyo 8711 … webOS 8715).deploy/alpine/runs this as a service. The loopback bind keeps it inside that machine or container; nothing else on the LAN can reach it.
Because serve --http runs on remote.py, this repo keeps mcp-server-shared's remote module.
Batocera and Pixelcade exclude it, but they only ever speak stdio. Here, "LAN-only" means no
tunnel route, not no HTTP.
On any address other than loopback, remote.py refuses to start without Cloudflare Access, unless
you pass --insecure-no-auth. Don't use that flag for this server. It exists for short tests
on a trusted LAN, and here it would give anything that can reach the port reboot-capable,
root-over-SSH control of your MiSTers. Its Host and Origin checks block DNS-rebinding from a
browser, not a direct request.
deploy/alpine/ installs it as an OpenRC service (in an Alpine LXC, for example), owned by its
own user, with the SSH key and config.json in /var/lib/mcp-server-misterfpga (0700):
sh deploy/alpine/install.sh # prints the next steps: key, ssh-copy-id, check, doctor, startLogs
stderr only (stdout is the MCP stdio transport). Addresses and MACs are redacted unless
--no-redact (MISTERFPGA_LOG_UNREDACTED=1); keys and tokens always are. -v adds protocol
traffic, including SSH.
Testing without a MiSTer
pip install -e ".[dev]" && pytest && ruff check . && ruff format --check . && mypy
mcp-server-misterfpga simulate # two simulated MiSTers; prints what to run next, e.g.:
MISTERFPGA_CONFIG_DIR=~/.config/mcp-server-misterfpga-sim/config mcp-server-misterfpga call launch game="Office/101"The simulator (src/misterfpga_mcp/sim/) runs, for each MiSTer, a fake Zaparoo Core speaking
JSON-RPC over a real WebSocket and a fake MiSTer SSH server (real SSH and SFTP, chrooted to a
folder standing in for the SD card), over one shared device state, so launching through Zaparoo
changes what /tmp/CORENAME says over SSH. The server's real clients connect to both. Two
MiSTers on 127.0.0.1 and 127.0.0.2: Office (a MiSTer Pi) and Family Room (a SuperStation One),
with Super Metroid on both on purpose.
doctor --dump DIR records a real MiSTer's read-only Zaparoo answers (redacted); put them in
tests/fixtures/recorded/ and test_recorded.py replays them through the tools.
Shared code
src/misterfpga_mcp/remote.py, logsafe.py, tasks.py, ratelimit.py, their tests and
scripts/sync_shared.py come from mcp-server-shared, pinned by shared.lock. Don't edit them
here: change them there, then python scripts/sync_shared.py --update. CI's shared-drift job
fails on a copy that differs.
What has been confirmed on hardware
Every protocol detail this server relies on is in src/misterfpga_mcp/assumptions.py, with its
source and how confident that source is. doctor prints the unconfirmed ones;
HARDWARE_VALIDATION.md says which step confirms each.
Assumption | Confidence | Status | Claim |
| high | simulator-only | Zaparoo's API: JSON-RPC 2.0 over |
| high | simulator-only | MiSTer admits unauthenticated LAN clients (no encryption by default) |
| high | simulator-only | such a client may launch, search, screenshot, use NFC; not device or update methods |
| high | simulator-only |
|
| medium | simulator-only |
|
| medium | simulator-only |
|
| medium | simulator-only | menu launches are tracked too (with |
| high | simulator-only |
|
| medium | simulator-only | play history includes menu launches |
| high | simulator-only |
|
| medium | simulator-only |
|
| low | simulator-only |
|
| high | simulator-only | how MiSTer matches |
| high | simulator-only | the key table, from Main_MiSTer |
| medium | simulator-only | INI changes apply at the next core load |
| low | simulator-only | write-then-rename works on the SD card over SFTP |
| high | simulator-only |
|
| high | simulator-only |
|
| medium | simulator-only | saves and save states locations |
| medium | simulator-only | root SSH with a key, and SFTP |
| medium | simulator-only | attract mode ⇔ SAM's |
| medium | simulator-only | picking up a controller in attract mode reloads the game |
| high | simulator-only | Update All may reboot when it finishes |
| low | simulator-only | the SuperStation One behaves like any MiSTer here |
License
MIT. Zaparoo Core, Main_MiSTer and mrext are GPL-3.0; this server uses their documented protocols and cites facts read from their source (such as the INI key table), and copies none of their code.
This server cannot be deployed
Maintenance
Related MCP Connectors
Operate Linux, macOS and Windows from your LLM. Every action runs through an auditable allowlist.
Work on your own computers from Claude or ChatGPT: run commands, edit and search files.
Drive real Android & iOS devices and web browsers from natural language for mobile + web QA. 290+ tools across device control, app management, automation sessions, browser automation, and flow recording / replay. Bearer-auth — get a token at robotactions.com → Profile → API Tokens.
Remote MCP server exposing your Node Command fleet (files, shell, processes, browser) to AI hosts.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceEnables AI assistants to control Commodore 64 Ultimate hardware via REST API, supporting program execution, memory operations, disk management, audio playback, and device configuration through natural language commands.22MIT
- AlicenseAqualityFmaintenanceEnables controlling and querying Home Assistant devices and services via natural language. Supports state retrieval, listing states, and calling any Home Assistant service.13138 npm5MIT
- AlicenseNot gradedqualityDmaintenanceEnables AI assistants to control Home Assistant via natural language, including device control, automation management, and system monitoring.MIT
- FlicenseNot gradedqualityCmaintenanceEnables natural-language querying and control of a Lyrion Music Server, including music library searches, album playback, player controls, and play-history tracking.1-