Skip to main content
Glama
loop-killer

NAS Serial Console MCP Server

by loop-killer

NAS Serial Console MCP Server

通过 Model Context Protocol (MCP) 远程接管 NAS 物理串口控制台 —— 一条在设备彻底失联时仍可用的应急通道。

Node.js MCP Cloudflare


问题背景

NAS 设备在系统崩溃、内核 panic 或网络配置错误时会彻底失联——SSH 连不上、Web 管理界面打不开、ping 不通。此时唯一的救援通道是物理串口控制台,但维护者往往不在设备旁边。

更麻烦的是,接入路径上的软路由不允许安装任何额外软件(零安装约束),常规的 VPN / 反向代理方案全部不可用。

目标:在软路由零改动的前提下,建立一条可从公网安全接入的串口应急通道,并把它封装成 LLM 智能体可以直接调用的工具——让 AI 能在无人值守时完成救援。


Related MCP server: serial-mcp

架构

┌─────────────────┐
│  MCP Server     │  本地 PC(不受软路由零安装约束)
│  (Node.js)      │
└────────┬────────┘
         │ wss://your-tunnel.example.com/ws
         │ + CF-Access-Client-Id / CF-Access-Client-Secret
         ▼
┌─────────────────┐
│ Cloudflare      │  Service Token 机器身份认证(唯一门禁)
│ Access + Tunnel │
└────────┬────────┘
         ▼
┌─────────────────┐
│  ttyd           │  软路由本地 127.0.0.1:7682(WebSocket 终端)
│  (软路由)        │  ⚠️ 零安装:仅使用软路由自带组件
└────────┬────────┘
         ▼
┌─────────────────┐
│ serial-bridge   │  /dev/ttyS1
│                 │  ── 母对母交叉线 ──
└────────┬────────┘
         ▼
┌─────────────────┐
│  NAS ttyS0      │  115200 8N1
└─────────────────┘

设计要点

决策

理由

MCP Server 跑在主 PC 而非软路由

绕开"软路由零安装"硬约束,同时获得完整的 Node.js 运行时

使用 Cloudflare Service Token 而非用户密码

机器身份认证,无交互、可轮换、可吊销;ttyd 本地认证已移除,CF Access 成为唯一门禁

全程 WebSocket 二进制帧

ttyd 1.7.3 的协议要求(见下方「协议逆向」)

串口连接独立于 NAS 生命周期

NAS 重启不打断桥接,仅需 serial_reset 同步状态


协议逆向(ttyd 1.7.3)

ttyd-client.js 中的协议实现经活体实例抓包验证,与 ttyd 官方前端源码一致:

  1. GET /token{"token":"..."} (ttyd 1.7.3 返回的实为 -c 凭据本身;未配置 -c 时为空串,WS 侧 AuthToken 校验同步失效)

  2. 连接 ws(s)://host/ws,子协议 tty,请求头携带:

    • CF-Access-Client-Id / CF-Access-Client-Secret(经 Cloudflare Access)

    • Authorization: Basic base64(user:pass)(ttyd 本地认证)

  3. 连接建立后,发送二进制帧初始化 JSON:{"AuthToken":"<token>","columns":120,"rows":40}

  4. 服务端二进制帧首字节为类型判别符:

    • '0' (0x30) = 输出数据

    • '1' (0x31) = 设置标题

    • '2' (0x32) = 应用偏好(JSON)

  5. 客户端输入:二进制帧 = '0' (0x30) + 原始输入字节

⚠️ 关键坑:初始化与输入都必须用二进制帧TextEncoder 编码)。用文本帧发送输入会被 ttyd 静默忽略——这是本项目调试耗时最久的问题。


兼容性处理

逐字符节流(charThrottleMs

旧版 ash shell 的串口行处理存在字符丢失缺陷,整帧直发会导致命令截断。解决方案是逐字符节流发送,模拟人工输入速率。

桥接实现

推荐值

说明

tio(现役)

0

整帧直发,性能最优

旧版 ash

10–20

逐字符节流,规避字符丢失

BREAK 信号字节序列(breakBytesHex

不同桥接实现对 BREAK 的定义不同,需按实现配置:

桥接实现

含义

tio(现役)

1462

Ctrl-T b

旧版 ash

1b9c1b9c

历史魔数

BREAK 用于 NAS 完全卡死(console 无响应)时触发内核 Magic SysRq,随后在 5 秒窗口内发送 REISUB 序列,完成「杀进程 → 落盘 → 只读重挂 → 重启」。


快速开始

npm install
cp config.example.json config.json
# 编辑 config.json,填入 Cloudflare Service Token

配置项

字段

说明

baseUrl

隧道公网入口(如 https://your-tunnel.example.com),或内网测试用 http://127.0.0.1:7682

cfClientId / cfClientSecret

Cloudflare Access Service Token(CF 后台生成)

columns / rows

终端尺寸,默认 120x40

charThrottleMs

逐字符节流间隔;tio 桥设 0

breakBytesHex

BREAK 键字节序列(hex)

username / password

ttyd 本地认证,可留空(CF Access 为唯一门禁)

nasUsername / nasPassword

(可选)NAS 登录凭据,供 Agent 救急时自动登录

内网测试

若软路由可达但 Cloudflare 未配好,用 SSH 端口转发临时映射:

ssh -L 7682:127.0.0.1:7682 root@<软路由IP>
# baseUrl 填 http://127.0.0.1:7682,cfClientId/cfClientSecret 留空

MCP 工具

工具

功能

read_serial_output

读取自上次读取以来的新增输出(原始文本,无 OCR 误差)

send_serial_command

发送一行命令(自动补 \r

send_raw_key

发送特殊按键:enter ctrl+c ctrl+d ctrl+z esc tab backspace up down left right home end space break

serial_reset

NAS 重启后重建连接、清空旧缓冲

注册到 MCP 客户端

{
  "mcpServers": {
    "nas-serial": {
      "command": "node",
      "args": ["/绝对路径/mcp-server/index.js"],
      "cwd": "/绝对路径/mcp-server"
    }
  }
}

注册后工具名为 mcp__nas-serial__read_serial_output 等。


救急工作流

1. read_serial_output          → 看到 getty 登录提示
2. send_serial_command("root") → 提示 Password:
3. send_serial_command("<pw>") → 进入 shell
4. send_serial_command("sync") → 必要时 reboot

【NAS 卡死】
5. send_raw_key("ctrl+c")      → 中断当前进程,再诊断

【NAS 完全卡死,console 无响应】
6. send_raw_key("break")       → 触发内核 Magic SysRq
   5 秒窗口内发送 REISUB 序列(e s u b)
   → 杀进程 → 落盘 → 只读重挂 → 重启

安全说明

  • 凭据绝不入库config.json 已加入 .gitignore(文件权限 600)。仓库中仅提供 config.example.json 占位模板

  • 零信任接入:Cloudflare Access Service Token 为唯一门禁,无公网 IP、无端口映射暴露

  • 单客户端连接:ttyd max_clients=1,避免多个会话争抢串口字符(人类占用网页终端时 MCP 会连接失败,稍后重试即可)

  • 最小依赖:软路由侧零安装,不引入新的攻击面


项目结构

mcp-server/
├── index.js              # MCP Server 主体:工具定义、会话管理、串口交互
├── ttyd-client.js        # ttyd 1.7.3 WebSocket 协议客户端
├── config.example.json   # 配置模板(占位符)
├── config.json           # 实际配置(gitignored,含凭据)
└── package.json

依赖@modelcontextprotocol/sdk · ws · zod


已知限制

  • 依赖 ttyd 1.7.3 的协议实现,升级 ttyd 可能需要适配

  • charThrottleMs > 0 时输入吞吐降低(旧 shell 兼容代价)

  • 单一串口设备对应单一 MCP Server 实例


许可

MIT

Related MCP Connectors

Related MCP Servers

  • A
    license
    B
    quality
    A
    maintenance
    Enables SSH and UART/serial port access for Claude Code to directly control remote devices like Raspberry Pi, embedded systems, and IoT devices. Supports command execution, file transfers via SFTP, and serial communication.
    24
    1
    AGPL 3.0
  • A
    license
    A
    quality
    A
    maintenance
    MCP server that lets LLMs talk to serial devices: microcontrollers, routers, modems, embedded Linux, anything with a UART.
    23
    66 PyPI
    5
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables AI agents to have persistent, fully interactive SSH sessions into remote hosts, behaving like a local terminal.
    9 npm
    1
    MIT