• 正文
  • 相关推荐
申请入驻 产业图谱

嵌入式 Linux 工程师 AI 实战:让 Claude 直连你的板子

05/16 15:58
169
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

嵌入式工程师真正的护城河,是"把工程系统改造成 AI 友好"的能力。

今天讲怎么把这件事做到最彻底的一步:让 AI 直接连到你的目标板,自己跑命令、自己看输出、自己 debug

MCP(Model Context Protocol)—— Anthropic 2024 年底推出的 Agent 调外部能力的开放协议。这一年它从"看起来挺有用" 变成了"我现在做嵌入式离不开"。

我自己这套用了快半年,本文是把它整理成可复制的工作流。代码量不大(核心 200 行 Python),但改变的工作方式比代码量值多了

本文假设你用 Claude Code,知道什么是 stdio MCP server。完全不熟的话先看 Anthropic 官方那份 quickstart。


一、问题:嵌入式调试的"复制粘贴税"

先讲为什么要做这件事。

我以前调驱动的工作流:

板子卡了
  ↓
ssh 上去敲 dmesg | tail -50
  ↓
复制 输出
  ↓
切到 Claude Code 窗口
  ↓
粘贴 + "帮我看一下这个 oops"
  ↓
Claude 说"再给我看一下 /sys/class/iio/iio:device0/ 下面的内容"
  ↓
切回 ssh
  ↓
ls /sys/class/iio/iio:device0/
  ↓
复制
  ↓
粘贴
  ↓
(如此反复 10 次)

我管这个叫复制粘贴税。一次调试要 copy/paste 三五十次。

不仅烦,关键是会丢上下文——粘贴时容易粘错、容易漏行;Claude 看到的不是真实板子状态,是经过你手"过滤"的版本。

MCP 解决的就是这件事:让 Claude 跟板子之间有一条直通线

Claude Code  ←→  MCP server  ←→  ssh / serial  ←→  目标板

Claude 想看 dmesg,自己去看;想读 sysfs,自己去读;想跑一行命令,自己去跑。


二、MCP 是什么,30 秒讲清楚

跳过的话直接看下一节。

MCP 是 Agent 调外部能力的标准协议

具体形态:

MCP server:一个进程,暴露一组"工具"(tool)和"资源"(resource)。你写一个

read_dmesg工具,里面是任意代码。

MCP client:Claude Code / Cline / Cursor / Continue 等等。它们读你的 MCP server 列表,把 server 暴露的工具当成"Claude 能调用的函数"。

协议:JSON-RPC over stdio(最常用)/ SSE / HTTP。stdio 最简单,本文都用 stdio。

为什么不直接让 Claude 跑 bash?Claude Code 的 Bash 工具跑在你的开发机上,不是板子上。你不想让 Claude 直接 ssh 进板子瞎搞——需要安全围栏(白名单命令、确认 prompt、审计日志)。你想让某些操作有"语义包装"——比如

read_iio_channel(0

cat /sys/bus/iio/devices/iio:device0/in_voltage0_raw易用。


三、整体架构

我自己的 setup 长这样:

┌─────────────────────────────────┐
│  开发机(Win/Mac/Linux)         │
│  - Claude Code                   │
│  - MCP server (Python, stdio)    │  ←── 本文重点
│  - paramiko / asyncssh           │
└──────────┬──────────────────────┘
           │ SSH (LAN / USB-Ethernet gadget)
           ↓
┌─────────────────────────────────┐
│  目标板(i.MX 8M Plus / RK3588 等)│
│  - sshd                          │
│  - 我的驱动 + sysfs / debugfs    │
│  - serial gadget (备用通道)      │
└─────────────────────────────────┘

MCP server 跑在开发机上、stdio 模式、被 Claude Code 拉起。它再通过 SSH/serial 操作板子。

为什么 MCP server 在开发机而不是板子上

    1. 板子上跑 Python 太重,stdio 给开发机上的 Claude 用最方便SSH 出问题 / 板子重启 / 板子没网,MCP server 还活着,可以重连凭证管理(SSH key)在开发机上更好控制同一份 MCP server 配置,不同板子切

--target

     参数就行

四、第一版:100 行能用的 MCP server

直接给代码。先实现两个工具:read_dmesg 和 run_shell(带白名单)。

依赖:

pip install mcp asyncssh

embedded_mcp/server.py

#!/usr/bin/env python3
"""嵌入式调试 MCP server,stdio 模式"""
import asyncio
import os
import re
from mcp.server.fastmcp import FastMCP
import asyncssh

mcp = FastMCP("embedded-board")

TARGET_HOST = os.environ.get("BOARD_HOST", "192.168.7.2")
TARGET_USER = os.environ.get("BOARD_USER", "root")
TARGET_KEY  = os.environ.get("BOARD_KEY",  os.path.expanduser("~/.ssh/board_rsa"))

ALLOW_PREFIXES = (
    "dmesg", "uname", "uptime", "free", "cat /proc/", "cat /sys/",
    "ls /sys/", "ls /proc/", "ls /dev/", "lsmod", "modinfo",
    "iio_info", "iio_readdev",
    "ip addr", "ip route", "ifconfig",
)

DENY_PATTERNS = (
    re.compile(r"brmb"),
    re.compile(r"bddb"),
    re.compile(r">"),
    re.compile(r">>"),
    re.compile(r"bmkfs"),
    re.compile(r"brebootb"),
    re.compile(r"bshutdownb"),
    re.compile(r"binsmodb.*.."),
    re.compile(r"brmmodb"),
)


asyncdef ssh_run(cmd: str, timeout: float = 15) -> dict:
    asyncwith asyncssh.connect(
        TARGET_HOST, username=TARGET_USER,
        client_keys=[TARGET_KEY], known_hosts=None,
    ) as conn:
        r = await asyncio.wait_for(conn.run(cmd), timeout=timeout)
        return {"stdout": r.stdout, "stderr": r.stderr, "rc": r.exit_status}


def is_allowed(cmd: str) -> tuple[bool, str]:
    if any(p.search(cmd) for p in DENY_PATTERNS):
        returnFalse, "命中黑名单"
    ifnot any(cmd.strip().startswith(p) for p in ALLOW_PREFIXES):
        returnFalse, f"前缀不在白名单:{ALLOW_PREFIXES[:3]} 等"
    returnTrue, ""


@mcp.tool()
asyncdef read_dmesg(lines: int = 100, grep: str | None = None) -> str:
    """读取目标板最近的 dmesg。lines: 取末尾多少行;grep: 可选过滤。"""
    cmd = f"dmesg | tail -n {int(lines)}"
    if grep:
        safe = grep.replace("'", "").replace('"', "")
        cmd += f" | grep -E '{safe}'"
    r = await ssh_run(cmd)
    return r["stdout"] or"(空)"


@mcp.tool()
asyncdef run_shell(cmd: str) -> str:
    """在目标板上跑只读命令(白名单约束)。"""
    ok, reason = is_allowed(cmd)
    ifnot ok:
        returnf"REJECTED: {reason}"
    r = await ssh_run(cmd, timeout=30)
    out = f"[exit={r['rc']}]n{r['stdout']}"
    if r["stderr"]:
        out += f"n[stderr]n{r['stderr']}"
    return out


if __name__ == "__main__":
    mcp.run()

挂到 Claude Code 的 ~/.config/claude-code/mcp.json(macOS / Linux)或 %APPDATA%Claudemcp.json(Windows):

{
  "mcpServers": {
    "embedded-board": {
      "command": "python",
      "args": ["-m", "embedded_mcp.server"],
      "env": {
        "BOARD_HOST": "192.168.7.2",
        "BOARD_USER": "root",
        "BOARD_KEY":  "/home/me/.ssh/board_rsa"
      }
    }
  }
}

重启 Claude Code,跟它说 "看一下板子上最近的 dmesg",它就会自己调 read_dmesg

到这一步你应该已经感觉到不一样了——你不再"贴"信息给 Claude,Claude 自己去拿。


五、第二版:把工具集补齐

第一版能用,但工具太少。下面这套是我现在线上跑的版本暴露的工具清单:

工具 干什么 是否破坏性
read_dmesg 读 dmesg(带 grep / tail) 只读
read_sysfs 读 /sys/ 下指定路径 只读
read_proc 读 /proc/ 下指定路径 只读
list_dir ls 某个路径 只读
lsmod / modinfo 内核模块状态 只读
run_shell 跑白名单内 shell 只读
read_gpio GPIO 状态 只读
read_iio 读 IIO 设备 raw 值 只读
capture_serial 抓串口 N 秒输出 只读
dump_devicetree dump /proc/device-tree 子树 只读
install_module insmod 我们自己的驱动 破坏性
remove_module rmmod 破坏性
write_sysfs 写 sysfs(受限路径) 破坏性
set_gpio 设 GPIO 输出 破坏性
reboot_board 重启板子 破坏性
flash_image dd 镜像到 eMMC 破坏性

只读工具:Claude 想调就调,不问。破坏性工具:每次调用都触发 Claude Code 的 permission prompt——必须我手动批准。

这是关键的安全模式。

几个具体工具的实现要点

read_sysfs:要白名单根路径,不能让 Claude 读 /sys/firmware/efi/efivars/ 这种东西。

SYSFS_ALLOW_ROOTS = ("/sys/class/", "/sys/bus/", "/sys/devices/", "/sys/module/")

@mcp.tool()
asyncdef read_sysfs(path: str) -> str:
    """读取 /sys/ 下指定路径的内容。"""
    ifnot any(path.startswith(r) for r in SYSFS_ALLOW_ROOTS):
        returnf"REJECTED: 路径必须以 {SYSFS_ALLOW_ROOTS} 之一开头"
    if".."in path:
        return"REJECTED: 路径不能包含 .."
    r = await ssh_run(f"cat {path}")
    return r["stdout"]

capture_serial:板子卡死的时候 ssh 进不去,这时候 serial 是唯一的窗口。

import serial_asyncio

@mcp.tool()
asyncdef capture_serial(seconds: float = 5.0, port: str = "/dev/ttyUSB0",
                         baud: int = 115200) -> str:
    """抓取串口 N 秒输出。用于 ssh 不通的死机场景。"""
    reader, _ = await serial_asyncio.open_serial_connection(
        url=port, baudrate=baud,
    )
    chunks = []
    try:
        end = asyncio.get_event_loop().time() + seconds
        while asyncio.get_event_loop().time() < end:
            data = await asyncio.wait_for(reader.read(4096), timeout=0.5)
            chunks.append(data.decode("utf-8", errors="replace"))
    except asyncio.TimeoutError:
        pass
    return"".join(chunks) or"(无输出)"

install_module

@mcp.tool()
asyncdef install_module(ko_path: str, params: str = "") -> str:
    """insmod 一个我们自己编的 .ko。ko_path 是开发机上的路径,会先 scp 上去。"""
    ifnot ko_path.endswith(".ko") or".."in ko_path:
        return"REJECTED: 路径不合法"
    asyncwith asyncssh.connect(...) as conn:
        await asyncssh.scp(ko_path, (conn, "/tmp/test.ko"))
        r = await conn.run(f"insmod /tmp/test.ko {params}")
        returnf"[exit={r.exit_status}]n{r.stdout}{r.stderr}"

注意 每个破坏性工具的 docstring 都要简洁明确——Claude 是按 docstring 决定什么时候调用,写错了它会乱调。


六、安全:嵌入式 MCP 的几条铁律

这一节是本文最重要的。写错了会烧板子、丢数据、把客户测试机搞砸

铁律 1:默认拒绝,白名单放行

不是"黑名单拦截",是白名单放行。Claude 偶尔会"创造性"地构造命令——比如把 cat /sys/kernel/debug/... 换成 xxd /sys/kernel/debug/... 来绕过。白名单挡得住,黑名单挡不住。

铁律 2:写操作必须经过 permission prompt

破坏性工具不要写成 mcp.tool() 直接暴露。要么用 Claude Code 的 requireApproval 配置,要么在工具内部主动 await ask_user()(FastMCP 支持)。

我的实践:所有改板子状态的操作都需要 Y/n 确认。多敲两个回车,比烧一颗芯片便宜。

铁律 3:路径必须 sanitize

# 错误
await ssh_run(f"cat {path}")

# 正确
if ".." in path or path.startswith("/"):
    if not path_in_allowlist(path):
        raise ValueError(...)
await ssh_run(["cat", path])  # 别用 shell 拼接

Claude 不是恶意的,但它写 prompt 时没有 shell 安全意识path = "; rm -rf /; #" 在它看来只是个字符串。你的代码必须假定 path 是敌方输入。

铁律 4:审计日志

所有 MCP 调用记一份日志:

LOG_PATH = "~/.embedded_mcp/audit.log"

def audit(tool: str, args: dict, result_summary: str):
    with open(os.path.expanduser(LOG_PATH), "a") as f:
        f.write(f"{datetime.now().isoformat()} {tool} {args} -> {result_summary[:200]}n")

Claude 出过一次"我自己也不记得调了什么"的情况——日志能让你复盘。

铁律 5:生产板子上别接 MCP

MCP 工具是给开发板的,不是生产设备。

硬规矩:任何接入 MCP 的板子,外壳上贴红色 "DEV" 标签。装客户那里的板子永远不接 MCP server。

铁律 6:危险命令显式禁止

无论白名单怎么写,这几类操作就算"看起来安全"也要默认拒绝

    任何对 eMMC / SD 的块设备写(dd, mkfs)任何 fuse / OTP 烧写任何 PMIC 寄存器写任何 cpufreq / thermal 限制解除任何对 watchdog 的禁用

要做这些,人工敲命令,不要让 Agent 触发。


七、实战:用 MCP 调一个 ADS1256 驱动

场景:probe 失败,ret=-110

有 MCP 之前

我:板子上 ads1256 probe 失败了 (手动 ssh 板子,dmesg | tail -50,复制)

我:[粘贴 50 行 dmesg] 帮我看一下

Claude:能让我看一下 /sys/bus/spi/devices/spi3.0/ 下的内容吗? (ssh + ls + 复制粘贴)

我:[粘贴] 看

Claude:你的 device tree 里有这个节点吗? (开 vscode,打开 dts,复制)

我:[粘贴片段] …

20 分钟过去了。

有 MCP 之后

我:板子上 ads1256 probe 失败,帮我分析

Claude:(自动调 read_dmesg) 看到 ret=-110,是 SPI 通信超时。让我看一下 SPI 总线状态。

Claude:(自动调 read_sysfs("/sys/bus/spi/devices/spi3.0/"))

Claude:(自动调 dump_devicetree("/proc/device-tree/soc@0/bus@30800000/spi@30840000"))

Claude:找到原因了——你 device tree 里 spi-max-frequency 写的是 1920000,但你的 SoC SPI controller 实际最高跑不到这个频率。建议改成 1000000。

3 分钟。我做的事:敲了一句话,看了 Claude 的结论,验证。

收益不在快——快只是表面。真正的收益是 Claude 看到的是真实板子状态,不是经过我手过滤的版本。我经常因为"我以为重要"而漏掉关键信息,Claude 不会。

场景:oops 分析 + 修复 + 验证一条龙

我:刚 insmod 自己写的驱动板子 oops 了,帮我看
Claude:(capture_serial 抓 10 秒) → 拿到完整 oops
Claude:(read_sysfs 读 /sys/module/) → 确认模块状态
Claude:分析:是 iio_buffer_setup_ops 没初始化导致 NULL deref。修改 probe 函数加上这几行 …
(Claude 改完代码)
我:编译一下
(Claude 在本地跑 make)
Claude:编译过了。要不要 install_module 测一下?
我:好
Claude:(install_module → 需要 approval) 等批准…
我:批准
Claude:(install_module 完成) (read_dmesg) probe 成功,没有 oops。建议跑一个 iio_readdev 验证一下数据。
我:跑
Claude:(read_iio + iio_readdev) 读到 524288,是 1V 输入的正确值。

整个调试闭环没有一次手动复制粘贴


八、跟传统工作流的时间对比

调了 10 个真实驱动 bug,对比两种模式:

阶段 传统 MCP 加持 节省
信息收集(dmesg / sysfs / dts) 8 分钟 30 秒 16x
第一次定位假设 5 分钟 2 分钟 2.5x
验证假设 10 分钟 4 分钟 2.5x
修代码 + 重编 15 分钟 15 分钟 1x
上板验证 8 分钟 3 分钟 2.7x
总计 46 分钟 24 分钟 1.9x

接近腰斩

注意几件事:

修代码这一步几乎没省

    1. ——AI 本来就在帮你写代码,跟有没有 MCP 关系不大。

信息收集省得最多

    ——这是预料之中。真正的隐性收益是"思路不会断"——以前在 ssh 窗口和 Claude 之间反复切,思路经常断;现在你只跟 Claude 对话,它去敲板子。

九、踩过的坑

按踩坑频率排:

坑 1:Claude 一遇 SSH 超时就疯狂重试

Claude 的工具调用失败时默认会重试 3 次。如果板子真死机了,它会连试 3 次每次 30 秒,加起来卡你 90 秒。

解决:工具里实现"失败立即返回 + 明确告知 Claude 别重试":

return "BOARD_UNREACHABLE: ssh 连不上,可能死机。请改用 capture_serial。"

Claude 看到这个返回会自动换工具

坑 2:dmesg 输出超大塞爆 context

长跑的板子 dmesg 几万行。默认 tail 100 行不够、全 dump 又把 context window 占完。

解决:默认

tail -n 100提供

grep 参数加一个

dmesg_since(boot_id)

     工具,只看从某次 boot 之后的

坑 3:path traversal

第一版我写过 await ssh_run(f"cat {path}"),被自己 Claude 顺手用 "; reboot;" 试出来。

解决:所有路径用 shlex.quote()subprocess style argv 数组、白名单根目录。

坑 4:MCP server 崩了 Claude 不知道

stdio server 进程挂了之后 Claude Code 经常不知道,下次调用直接 hang。

解决:MCP server 加 watchdog,超时自动重启自己;Claude Code 那边设短超时(5 秒)。

坑 5:多板子切换

公司里同时调 3 颗板子,每个一个 MCP server 实例,Claude Code 工具列表里冒出 30 个 tool name,它经常调错板子。

解决:工具名加板子前缀:

rk3588_read_dmesg

imx8mp_read_dmesg

    • 或者 MCP server 暴露一个

select_board(id)

     工具,所有后续调用走这个 board

坑 6:串口被 minicom 占着

capture_serial 跟你手动 minicom 抢同一个 /dev/ttyUSB0,互相崩。

解决:用 conserver 或 ser2net 把串口变成网络服务,多客户端共用。

坑 7:模型选错

Claude Code 默认 Sonnet。Sonnet 调 MCP 是够用的,但遇到复杂调试要切 Opus。我现在的工作流:日常 Sonnet,遇到 oops 或者数据异常切 Opus。


十、还没解决的问题
还有几个真的没解决的问题。

问题 1:示波器 / 逻辑分析仪 接不进 MCP

理论上 sigrok 可以、Saleae 也有 SDK,能写个 MCP server 让 Claude 触发采样、读波形。实践上我没做成——波形数据太大、Claude 看图能力一般、调一次时序问题省的时间不如自己看。

目前的做法示波器还是自己看,看完口述给 Claude。

问题 2:vendor BSP 编译没接入

Yocto / Buildroot 编译 30 分钟起跳,让 Claude 在 MCP server 里跑编译有点过分。目前还是手动 bitbake,编完告诉 Claude 结果

问题 3:硬件改板 / 飞线场景 AI 完全没用

板子改了一根飞线、改了一个电阻分压,MCP 这边什么都不知道。这种场景必须人主导

问题 4:CI 里没法直接用

CI runner 跟开发机 MCP 是两套 setup。我们 CI 里跑的是另一套专门的 headless 测试驱动,跟 MCP server 共用底层 SSH 代码、但不走 Claude。


十一、要不要开源

我做的 embedded-mcp-toolkit,但整体还在调试中

    一个 stdio MCP server 框架(FastMCP based)一组开箱即用的 tool(dmesg / sysfs / iio / gpio / serial)内置安全围栏 + 审计日志板子适配层:插件式支持 i.MX、Rockchip、Allwinner

十二、给嵌入式工程师的总结

回到开头那句话:

AI 时代嵌入式工程师真正的护城河,是"把工程系统改造成 AI 友好"的能力。

MCP 是这件事最关键的一块拼图。它把"AI Infra 嵌入式版"(你的板子 / 调试栈)和"AI Docs"(CLAUDE.md / driver.ai.md)真正接起来。

接起来之后 AI 不只是个"会写代码的助手",是个能看到、能动手的协同工程师

具体的行动建议,按优先级:

本周内

    1.  —— 跑通本文第四节的 100 行最小 MCP server,至少有

read_dmesg

    1.  一个工具

本月内

    1.  —— 把第五节那个工具集补齐,覆盖你日常 80% 的查询动作

持续做

     —— 每次发现"我又在切窗口复制粘贴",就给 MCP server 加一个工具

最后再强调一遍:

    默认拒绝,白名单放行写操作必须确认生产板子永远别接 MCP审计日志永远开着

相关推荐

登录即可解锁
  • 海量技术文章
  • 设计资源下载
  • 产业链客户资源
  • 写文章/发需求
立即登录

研究生在读,熟悉硬件、STM32单片机、嵌入式Linux。已收获小米、联发科、浙江大华、上能电气、英威腾、汇川技术、格力、富士康等大厂offer。在这里分享求职经验、嵌入式学习规划、考研、嵌入式Linux技术文章等。