serial-console-mcp 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- serial_console_mcp-0.1.0/.gitignore +20 -0
- serial_console_mcp-0.1.0/BUILD.md +116 -0
- serial_console_mcp-0.1.0/CHANGELOG.md +35 -0
- serial_console_mcp-0.1.0/LICENSE +21 -0
- serial_console_mcp-0.1.0/PKG-INFO +200 -0
- serial_console_mcp-0.1.0/README.md +170 -0
- serial_console_mcp-0.1.0/build_macos.sh +59 -0
- serial_console_mcp-0.1.0/docs/serial-console-mcp Field Guide.pdf +0 -0
- serial_console_mcp-0.1.0/installer.iss +48 -0
- serial_console_mcp-0.1.0/packaging/entry.py +9 -0
- serial_console_mcp-0.1.0/pyproject.toml +82 -0
- serial_console_mcp-0.1.0/scripts/postinstall +21 -0
- serial_console_mcp-0.1.0/src/serial_console_mcp/__init__.py +10 -0
- serial_console_mcp-0.1.0/src/serial_console_mcp/__main__.py +5 -0
- serial_console_mcp-0.1.0/src/serial_console_mcp/configure.py +101 -0
- serial_console_mcp-0.1.0/src/serial_console_mcp/server.py +661 -0
- serial_console_mcp-0.1.0/tests/conftest.py +6 -0
- serial_console_mcp-0.1.0/tests/test_serial_console.py +416 -0
- serial_console_mcp-0.1.0/uninstall_macos.sh +13 -0
- serial_console_mcp-0.1.0/uv.lock +1241 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# BUILD — making the two installers
|
|
2
|
+
|
|
3
|
+
Goal: a single file the user double-clicks. No Python, no terminal, no JSON.
|
|
4
|
+
Everything here is wired up; you mostly just run it.
|
|
5
|
+
|
|
6
|
+
## Why not a `.mcpb`?
|
|
7
|
+
|
|
8
|
+
A `.mcpb` extension runs in Claude Desktop's sandbox — no LAN, no `/dev/cu.*`, no
|
|
9
|
+
COM ports. So it can't drive serial. The install therefore registers a **host
|
|
10
|
+
stdio subprocess** (the route that works for reaching real hardware), but hides
|
|
11
|
+
every manual step inside a clicker.
|
|
12
|
+
|
|
13
|
+
## One binary, two jobs
|
|
14
|
+
|
|
15
|
+
The `serial_console_mcp` package (entry script `packaging/entry.py`) is frozen
|
|
16
|
+
into a single executable:
|
|
17
|
+
- launched with **no args** -> runs the MCP stdio server (what Claude calls);
|
|
18
|
+
- launched as **`serial-console-mcp configure --command <self>`** -> writes the
|
|
19
|
+
`claude_desktop_config.json` entry and exits (what the installer calls).
|
|
20
|
+
|
|
21
|
+
The configurator merges into existing servers (won't touch your other MCP
|
|
22
|
+
entries) and backs up the old file first.
|
|
23
|
+
|
|
24
|
+
## The one non-obvious trap: elevated installer vs. per-user config
|
|
25
|
+
|
|
26
|
+
Installer postinstall steps run **elevated** (root on macOS, admin on Windows),
|
|
27
|
+
but Claude's config is per-user. Writing it from the elevated context lands in the
|
|
28
|
+
wrong home and silently does nothing. Both installers correct for this:
|
|
29
|
+
- **Windows:** the `[Run]` entry has `Flags: runasoriginaluser`. Inno does not allow
|
|
30
|
+
that flag in `[UninstallRun]`, so the uninstall-time `configure --remove` runs
|
|
31
|
+
elevated; it only reaches the right profile when the admin account is the user's
|
|
32
|
+
own (the usual single-user PC). Otherwise the stale entry is harmless: Claude
|
|
33
|
+
Desktop shows the server as unavailable until it is removed by hand.
|
|
34
|
+
- **macOS:** `scripts/postinstall` finds the console user and runs the configurator
|
|
35
|
+
as them via `launchctl asuser <uid> sudo -u <user> ... --config-home <home>`.
|
|
36
|
+
|
|
37
|
+
## Windows -> SerialConsoleMCP-Setup.exe
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
python -m venv .venv && .venv\Scripts\activate
|
|
41
|
+
pip install ".[freeze]"
|
|
42
|
+
pyinstaller --onefile --name serial-console-mcp --collect-all mcp --collect-all serial_console_mcp packaging/entry.py
|
|
43
|
+
```
|
|
44
|
+
Then compile `installer.iss` with Inno Setup (free). Output:
|
|
45
|
+
`Output\SerialConsoleMCP-Setup.exe`. Edit `AppPublisher` (your name/callsign) and,
|
|
46
|
+
if you have a code-signing cert, add a `SignTool` directive to avoid SmartScreen
|
|
47
|
+
warnings (optional; unsigned works, just warns).
|
|
48
|
+
|
|
49
|
+
## macOS -> SerialConsoleMCP-0.1.0.pkg
|
|
50
|
+
|
|
51
|
+
Unsigned (testing):
|
|
52
|
+
```
|
|
53
|
+
./build_macos.sh
|
|
54
|
+
```
|
|
55
|
+
Signed + notarized (on your Mac, with your Developer ID):
|
|
56
|
+
```
|
|
57
|
+
SIGN_IDENTITY_APP="Developer ID Application: Your Name (TEAMID)" \
|
|
58
|
+
SIGN_IDENTITY_INSTALLER="Developer ID Installer: Your Name (TEAMID)" \
|
|
59
|
+
NOTARY_PROFILE="AC" \
|
|
60
|
+
./build_macos.sh
|
|
61
|
+
```
|
|
62
|
+
`NOTARY_PROFILE` is a `notarytool store-credentials` keychain profile. The
|
|
63
|
+
package identifier is `org.stefanbrunner.serialconsolemcp` (override with
|
|
64
|
+
`IDENTIFIER=...`). Uninstall: `./uninstall_macos.sh`.
|
|
65
|
+
|
|
66
|
+
## PyPI
|
|
67
|
+
|
|
68
|
+
`.github/workflows/release.yml` publishes the sdist and wheel to PyPI with
|
|
69
|
+
Trusted Publishing (OIDC, no API token) whenever a GitHub Release is published.
|
|
70
|
+
The tag must equal `v` + the version in `pyproject.toml`; the workflow checks.
|
|
71
|
+
Release steps:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
# bump version in pyproject.toml and src/serial_console_mcp/__init__.py, update CHANGELOG.md
|
|
75
|
+
git tag vX.Y.Z && git push origin vX.Y.Z # builds the installers
|
|
76
|
+
gh release create vX.Y.Z --title "vX.Y.Z" --notes-file <(sed -n '/^## \[X.Y.Z\]/,/^## \[/p' CHANGELOG.md)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
One-time setup on pypi.org: Manage project → Publishing → add a trusted publisher
|
|
80
|
+
for `sbrunner-atx/serial-console-mcp`, workflow `release.yml`, environment `release`.
|
|
81
|
+
|
|
82
|
+
## CI (both at once)
|
|
83
|
+
|
|
84
|
+
`.github/workflows/build.yml` runs the pytest suite on Linux/macOS/Windows on
|
|
85
|
+
every push and pull request. On a `v*` tag (or manual dispatch) it additionally
|
|
86
|
+
freezes the binary and builds each installer, uploading them as artifacts. PyInstaller isn't a cross-compiler, so this is the easiest way to get
|
|
87
|
+
both from one `git tag`. The Mac job is **unsigned** in CI — for a notarized pkg,
|
|
88
|
+
run `build_macos.sh` locally with your Developer ID, since exporting certs into CI
|
|
89
|
+
is extra ceremony you don't need for a hobby tool.
|
|
90
|
+
|
|
91
|
+
## Things to personalize if you fork this
|
|
92
|
+
|
|
93
|
+
- `installer.iss`: `AppPublisher`, `AppId` (generate a new GUID), optional signing.
|
|
94
|
+
- `build_macos.sh` / `uninstall_macos.sh`: `IDENTIFIER`.
|
|
95
|
+
- The server and configurator need no edits.
|
|
96
|
+
|
|
97
|
+
## Testing against real hardware
|
|
98
|
+
|
|
99
|
+
`tests/` is the unit suite (fake serial port, runs in CI). `_hosttest/run_live.py`
|
|
100
|
+
is a developer harness that drives the real tool functions against a real port
|
|
101
|
+
from a JSON list of steps, without the MCP transport in the way. It ships a stub
|
|
102
|
+
`mcp` package so it also runs on a host Python older than 3.10; it sends only
|
|
103
|
+
what the step file lists.
|
|
104
|
+
|
|
105
|
+
## The config entry produced
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"mcpServers": {
|
|
110
|
+
"serial-console": {
|
|
111
|
+
"command": "/Library/Application Support/SerialConsoleMCP/serial-console-mcp",
|
|
112
|
+
"args": []
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0] - 2026-09-14
|
|
11
|
+
|
|
12
|
+
First public, experimental release.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- Eleven tools: `list_serial_ports`, `connect`, `reconnect_last`, `send_text`,
|
|
16
|
+
`send_hex`, `read_until_prompt`, `read_available`, `query_text`, `clear_buffer`,
|
|
17
|
+
`status`, `disconnect`.
|
|
18
|
+
- Interactive-console model: a background reader drains the port into a buffer
|
|
19
|
+
from the moment it opens; reads match a literal or regex prompt and push
|
|
20
|
+
leftover bytes back, or return once the line goes idle.
|
|
21
|
+
- Every Quick-Connect setting on `connect`: baud, data bits, parity, stop bits,
|
|
22
|
+
RTS/CTS and XON/XOFF. Default 9600 8N1, no flow control. Settings are
|
|
23
|
+
remembered for `reconnect_last`.
|
|
24
|
+
- Console script `serial-console-mcp`; `serial-console-mcp configure` writes the
|
|
25
|
+
Claude Desktop config entry (merging, with backup) and `--remove` undoes it.
|
|
26
|
+
- Windows (Inno Setup) and macOS (.pkg) installers built by CI on `v*` tags.
|
|
27
|
+
- The serial-console-mcp Field Guide (`docs/`).
|
|
28
|
+
- 37-case test suite on a simulated serial port; CI on Linux, macOS, Windows.
|
|
29
|
+
|
|
30
|
+
### Notes
|
|
31
|
+
- The MCP SDK is pinned `<2`; the 2.x line removed `mcp.server.fastmcp`.
|
|
32
|
+
- A dead reader (unplugged adapter) is reported by every tool instead of a
|
|
33
|
+
silent timeout; the receive buffer is capped at 4 MB.
|
|
34
|
+
- Verified with the simulated port and a live MCP stdio handshake. A run against
|
|
35
|
+
physical hardware is the next milestone.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Stefan Brunner (AE5VG)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: serial-console-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: An MCP server that lets Claude Desktop drive a serial console: network craft ports, CAT radios, Icom CI-V, rotators, microcontrollers.
|
|
5
|
+
Project-URL: Homepage, https://github.com/sbrunner-atx/serial-console-mcp
|
|
6
|
+
Project-URL: Repository, https://github.com/sbrunner-atx/serial-console-mcp
|
|
7
|
+
Project-URL: Issues, https://github.com/sbrunner-atx/serial-console-mcp/issues
|
|
8
|
+
Project-URL: Field Guide, https://github.com/sbrunner-atx/serial-console-mcp/blob/main/docs/serial-console-mcp%20Field%20Guide.pdf
|
|
9
|
+
Author-email: "Stefan Brunner (AE5VG)" <me@stefanbrunner.org>
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: cat,ci-v,console,craft-port,ham-radio,mcp,model-context-protocol,pyserial,serial,serial-port
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: System Administrators
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
+
Classifier: Topic :: Communications :: Ham Radio
|
|
21
|
+
Classifier: Topic :: System :: Hardware
|
|
22
|
+
Classifier: Topic :: Terminals :: Serial
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Requires-Dist: mcp<2,>=1.2.0
|
|
25
|
+
Requires-Dist: pyserial>=3.5
|
|
26
|
+
Provides-Extra: freeze
|
|
27
|
+
Requires-Dist: mcp[cli]<2,>=1.2.0; extra == 'freeze'
|
|
28
|
+
Requires-Dist: pyinstaller>=6.0; extra == 'freeze'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# serial-console-mcp
|
|
32
|
+
|
|
33
|
+
**Let Claude drive your serial console.**
|
|
34
|
+
|
|
35
|
+
[](https://github.com/sbrunner-atx/serial-console-mcp/actions/workflows/build.yml)
|
|
36
|
+
[](https://pypi.org/project/serial-console-mcp/)
|
|
37
|
+
MIT licensed · Python 3.10+ · **status: experimental (0.1.0)**
|
|
38
|
+
|
|
39
|
+
This adds a few tools to **Claude Desktop** so you can talk to anything on a serial
|
|
40
|
+
port — a network device's console/craft port (Juniper, Cisco, etc.), a radio,
|
|
41
|
+
rotator, amplifier, antenna switch, or a microcontroller — just by *asking Claude*.
|
|
42
|
+
|
|
43
|
+
You do **not** need to know any programming. After it's installed you talk to Claude
|
|
44
|
+
normally:
|
|
45
|
+
|
|
46
|
+
> **You:** What serial ports do you see?
|
|
47
|
+
> **Claude:** I found two. One looks like a Silicon Labs CP210x on COM4 — that's
|
|
48
|
+
> probably your device.
|
|
49
|
+
>
|
|
50
|
+
> **You:** Connect to COM4 at 9600 baud.
|
|
51
|
+
> **Claude:** Connected.
|
|
52
|
+
>
|
|
53
|
+
> **You:** Log in and show me the version.
|
|
54
|
+
|
|
55
|
+
## How it works (the important part)
|
|
56
|
+
|
|
57
|
+
A serial console isn't a simple question-and-answer channel. It echoes what you
|
|
58
|
+
type, prints unsolicited messages on its own (logs, interface flaps), and can dump
|
|
59
|
+
pages of output. So the moment a port is open, a background reader keeps draining
|
|
60
|
+
it into a buffer. That means Claude can:
|
|
61
|
+
|
|
62
|
+
- **send** a command (writes only — doesn't guess when the reply is done), then
|
|
63
|
+
- **read until a prompt** appears (`# `, `> `, `login:` …) to capture the whole
|
|
64
|
+
reply — even a long one — without cutting it off, or
|
|
65
|
+
- **read whatever's waiting** for streaming/unsolicited output.
|
|
66
|
+
|
|
67
|
+
This is the same model `minicom` and `expect` use, which is why it handles
|
|
68
|
+
interactive CLIs properly.
|
|
69
|
+
|
|
70
|
+
### The tools
|
|
71
|
+
|
|
72
|
+
| Tool | What it does |
|
|
73
|
+
| --- | --- |
|
|
74
|
+
| `list_serial_ports` | Enumerate ports with description and USB hardware id |
|
|
75
|
+
| `connect` / `reconnect_last` / `disconnect` | Open a port. Defaults to 9600 8N1, no flow control; baud, data bits, parity, stop bits, RTS/CTS and XON/XOFF are all settable by asking. Remembers the last one |
|
|
76
|
+
| `send_text` | Write an ASCII command with CR / LF / CRLF / no line ending. Write-only |
|
|
77
|
+
| `send_hex` | Write raw bytes given as hex (Icom CI-V and other binary protocols) |
|
|
78
|
+
| `read_until_prompt` | Return buffered output up to a literal or regex prompt, leaving the rest |
|
|
79
|
+
| `read_available` | Return whatever has arrived, as text and hex |
|
|
80
|
+
| `query_text` | Clear, send, then read until a prompt or until the line goes idle |
|
|
81
|
+
| `clear_buffer` / `status` | Housekeeping |
|
|
82
|
+
|
|
83
|
+
One port is open at a time. The receive buffer is capped at 4 MB; if a device
|
|
84
|
+
streams for hours unread, the oldest bytes are dropped and `status` says how many.
|
|
85
|
+
|
|
86
|
+
## The Field Guide
|
|
87
|
+
|
|
88
|
+
[serial-console-mcp Field Guide (PDF)](docs/serial-console-mcp%20Field%20Guide.pdf) is the
|
|
89
|
+
operator's manual: what each tool does, every connection setting said in plain
|
|
90
|
+
language, the console rules, a per-device playbook (craft ports, text CAT,
|
|
91
|
+
Icom CI-V, rotators and microcontrollers), four worked sessions, and a
|
|
92
|
+
troubleshooting table. Source is `docs/brand/` (HTML + CSS, rendered with
|
|
93
|
+
WeasyPrint).
|
|
94
|
+
|
|
95
|
+
## Installing
|
|
96
|
+
|
|
97
|
+
1. Download the installer for your computer from the
|
|
98
|
+
[Releases page](https://github.com/sbrunner-atx/serial-console-mcp/releases)
|
|
99
|
+
(the `.exe` on Windows, or the `.pkg` on a Mac) and click through it like any
|
|
100
|
+
normal program. It sets everything up for you. The installers are unsigned for
|
|
101
|
+
now, so expect a Gatekeeper / SmartScreen warning.
|
|
102
|
+
2. **Completely quit Claude Desktop** — not just closing the window. On Windows,
|
|
103
|
+
right-click the Claude icon near the clock and choose Quit. On a Mac, press
|
|
104
|
+
⌘Q or choose **Claude → Quit**.
|
|
105
|
+
3. Open Claude Desktop again.
|
|
106
|
+
4. In a new chat, type: **"What serial ports do you see?"** If Claude lists your
|
|
107
|
+
ports, you're done.
|
|
108
|
+
|
|
109
|
+
That's the whole thing. There's no separate program to keep open and nothing to
|
|
110
|
+
configure by hand.
|
|
111
|
+
|
|
112
|
+
## Installing from PyPI
|
|
113
|
+
|
|
114
|
+
If you already have Python 3.10+ and [uv](https://docs.astral.sh/uv/) or pipx,
|
|
115
|
+
you don't need the installer:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
uvx serial-console-mcp --version # fetches and runs it
|
|
119
|
+
uvx serial-console-mcp configure --command uvx --arg serial-console-mcp
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
or
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
pipx install serial-console-mcp
|
|
126
|
+
serial-console-mcp configure --command "$(which serial-console-mcp)"
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`configure` writes a `serial-console` entry into `claude_desktop_config.json`
|
|
130
|
+
(merging with whatever is already there and backing the old file up first).
|
|
131
|
+
Quit and reopen Claude Desktop. `serial-console-mcp configure --remove` undoes
|
|
132
|
+
it. Any other MCP client can launch the same command over stdio.
|
|
133
|
+
|
|
134
|
+
## Installing from source (developers)
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
git clone https://github.com/sbrunner-atx/serial-console-mcp.git
|
|
138
|
+
cd serial-console-mcp
|
|
139
|
+
uv sync # or: python3 -m venv .venv && . .venv/bin/activate && pip install -e . pytest
|
|
140
|
+
uv run pytest # fake serial port, no hardware needed
|
|
141
|
+
uv run serial-console-mcp configure --command "$PWD/.venv/bin/serial-console-mcp"
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The package lives in `src/serial_console_mcp/`: `server.py` is the MCP server,
|
|
145
|
+
`configure.py` the Claude Desktop registrar. See [BUILD.md](BUILD.md) for the
|
|
146
|
+
installers.
|
|
147
|
+
|
|
148
|
+
## Using it
|
|
149
|
+
|
|
150
|
+
Plain-English requests work. Some examples:
|
|
151
|
+
|
|
152
|
+
- "List my serial ports."
|
|
153
|
+
- "Connect to the console on /dev/cu.usbserial-10 at 9600 baud."
|
|
154
|
+
- "Connect to /dev/cu.BLTH at 38400, 8 data bits, no parity, 1 stop bit, XON/XOFF flow control."
|
|
155
|
+
- "Reconnect to the same port as last time." (it remembers)
|
|
156
|
+
- "Send a return, then read until the login prompt."
|
|
157
|
+
- "Log in as admin and run `show interfaces terse`, then show me all of it."
|
|
158
|
+
- "Just read whatever the device is printing right now."
|
|
159
|
+
- "Disconnect when you're done."
|
|
160
|
+
|
|
161
|
+
For a router/switch console, tell Claude the prompt it should wait for (often `# `
|
|
162
|
+
for enable mode or `> ` for user mode) and it will read until it sees it. For an
|
|
163
|
+
Icom radio (CI-V), tell Claude — it can send the hex commands those radios expect.
|
|
164
|
+
|
|
165
|
+
## If something doesn't work
|
|
166
|
+
|
|
167
|
+
**"No serial ports found."**
|
|
168
|
+
- Is the device turned on?
|
|
169
|
+
- Is the USB cable a real *data* cable, not a charge-only one? (A very common gotcha.)
|
|
170
|
+
- On Windows, open Device Manager and look under **Ports (COM & LPT)**. If nothing's
|
|
171
|
+
there, Windows needs the cable's driver (often FTDI, CP210x, or CH340).
|
|
172
|
+
|
|
173
|
+
**"Could not open the port" / "access denied."**
|
|
174
|
+
- A serial port can only be used by one program at a time. Close anything else that
|
|
175
|
+
might be holding it: a terminal (PuTTY/minicom/screen), WSJT-X, your contest
|
|
176
|
+
logger, the device's own software.
|
|
177
|
+
|
|
178
|
+
**Claude says it can't access serial ports at all.**
|
|
179
|
+
- Make sure you fully quit and reopened Claude Desktop after installing.
|
|
180
|
+
- Start a brand-new chat and ask "What serial ports do you see?" again.
|
|
181
|
+
|
|
182
|
+
**It connected but a command gets no reply.**
|
|
183
|
+
- Almost always the **baud rate** is wrong, or the **line ending** is wrong for your
|
|
184
|
+
gear. Most Unix-style consoles want a plain newline (LF); most rigs want a carriage
|
|
185
|
+
return (CR). Ask Claude to send a return first to draw a fresh prompt.
|
|
186
|
+
|
|
187
|
+
## A word on safety
|
|
188
|
+
|
|
189
|
+
These tools send exactly what you (through Claude) ask them to send, to whatever
|
|
190
|
+
device is on the cable. Claude Desktop asks you to approve each tool call, so you
|
|
191
|
+
see every command before it runs. Read it. A console session on a router or a
|
|
192
|
+
rig can reconfigure, reboot, or transmit, and this server does not try to guess
|
|
193
|
+
which commands are dangerous. If something looks wrong, decline it, and keep a
|
|
194
|
+
real terminal handy for anything you would not want an assistant to type.
|
|
195
|
+
|
|
196
|
+
## License
|
|
197
|
+
|
|
198
|
+
MIT. See [LICENSE](LICENSE).
|
|
199
|
+
|
|
200
|
+
73!
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# serial-console-mcp
|
|
2
|
+
|
|
3
|
+
**Let Claude drive your serial console.**
|
|
4
|
+
|
|
5
|
+
[](https://github.com/sbrunner-atx/serial-console-mcp/actions/workflows/build.yml)
|
|
6
|
+
[](https://pypi.org/project/serial-console-mcp/)
|
|
7
|
+
MIT licensed · Python 3.10+ · **status: experimental (0.1.0)**
|
|
8
|
+
|
|
9
|
+
This adds a few tools to **Claude Desktop** so you can talk to anything on a serial
|
|
10
|
+
port — a network device's console/craft port (Juniper, Cisco, etc.), a radio,
|
|
11
|
+
rotator, amplifier, antenna switch, or a microcontroller — just by *asking Claude*.
|
|
12
|
+
|
|
13
|
+
You do **not** need to know any programming. After it's installed you talk to Claude
|
|
14
|
+
normally:
|
|
15
|
+
|
|
16
|
+
> **You:** What serial ports do you see?
|
|
17
|
+
> **Claude:** I found two. One looks like a Silicon Labs CP210x on COM4 — that's
|
|
18
|
+
> probably your device.
|
|
19
|
+
>
|
|
20
|
+
> **You:** Connect to COM4 at 9600 baud.
|
|
21
|
+
> **Claude:** Connected.
|
|
22
|
+
>
|
|
23
|
+
> **You:** Log in and show me the version.
|
|
24
|
+
|
|
25
|
+
## How it works (the important part)
|
|
26
|
+
|
|
27
|
+
A serial console isn't a simple question-and-answer channel. It echoes what you
|
|
28
|
+
type, prints unsolicited messages on its own (logs, interface flaps), and can dump
|
|
29
|
+
pages of output. So the moment a port is open, a background reader keeps draining
|
|
30
|
+
it into a buffer. That means Claude can:
|
|
31
|
+
|
|
32
|
+
- **send** a command (writes only — doesn't guess when the reply is done), then
|
|
33
|
+
- **read until a prompt** appears (`# `, `> `, `login:` …) to capture the whole
|
|
34
|
+
reply — even a long one — without cutting it off, or
|
|
35
|
+
- **read whatever's waiting** for streaming/unsolicited output.
|
|
36
|
+
|
|
37
|
+
This is the same model `minicom` and `expect` use, which is why it handles
|
|
38
|
+
interactive CLIs properly.
|
|
39
|
+
|
|
40
|
+
### The tools
|
|
41
|
+
|
|
42
|
+
| Tool | What it does |
|
|
43
|
+
| --- | --- |
|
|
44
|
+
| `list_serial_ports` | Enumerate ports with description and USB hardware id |
|
|
45
|
+
| `connect` / `reconnect_last` / `disconnect` | Open a port. Defaults to 9600 8N1, no flow control; baud, data bits, parity, stop bits, RTS/CTS and XON/XOFF are all settable by asking. Remembers the last one |
|
|
46
|
+
| `send_text` | Write an ASCII command with CR / LF / CRLF / no line ending. Write-only |
|
|
47
|
+
| `send_hex` | Write raw bytes given as hex (Icom CI-V and other binary protocols) |
|
|
48
|
+
| `read_until_prompt` | Return buffered output up to a literal or regex prompt, leaving the rest |
|
|
49
|
+
| `read_available` | Return whatever has arrived, as text and hex |
|
|
50
|
+
| `query_text` | Clear, send, then read until a prompt or until the line goes idle |
|
|
51
|
+
| `clear_buffer` / `status` | Housekeeping |
|
|
52
|
+
|
|
53
|
+
One port is open at a time. The receive buffer is capped at 4 MB; if a device
|
|
54
|
+
streams for hours unread, the oldest bytes are dropped and `status` says how many.
|
|
55
|
+
|
|
56
|
+
## The Field Guide
|
|
57
|
+
|
|
58
|
+
[serial-console-mcp Field Guide (PDF)](docs/serial-console-mcp%20Field%20Guide.pdf) is the
|
|
59
|
+
operator's manual: what each tool does, every connection setting said in plain
|
|
60
|
+
language, the console rules, a per-device playbook (craft ports, text CAT,
|
|
61
|
+
Icom CI-V, rotators and microcontrollers), four worked sessions, and a
|
|
62
|
+
troubleshooting table. Source is `docs/brand/` (HTML + CSS, rendered with
|
|
63
|
+
WeasyPrint).
|
|
64
|
+
|
|
65
|
+
## Installing
|
|
66
|
+
|
|
67
|
+
1. Download the installer for your computer from the
|
|
68
|
+
[Releases page](https://github.com/sbrunner-atx/serial-console-mcp/releases)
|
|
69
|
+
(the `.exe` on Windows, or the `.pkg` on a Mac) and click through it like any
|
|
70
|
+
normal program. It sets everything up for you. The installers are unsigned for
|
|
71
|
+
now, so expect a Gatekeeper / SmartScreen warning.
|
|
72
|
+
2. **Completely quit Claude Desktop** — not just closing the window. On Windows,
|
|
73
|
+
right-click the Claude icon near the clock and choose Quit. On a Mac, press
|
|
74
|
+
⌘Q or choose **Claude → Quit**.
|
|
75
|
+
3. Open Claude Desktop again.
|
|
76
|
+
4. In a new chat, type: **"What serial ports do you see?"** If Claude lists your
|
|
77
|
+
ports, you're done.
|
|
78
|
+
|
|
79
|
+
That's the whole thing. There's no separate program to keep open and nothing to
|
|
80
|
+
configure by hand.
|
|
81
|
+
|
|
82
|
+
## Installing from PyPI
|
|
83
|
+
|
|
84
|
+
If you already have Python 3.10+ and [uv](https://docs.astral.sh/uv/) or pipx,
|
|
85
|
+
you don't need the installer:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
uvx serial-console-mcp --version # fetches and runs it
|
|
89
|
+
uvx serial-console-mcp configure --command uvx --arg serial-console-mcp
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
or
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
pipx install serial-console-mcp
|
|
96
|
+
serial-console-mcp configure --command "$(which serial-console-mcp)"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`configure` writes a `serial-console` entry into `claude_desktop_config.json`
|
|
100
|
+
(merging with whatever is already there and backing the old file up first).
|
|
101
|
+
Quit and reopen Claude Desktop. `serial-console-mcp configure --remove` undoes
|
|
102
|
+
it. Any other MCP client can launch the same command over stdio.
|
|
103
|
+
|
|
104
|
+
## Installing from source (developers)
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
git clone https://github.com/sbrunner-atx/serial-console-mcp.git
|
|
108
|
+
cd serial-console-mcp
|
|
109
|
+
uv sync # or: python3 -m venv .venv && . .venv/bin/activate && pip install -e . pytest
|
|
110
|
+
uv run pytest # fake serial port, no hardware needed
|
|
111
|
+
uv run serial-console-mcp configure --command "$PWD/.venv/bin/serial-console-mcp"
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The package lives in `src/serial_console_mcp/`: `server.py` is the MCP server,
|
|
115
|
+
`configure.py` the Claude Desktop registrar. See [BUILD.md](BUILD.md) for the
|
|
116
|
+
installers.
|
|
117
|
+
|
|
118
|
+
## Using it
|
|
119
|
+
|
|
120
|
+
Plain-English requests work. Some examples:
|
|
121
|
+
|
|
122
|
+
- "List my serial ports."
|
|
123
|
+
- "Connect to the console on /dev/cu.usbserial-10 at 9600 baud."
|
|
124
|
+
- "Connect to /dev/cu.BLTH at 38400, 8 data bits, no parity, 1 stop bit, XON/XOFF flow control."
|
|
125
|
+
- "Reconnect to the same port as last time." (it remembers)
|
|
126
|
+
- "Send a return, then read until the login prompt."
|
|
127
|
+
- "Log in as admin and run `show interfaces terse`, then show me all of it."
|
|
128
|
+
- "Just read whatever the device is printing right now."
|
|
129
|
+
- "Disconnect when you're done."
|
|
130
|
+
|
|
131
|
+
For a router/switch console, tell Claude the prompt it should wait for (often `# `
|
|
132
|
+
for enable mode or `> ` for user mode) and it will read until it sees it. For an
|
|
133
|
+
Icom radio (CI-V), tell Claude — it can send the hex commands those radios expect.
|
|
134
|
+
|
|
135
|
+
## If something doesn't work
|
|
136
|
+
|
|
137
|
+
**"No serial ports found."**
|
|
138
|
+
- Is the device turned on?
|
|
139
|
+
- Is the USB cable a real *data* cable, not a charge-only one? (A very common gotcha.)
|
|
140
|
+
- On Windows, open Device Manager and look under **Ports (COM & LPT)**. If nothing's
|
|
141
|
+
there, Windows needs the cable's driver (often FTDI, CP210x, or CH340).
|
|
142
|
+
|
|
143
|
+
**"Could not open the port" / "access denied."**
|
|
144
|
+
- A serial port can only be used by one program at a time. Close anything else that
|
|
145
|
+
might be holding it: a terminal (PuTTY/minicom/screen), WSJT-X, your contest
|
|
146
|
+
logger, the device's own software.
|
|
147
|
+
|
|
148
|
+
**Claude says it can't access serial ports at all.**
|
|
149
|
+
- Make sure you fully quit and reopened Claude Desktop after installing.
|
|
150
|
+
- Start a brand-new chat and ask "What serial ports do you see?" again.
|
|
151
|
+
|
|
152
|
+
**It connected but a command gets no reply.**
|
|
153
|
+
- Almost always the **baud rate** is wrong, or the **line ending** is wrong for your
|
|
154
|
+
gear. Most Unix-style consoles want a plain newline (LF); most rigs want a carriage
|
|
155
|
+
return (CR). Ask Claude to send a return first to draw a fresh prompt.
|
|
156
|
+
|
|
157
|
+
## A word on safety
|
|
158
|
+
|
|
159
|
+
These tools send exactly what you (through Claude) ask them to send, to whatever
|
|
160
|
+
device is on the cable. Claude Desktop asks you to approve each tool call, so you
|
|
161
|
+
see every command before it runs. Read it. A console session on a router or a
|
|
162
|
+
rig can reconfigure, reboot, or transmit, and this server does not try to guess
|
|
163
|
+
which commands are dangerous. If something looks wrong, decline it, and keep a
|
|
164
|
+
real terminal handy for anything you would not want an assistant to type.
|
|
165
|
+
|
|
166
|
+
## License
|
|
167
|
+
|
|
168
|
+
MIT. See [LICENSE](LICENSE).
|
|
169
|
+
|
|
170
|
+
73!
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Build a (optionally signed + notarized) macOS .pkg.
|
|
3
|
+
#
|
|
4
|
+
# Unsigned local/CI build: ./build_macos.sh
|
|
5
|
+
# Signed + notarized build (on your Mac with your Developer ID):
|
|
6
|
+
# SIGN_IDENTITY_APP="Developer ID Application: Your Name (TEAMID)" \
|
|
7
|
+
# SIGN_IDENTITY_INSTALLER="Developer ID Installer: Your Name (TEAMID)" \
|
|
8
|
+
# NOTARY_PROFILE="AC" ./build_macos.sh
|
|
9
|
+
set -euo pipefail
|
|
10
|
+
|
|
11
|
+
APP_NAME="serial-console-mcp"
|
|
12
|
+
VERSION="${VERSION:-0.1.0}"
|
|
13
|
+
IDENTIFIER="${IDENTIFIER:-org.stefanbrunner.serialconsolemcp}"
|
|
14
|
+
INSTALL_LOCATION="/Library/Application Support/SerialConsoleMCP"
|
|
15
|
+
|
|
16
|
+
python3 -m venv .venv
|
|
17
|
+
# shellcheck disable=SC1091
|
|
18
|
+
source .venv/bin/activate
|
|
19
|
+
pip install --quiet ".[freeze]"
|
|
20
|
+
pyinstaller --onefile --name "$APP_NAME" --collect-all mcp --collect-all serial_console_mcp packaging/entry.py
|
|
21
|
+
|
|
22
|
+
# Sign the Mach-O with hardened runtime (required for notarization).
|
|
23
|
+
if [ -n "${SIGN_IDENTITY_APP:-}" ]; then
|
|
24
|
+
codesign --force --options runtime --timestamp \
|
|
25
|
+
--sign "$SIGN_IDENTITY_APP" "dist/$APP_NAME"
|
|
26
|
+
else
|
|
27
|
+
echo "WARN: SIGN_IDENTITY_APP not set -> unsigned binary (fine for testing)."
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
rm -rf pkgroot
|
|
31
|
+
mkdir -p "pkgroot${INSTALL_LOCATION}"
|
|
32
|
+
cp "dist/$APP_NAME" "pkgroot${INSTALL_LOCATION}/"
|
|
33
|
+
chmod +x "pkgroot${INSTALL_LOCATION}/$APP_NAME"
|
|
34
|
+
chmod +x scripts/postinstall
|
|
35
|
+
|
|
36
|
+
pkgbuild --root pkgroot \
|
|
37
|
+
--identifier "$IDENTIFIER" \
|
|
38
|
+
--version "$VERSION" \
|
|
39
|
+
--scripts scripts \
|
|
40
|
+
--install-location "/" \
|
|
41
|
+
"SerialConsoleMCP-component.pkg"
|
|
42
|
+
|
|
43
|
+
productbuild --package "SerialConsoleMCP-component.pkg" "SerialConsoleMCP-$VERSION.pkg"
|
|
44
|
+
rm -f "SerialConsoleMCP-component.pkg" # intermediate; keep only the product pkg
|
|
45
|
+
|
|
46
|
+
if [ -n "${SIGN_IDENTITY_INSTALLER:-}" ]; then
|
|
47
|
+
productsign --sign "$SIGN_IDENTITY_INSTALLER" \
|
|
48
|
+
"SerialConsoleMCP-$VERSION.pkg" "SerialConsoleMCP-$VERSION-signed.pkg"
|
|
49
|
+
mv "SerialConsoleMCP-$VERSION-signed.pkg" "SerialConsoleMCP-$VERSION.pkg"
|
|
50
|
+
if [ -n "${NOTARY_PROFILE:-}" ]; then
|
|
51
|
+
xcrun notarytool submit "SerialConsoleMCP-$VERSION.pkg" \
|
|
52
|
+
--keychain-profile "$NOTARY_PROFILE" --wait
|
|
53
|
+
xcrun stapler staple "SerialConsoleMCP-$VERSION.pkg"
|
|
54
|
+
fi
|
|
55
|
+
else
|
|
56
|
+
echo "WARN: SIGN_IDENTITY_INSTALLER not set -> unsigned .pkg (Gatekeeper will warn)."
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
echo "Built SerialConsoleMCP-$VERSION.pkg"
|
|
Binary file
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
; Inno Setup script for Serial Console MCP (Windows)
|
|
2
|
+
; Build the binary first: pip install ".[freeze]" && pyinstaller --onefile --name serial-console-mcp --collect-all mcp --collect-all serial_console_mcp packaging/entry.py
|
|
3
|
+
; Then compile this with Inno Setup -> Output\SerialConsoleMCP-Setup.exe
|
|
4
|
+
|
|
5
|
+
#define MyAppName "Serial Console MCP"
|
|
6
|
+
#define MyAppVersion "0.1.0"
|
|
7
|
+
#define MyAppPublisher "Stefan Brunner (AE5VG)"
|
|
8
|
+
|
|
9
|
+
[Setup]
|
|
10
|
+
AppId={{B3F1A2C4-7E5D-4A19-9C2B-1D6E8F0A4C77}
|
|
11
|
+
AppName={#MyAppName}
|
|
12
|
+
AppVersion={#MyAppVersion}
|
|
13
|
+
AppPublisher={#MyAppPublisher}
|
|
14
|
+
DefaultDirName={autopf}\SerialConsoleMCP
|
|
15
|
+
DisableProgramGroupPage=yes
|
|
16
|
+
DisableDirPage=yes
|
|
17
|
+
OutputDir=Output
|
|
18
|
+
OutputBaseFilename=SerialConsoleMCP-Setup
|
|
19
|
+
Compression=lzma2
|
|
20
|
+
SolidCompression=yes
|
|
21
|
+
PrivilegesRequired=admin
|
|
22
|
+
ArchitecturesInstallIn64BitMode=x64compatible
|
|
23
|
+
WizardStyle=modern
|
|
24
|
+
|
|
25
|
+
[Files]
|
|
26
|
+
Source: "dist\serial-console-mcp.exe"; DestDir: "{app}"; Flags: ignoreversion
|
|
27
|
+
|
|
28
|
+
[Run]
|
|
29
|
+
; Register with Claude Desktop AS THE REAL USER (runasoriginaluser), so %APPDATA%
|
|
30
|
+
; resolves to the logged-in user's profile rather than the elevated admin's.
|
|
31
|
+
Filename: "{app}\serial-console-mcp.exe"; \
|
|
32
|
+
Parameters: "configure --command ""{app}\serial-console-mcp.exe"""; \
|
|
33
|
+
Flags: runhidden runasoriginaluser; \
|
|
34
|
+
StatusMsg: "Registering with Claude Desktop..."
|
|
35
|
+
|
|
36
|
+
[UninstallRun]
|
|
37
|
+
; Runs before files are removed, so the exe still exists here. Inno does not
|
|
38
|
+
; allow runasoriginaluser in this section, so this runs in the uninstaller's
|
|
39
|
+
; (elevated) context: it cleans the entry when the admin and the user are the
|
|
40
|
+
; same account, which is the common single-user case. Otherwise Claude Desktop
|
|
41
|
+
; simply shows the server as unavailable until the entry is removed by hand.
|
|
42
|
+
Filename: "{app}\serial-console-mcp.exe"; \
|
|
43
|
+
Parameters: "configure --remove"; \
|
|
44
|
+
Flags: runhidden; \
|
|
45
|
+
RunOnceId: "RemoveClaudeEntry"
|
|
46
|
+
|
|
47
|
+
[Messages]
|
|
48
|
+
FinishedLabel=Setup is done. Now FULLY QUIT Claude Desktop (right-click the tray icon, Quit) and reopen it. Then ask Claude: "What serial ports do you see?"
|