{
  "markdown": "<!-- mcp-name: io.github.sbrunner-atx/wsjtx-mcp -->\n\n# wsjtx-mcp\n\nAn [MCP](https://modelcontextprotocol.io) server that controls **WSJT-X**\n(FT8/FT4/JT65/MSK144/Q65/WSPR…) from MCP clients such as Claude Desktop and the\nMCP Inspector.\n\nIt is the weak-signal leg of an \"operate → log\" trio for amateur radio:\n\n- **[fldigi-mcp](https://github.com/sbrunner-atx/fldigi-mcp)** — operate broad\n  digital modes via fldigi (XML-RPC).\n- **[contest-mcp](https://github.com/sbrunner-atx/contest-mcp)** — log QSOs to\n  N3FJP (TCP API).\n- **wsjtx-mcp** *(this one)* — operate the FT8/FT4 weak-signal world via WSJT-X\n  (UDP message protocol).\n\n> ⚠️ **Experimental (v0.1).** Transmit is gated behind your callsign and you keep\n> the operator in command. Read the [Transmit safety](#transmit-safety) section.\n\n## How it is different\n\nWSJT-X does **not** offer a request/response API. It *broadcasts* state over UDP\n(`Status`, `Decode`, `QSOLogged`, `Heartbeat`, …) and honours a small set of\ninbound *control* messages. So this server runs a **background UDP listener** that\ncontinuously parses datagrams and keeps the latest status, a buffer of decodes,\nand completed QSOs — you read those, and nudge WSJT-X with control messages.\n\nConsequences worth knowing up front:\n\n- **No dial-frequency control over UDP.** You can read the dial frequency from\n  `Status`, and set mode/sub-mode/Rx DF/T-R period via `configure`, but **QSY is a\n  rig-control concern** (Hamlib/CAT or the UI), not this server.\n- **You can start and halt Tx, but cannot toggle \"Enable Tx\".** Transmission is\n  *started* by answering a CQ (`reply`) or by `free_text` with `send=true`, and\n  *stopped* by `transmit halt`. There is no UDP command for the \"Enable Tx\",\n  \"Auto Seq\", \"Call 1st\", or \"Hold Tx Freq\" checkboxes — those stay UI settings.\n- **`reply` gives hands-free QSOs only when WSJT-X's \"Auto Seq\" is on.** A `reply`\n  is equivalent to double-clicking a CQ; with **Auto Seq enabled** (the usual\n  FT8/FT4 default) WSJT-X then sequences the whole exchange to `QSOLogged` with no\n  further calls. With Auto Seq *off*, `reply` starts only the first transmission.\n- **Best for search-and-pounce, weak for RUN.** The API is built to *answer* CQs\n  (and only CQ/QRZ decodes), so S&P is fully automatable. It has no clean way to\n  drive a repeating *call-CQ* (RUN) cycle — that needs WSJT-X's own \"Enable Tx\",\n  or a `free_text` CQ re-sent each period.\n\n## Requirements\n\n- WSJT-X 2.1 through 3.x — the UDP message protocol is **schema 3 / Qt_5_4** and\n  has been stable across those releases (verified live against WSJT-X **3.0.2**).\n- In WSJT-X: **Settings → Reporting → UDP Server**\n  - **UDP Server** = the host running this server (default `127.0.0.1`), **port\n    `2237`**.\n  - **Accept UDP requests** = **ON** to allow *control* (it is OFF by default).\n    Observing decodes/status works without it; commanding does not.\n\n## Install (Claude Desktop)\n\nDownload the `wsjtx-mcp.mcpb` from the\n[latest release](https://github.com/sbrunner-atx/wsjtx-mcp/releases) and\ndouble-click it, or drag it onto Claude Desktop → Settings → Extensions. Fill in\nthe settings form (callsign, host, port). See [docs/INSTALL.md](docs/INSTALL.md).\n\n## Configuration\n\n| Variable | Default | Purpose |\n| --- | --- | --- |\n| `WSJTX_HOST` | `127.0.0.1` | UDP address to **bind/listen** on. |\n| `WSJTX_PORT` | `2237` | WSJT-X UDP Server port. |\n| `WSJTX_CALLSIGN` | _(empty)_ | Operator callsign — **the single transmit gate**. Blank = receive-only. |\n| `WSJTX_MULTICAST` | _(off)_ | Optional multicast group to join (coexist with other UDP consumers). |\n| `WSJTX_INSTANCE` | _(auto)_ | Target a specific WSJT-X `Id` when several instances broadcast. |\n\nHost/port are **where this server listens**; control replies are sent back to the\naddress each datagram arrived from.\n\n## Tools\n\n| Tool | Kind | What it does |\n| --- | --- | --- |\n| `status` | observe | Latest `Status` snapshot + listener/instance health. |\n| `diagnostics` | observe | Host/network + bind status + datagram counts + gate state. |\n| `decodes` | observe/nudge | `read` / `drain` (poll new) / `clear_local` / `replay`. The RX plane. |\n| `log` | observe | Buffered completed QSOs (`QSOLogged` + `LoggedADIF`) → feed N3FJP. |\n| `reply` | **transmit** | Answer a buffered CQ/QRZ decode (auto-sequences the QSO when WSJT-X \"Auto Seq\" is on). |\n| `free_text` | **transmit** if `send` | Set the Tx5 free-text message; `send=true` keys the radio. |\n| `transmit` | control | `halt` / `halt_auto` — stop transmitting (UDP can't *enable* Tx). |\n| `configure` | control | Mode/sub-mode/Rx DF/T-R period/freq-tol/DX call+grid. **No dial freq.** |\n| `clear` | control | Clear the Band Activity / Rx Frequency windows. |\n| `highlight` | control | Colour or clear a callsign in Band Activity. |\n| `location` | control | Override the session Maidenhead grid. |\n| `switch_config` | control | Switch to a named WSJT-X configuration. |\n| `annotate` | control | Set a Fox/Hound sort-order annotation for a DX call (niche, DXpedition). |\n| `wsjtx_call` | escape hatch | Build & send any message type by name (gate still applies). |\n\n## Transmit safety\n\nThe **callsign is the single transmit gate**, exactly as in fldigi-mcp. With\n`WSJTX_CALLSIGN` blank the server is **receive-only**: it refuses every\ntransmit-initiating message — `reply`, `free_text` with `send=true`, and any\nkeying message via `wsjtx_call`. `transmit halt`, `clear`, `configure`,\n`highlight`, `location`, `replay`, and all reads are always available (they don't\nput you on the air; halt takes you *off*).\n\nBeyond that gate:\n\n- Per-transmit approval comes from the Claude Desktop tool-permission prompt —\n  lean on it for human-in-the-loop control.\n- WSJT-X's own **Tx Watchdog** and the `Tx Enabled` / `Transmitting` flags\n  (surfaced in `status`) are extra safety signals.\n- Operating under **Part 97 automatic/remote control** is the operator's\n  responsibility: ensure station identification and a control operator who can\n  intervene.\n\n## Running alongside other UDP tools\n\nOnly one process can normally own UDP `2237` on a host. If JTAlert, GridTracker,\nor N1MM already consume it, either point WSJT-X's *secondary* UDP server here, use\na **multicast** group (`WSJTX_MULTICAST`) so several listeners coexist, or run\nthis server on a different host. To reach a WSJT-X on **another machine**, install\nthe [`mcp-host-bridge`](https://github.com/sbrunner-atx/mcp-host-bridge) tool\n(`mcp-host-bridge install wsjtx --to <rig-host>`) and set `WSJTX_PORT=2238` —\nsandboxed MCP clients reach only loopback, and the bridge does the LAN hop. See\n[docs/REMOTE-HOST.md](docs/REMOTE-HOST.md).\n\n## Development\n\n```sh\nuv sync\nuv run ruff check .\nuv run pytest\n```\n\nThe protocol codec is pure standard library and unit-tested against byte\nfixtures, so the tests need no running WSJT-X. A `smoke_test.py` proves a live\nWSJT-X is reachable receive-only. The field-tested message reference lives in\n[docs/WSJTX-API.md](docs/WSJTX-API.md).\n\n## License\n\nMIT © 2026 Stefan Brunner (AE5VG)\n",
  "bytes": 7028,
  "sha": "d35d1368b42e17613d8330eaa93f4ba8260146c3d86bec7a23798bdda7309b26",
  "repo_slug": "sbrunner-atx/wsjtx-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_sbrunner_atx_wsjtx_mcp_df723281/readme"
}