1. MCP 简介
MCP(模型上下文协议)是一套标准化的通信协议,目标是让 AI 模型用统一的方式接入外部工具和数据源。

MCP 和 Skill 到底有什么区别?
拿示波器来做类比。MCP 好比一台示波器,提供了“采集”这个能力;Skill 则相当于一份调试手册:先选哪个通道、采样率设多少、触发方式怎么定、抓到波形后按什么标准判读、出现异常又该走哪些步骤。
2. MCP 例子:串口日志 MCP

这次我们直接实现一个简单的 MCP Server 和配套 Skill,用来完成下面这类任务:
在 Windows 上打开 ST-Link 虚拟串口 COM7,波特率 115200,抓取 5 行日志。
其中 MCP Server 负责读串口,Skill 负责调用和解释结果。
2.1 本文的几个角色

2.2 给 Agent 的创建提示词
下面这段提示词可以让编码 Agent 一次性完成 Server、Skill、配置示例和验证:
你是熟悉 Windows 串口、Python 3.11、MCP Python SDK 和
pyserial 的嵌入式工具工程师。请在当前工作区创建一个名为
serial-mcp 的本地 stdio MCP Server,并配套创建
serial-log-capture Skill。
目标:
让 Agent 在用户明确要求时,只读抓取 STM32 或嵌入式板卡的
串口日志,并把原始文本、行数、超时状态和错误分类返回给会话。
硬约束:
1. 不提供写串口、发送命令、复位、烧录或修改 DTR/RTS 的工具。
2. MCP 只暴露一个 read_serial 工具。
3. 不使用 Shell 拼接命令,不依赖全局端口号猜测。
4. 串口资源无论成功还是异常都必须在 finally 中关闭。
5. 进程内只允许一个读取调用占用串口。
6. 返回值必须是结构化对象,不能只返回裸字符串。
工具接口:
read_serial(
port: str,
baudrate: int = 115200,
line_count: int = 5,
timeout_seconds: float = 10.0
)
参数与行为:
- baudrate 范围 1 到 4000000。
- line_count 范围 1 到 100。
- timeout_seconds 范围 1 到 30。
- 只读取以换行符结束的完整行;超时时保留已收到的完整行和尾部半行。
- 单次输出最多缓存 64 KiB,避免无换行输出无限增长。
- 使用单调时钟控制总超时,短 readline 超时用于循环检查截止时间。
- 返回 ok、timeout、error 三种 status。
- 返回 text、received_lines、requested_lines、duration_ms、
error_code、message。
- 错误码至少覆盖 INVALID_ARGUMENT、PORT_NOT_FOUND、
ACCESS_DENIED、PORT_BUSY、SERIAL_ERROR。
- 工具注解使用 readOnlyHint=True、destructiveHint=False、
openWorldHint=False,并配置结构化输出。
Skill 要求:
- 默认 COM7、115200、5 行、10 秒;用户给定时严格使用用户值。
- 单次抓取不超过 20 行,不自动循环重试。
- 成功或超时都先展示原文,再扫描 panic、oops、HardFault、
error、assert、failed、reset。
- 没有命中关键词时,只能说“未命中这组关键词”,不能宣称系统健康。
- 遇到 PORT_NOT_FOUND、ACCESS_DENIED、PORT_BUSY、
INVALID_ARGUMENT、SERIAL_ERROR 时给出不同排查动作。
工程交付:
- 使用 src 布局、pyproject.toml、独立虚拟环境。
- 提供 Codex config.toml 示例,command 指向虚拟环境 Python,
args 使用 -m serial_mcp。
- 编写单元测试,覆盖参数校验、超时、关闭端口和错误分类。
- 提供一份“抓启动日志、抓 HardFault、偶发失败复抓”的
会话使用说明。
- 最后运行测试并报告真实结果;无法连接硬件时明确说明,
不要伪造板端日志。
完整链路如下:

MCP 和 Skill 的差别可以归结为一句话:
MCP 决定这个动作能不能执行,Skill 决定这次任务应该怎样执行。
3. 典型应用
假设某块工业控制板在冷启动时偶发进入安全模式。设备能通过 USB 转串口输出日志,但没有连接在线调试器。
3.1 提示词
STM32 冷启动偶发卡在传感器初始化。
用 serial-mcp 抓 COM7、115200,20 行,10 秒。
只读,先贴原始日志,再指出异常;抓取期间我会手动复位。
3.2 Agent 实际传了什么
Agent 会把任务补成结构化参数,只调用一次 read_serial:

工具返回的核心信息不是只有 text,还包括 status、实际收到的行数、请求行数、耗时和错误码。例如:

Agent 最后输出的总结如下:

4. 怎样接进 Codex
项目使用 Python 3.11、MCP Python SDK 和 pyserial。
Codex 通过 config.toml 启动本地 stdio Server,并指向独立虚拟环境中的 Python。
4.1 先让 Server 出现在工具列表
示例配置如下,路径需要换成自己的项目位置:
[mcp_servers.serial-mcp]
command = 'C:\tools\serial-mcp\.venv\Scripts\python.exe'
args = ["-m", "serial_mcp"]
cwd = 'C:\tools\serial-mcp'
enabled = true
startup_timeout_sec = 15
tool_timeout_sec = 15
4.2 重启后确认工具已发现
配置后重启 Codex。连接正常时,会话中的 Agent 能看到名为 serial-mcp 的 Server,以及唯一的 read_serial 工具。

但是能不能抓得好,仍然需要我们告诉它:默认端口是什么、超时后能不能重试、日志里哪些词值得注意、输出应该怎么组织。这些正属于 Skill 的职责。
4.3 这个 MCP 应该限制什么
这个例子只暴露一个工具,压缩了模型误用能力的空间。项目核心目录也很小:

reader.py 需要在能力层处理几个串口的具体坑:
- 不调用
write(),也不主动切换 DTR/RTS,避免抓日志变成写命令或复位板卡。
- 以换行符计算完整行数,超时时仍保留已经收到的行和尾部半行。
- 总等待时间由单调时钟控制,单次读取只使用短超时,不永久占用串口。
- 缓存上限为 64 KiB,防止无换行输出把内存撑大。
- 不论成功还是异常,最终都关闭串口。
- 进程内加非阻塞锁,避免两个调用同时争抢同一个端口。
4.3.1 工具定义和启动方式
注册 MCP 工具时,应一并声明只读、非破坏性,并通过服务端工具白名单限制暴露范围:

readOnlyHint=True 只是给客户端的语义提示,真正可执行的接口里没有写串口方法,才算是在能力层完成了收紧。
启动方式保持简单:
def main() -> None:
mcp.run(transport="stdio")
配置指向虚拟环境后,Server 就能由 Agent 客户端拉起:

5. 总结
如果只是临时读一次日志,直接使用串口助手更简单。如果希望 AI 反复执行同类嵌入式调试任务,推荐按下面的边界来设计:
- 需要稳定、可复用、跨客户端暴露的硬件能力,做 MCP Server。
- 需要把参数默认值、错误处理、安全边界和输出格式固定下来,做 Skill。
- 只有流程,没有外部能力需求,可以先只做 Skill。
- 有串口、调试器、日志平台等外部能力,再给 Skill 配 MCP。
延伸阅读: