Skip to main content
Glama
bitshelf

Embedded Debug MCP

by bitshelf
README.md
# Embedded Debug MCP Server

## 架构
![Architecture](./docs/Architecture.png)

```
                         ┌────────────────────────┐
                         │    Ethernet Switch     │
                         └─┬──────┬──────┬──────┬┘
                           │      │      │      │
                  Ethernet │      │      │      │ Ethernet
                           │      │      │      │
          ┌────────────────┘      │      │      └──-───────────────────────────┐
          │                       │      └───-───-───┐                         │
          ▼                       ▼                  ▼                         ▼

┌──────────────────┐   ┌──────────────────┐   ┌───────────────┐         ┌──────────────────┐
│ Windows PC       │   │ Build Machine    │   │ ARM           │──UART───│ Target Board     │
│                  │   │ Linux x86        │   │               │──USB────│                  │
│ SSH / Client     │   │                  │   │               │.........│ ○ RECOVERY       │
│                  │   │ Claude Code      │   │ ser2net       │─────────│ ○ RESET          │
│                  │   │ MCP Server       │   │ USB Host      │.........│ ○ Power          │
└──────────────────┘   └──────────────────┘   └───────────────┘         └──────▲────▲──────┘
```

### 工作流

```
1. 构建         cargo install --git https://github.com/bitshelf/sermcp --locked --target x86_64-unknown-linux-musl
2. 配置         dutabo init              (在项目根目录;或 vi .target.jsonc)
3. 部署         vi .mcp.json             (或依赖 hook)
4. 启动         claude                   (Claude Code 自动 spawn MCP Server)
5. 使用         serial_send_command "uname -a"
6. 查看日志     tail -f .dut-serial/mcp.log
```


## 使用方法 

在项目根目录创建 `.target.jsonc`,用 `dutabo init` 交互式创建/更新, 可以使用图形界面进行测试。
`.mcp.json` 缺失或没有`sermcp` 条目时,`dutabo init` 会自动创建/补齐该条目(已有条目则原样保留)。

- `.mcp.json`
```json
{
  "mcpServers": {
    "sermcp": {
      "command": "sermcp",
      "args": []
    }
  }
}
```

```bash
# 确认项目有 .target.jsonc
ls -la .target.jsonc .mcp.json

# 启动 Claude Code 后查看状态栏
# 成功时会显示: ● serial:active 或 ● serial:disconnected
```

### 日志

- 运行时日志写入 `{project}/.dut-serial/mcp.log`。
- 串口数据日志写入 `{project}/.dut-serial/logs/boot-NNN_*.log`。

### 多 Agent 共享与占用状态(busy / dutabo)

多个代码 Agent 在同一项目打开时共享一个 MCP server。第一个 Agent
会话是 Owner,后续会话是 ReadOnly guest。

- `dutabo` — `dutabo serial` 交互会话持有串口;对**所有**会话生效(引擎状态
  机真实转移到 `Dutabo`);
- `busy` — 另一个 Agent 会话持有 MCP,本会话只读(控制类工具被
  `agent_read_only` 拒绝);

### 故障排除

| 现象 | 原因 | 解决 |
|------|------|------|
| 状态栏不显示 | 无 `.target.jsonc` 或 Server 未启动 | 检查项目根目录; 运行 `--log-to-stderr -v` 调试 |
| `disconnected` | 连不上 ser2net | 检查 IP/端口; `nc -zv host port` |
| `DUT-off` | 目标无输出 | `serial_send_command "echo ping"`; `serial_reset` |
| 二进制不启动 | 依赖库缺失 | 检查动态链接: `ldd target/release/sermcp` |
| 第二个实例被拒 | 同 host:port 互斥 | 退出第一个实例或等它释放锁 |

## 构建

```bash
rustup target add x86_64-unknown-linux-musl
cargo musl
```

### 测试

```bash
# 运行全部单元测试(在仓库根目录执行)
cargo test -p sermcp

# 运行特定模块测试
cargo test -p sermcp boot_detector
cargo test -p sermcp command_queue
cargo test -p sermcp mcp

# 查看测试覆盖率报告
cargo test -p sermcp -- --nocapture
```

### dutabo serial 日志回滚

交互模式连接 MCP 时,会先回放 MCP 内存中最近至多 512 KiB 的串口日志,再接收实时输出。本地终端保留最多 10,000 行滚动历史;MCP 重启前或超出窗口的日志请查看 `full.serial.log`。直接连接 ser2net 时,仅能回滚本次连接收到的输出。

- 鼠标滚轮或 `Alt+PgUp/PgDn`:向上/向下查看日志。
- `Alt+Home/End`:最早的历史/实时输出。
- 主机拦截翻页键时,使用 `Ctrl-T` 后按 `u/d` 翻页,按 `g/G` 跳到最早历史/实时输出。
- `Ctrl-T q`:退出;原有 `Ctrl-T PgUp/PgDn` 和 `Shift+PgUp/PgDn` 仍可用。

向上滚动后,新输出默认保持阅读位置。普通 `PgUp/PgDn` 仍发送给设备;设备上的 vim/less 等全屏程序仍由滚轮控制。非交互管道输出不回放历史。历史回放需要配套更新 MCP 服务端。

### 串口日志分段

`current.serial.log` 保存当前启动周期的原始输出,`full.serial.log` 连续追加。收到下一次启动的 DDR/SPL 前缀时,前一个周期归档为 `boot-NNN_*.log`,前缀归入新的当前日志;文件名时间是归档时刻。连接时设备已运行的首段可能只有部分周期。`dutabo serial` 直接接管 ser2net 的路径也保存到该 DUT 的日志目录。`dutabo serial` 占用期间仍保存和分段,但不会执行 Agent 自动登录等动作。

归档读取完整磁盘文件,不受 2 MiB 内存缓冲限制;MCP 重启继续追加当前文件。重置请求本身不切段,按实际收到的启动前缀划分;没有可识别前缀的启动无法保证自动分段。默认只保留最近 10 个归档,可用 `.target.jsonc` 的 `monitor.max_archived_logs` 调整;`full.serial.log` 不参与归档清理。

### Supply power and the sleep/wake key

Relay `power_ch` controls supply OFF/ON. `serial_power_cycle` cuts supply power,
waits for `power_off_time_ms` (at least 3000 ms), then restores it.
Relay `power_key_ch` (alias `power_key_channel`) controls the separate momentary
power key used for sleep/wake. Configure it on its own relay channel, for example:

```jsonc
"relay": {
  "type": "ch340-relay",
  "port": 2000,
  "power_ch": 3,
  "power_key_ch": 4,
  "power_off_time_ms": 3000
}
```

Use `dutabo button power_key pulse 500` for a short key press, or call
`serial_button` with `{"button":"power_key","action":"pulse","delay_ms":500}`.
`press` and `release` are also supported. Key pulses use the requested duration
(default 500 ms) without the supply-off minimum. The board's OS determines
whether the press sleeps, wakes, or performs another configured power-key action;
a successful relay operation does not confirm the OS state.
`dutabo init` provides separate `power` and `power key` rows. Channel 0 inherits
the global channel, consistent with the other relay channels. `dev_ctrl=none`
or `disabled` disables both controls. Restart the MCP server after changing its
configuration.