Doramagic 项目包 · 项目说明书
mcp-retroarch 项目
生成时间:2026-05-16 05:01:44 UTC
项目概述
mcp-retroarch 是一个基于 Model Context Protocol (MCP) 的服务端实现,用于通过标准化的 JSON-RPC 接口远程控制 RetroArch 模拟器。该项目采用 TypeScript 开发,以 npm 包的形式分发,充当 MCP 客户端(如 Claude Code、Claude Desktop)与 RetroArch 网络控制接口(N...
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
项目简介
mcp-retroarch 是一个基于 Model Context Protocol (MCP) 的服务端实现,用于通过标准化的 JSON-RPC 接口远程控制 RetroArch 模拟器。该项目采用 TypeScript 开发,以 npm 包的形式分发,充当 MCP 客户端(如 Claude Code、Claude Desktop)与 RetroArch 网络控制接口(Network Control Interface, NCI)之间的桥接层。
项目的核心价值在于将 RetroArch 的底层 UDP 协议封装为人类可读的工具(Tool),使 AI 助手能够直接与运行中的模拟器实例交互,执行内存读写、存档管理、截图、游戏控制等操作。资料来源:README.md:1
技术架构
整体架构图
graph TD
subgraph "MCP 客户端层"
A[Claude Code / Claude Desktop]
end
subgraph "mcp-retroarch 桥接层"
B[stdio JSON-RPC 传输]
C[MCP Server]
D[RetroArch.ts UDP 客户端]
end
subgraph "RetroArch 层"
E[RetroArch NCI<br/>UDP :55355]
F[libretro Core<br/>+ 游戏]
end
A -->|stdio| B
B --> C
C --> D
D -->|UDP| E
E --> F
style B fill:#e1f5fe
style D fill:#fff3e0mcp-retroarch 采用标准的 MCP 服务端架构:服务端通过标准输入输出(stdio)与 MCP 客户端通信,JSON-RPC 2.0 格式封装所有请求和响应。内部通过 RetroArch.ts 模块维护一个 UDP socket,与目标主机上的 RetroArch 实例保持长连接。资料来源:src/index.ts:1-30
核心模块
| 模块 | 文件路径 | 职责 |
|---|---|---|
| 入口模块 | src/index.ts | MCP 服务端初始化、后台连接探测、stdio 生命周期管理 |
| 工具定义 | src/tools.ts | 声明所有 MCP 工具的名称、描述、输入模式 |
| UDP 通信 | src/retroarch.ts | 管理 UDP socket、命令发送、响应解析、超时处理 |
资料来源:src/index.ts:1-30, src/tools.ts:1-100, src/retroarch.ts:1-80
技术特性
支持的功能
| 功能类别 | 具体操作 | 备注 |
|---|---|---|
| 内存读写 | retroarch_read_memory / retroarch_write_memory | 基于系统内存映射(READ_CORE_MEMORY) |
| 内存读写(兼容) | retroarch_read_ram / retroarch_write_ram | 基于 CHEEVOS 地址空间(READ_CORE_RAM) |
| 存档管理 | 保存当前槽位、加载指定槽位、切换槽位指针 | |
| 模拟器控制 | 暂停切换、帧进、退放 | |
| 游戏控制 | 重置游戏 | |
| 截图 | 截图保存 | |
| 状态获取 | 获取游戏状态、系统信息、CRC32 | |
| 用户通知 | 显示 OSD 消息 |
资料来源:README.md:1-50
内存读取的双路径设计
mcp-retroarch 实现了两种内存读取路径,以最大化兼容性:
graph LR
A[工具调用] --> B{核心是否暴露<br/>内存映射?}
B -->|是| C[READ_CORE_MEMORY]
B -->|否| D[READ_CORE_RAM]
C --> E[系统内存地址空间]
D --> F[CHEEVOS 地址空间]
E --> G[例: GBA EWRAM<br/>0x02000000]
F --> H[例: SNES WRAM<br/>0x000000]两种路径的主要区别:
| 特性 | read_memory (READ_CORE_MEMORY) | read_ram (READ_CORE_RAM) |
|---|---|---|
| 地址空间 | libretro 系统内存映射 | CHEEVOS 成就地址空间 |
| 适用场景 | 首选方案,有内存映射的现代核心 | 无内存映射的核心(旧核心、部分 PlayStation 核心) |
| 返回确认 | ❌ | ❌ |
资料来源:src/tools.ts:1-150
已测试的核心
| 系统 | 核心 | read_memory | read_ram | 备注 |
|---|---|---|---|---|
| Game Boy Advance | mgba_libretro | ✅ | ✅ | GBA 中断向量表在 0x0000 可见 |
| NES | mesen_libretro | ✅ | ✅ | 完整 16 位 NES 地址空间暴露 |
| NES | nestopia_libretro | ❌ | ✅ | 仅 CHEEVOS,64KB 限制 |
| SNES | snes9x_libretro | ❌ | ❌ |
资料来源:README.md:30-50
通信机制
UDP 传输特性
mcp-retroarch 与 RetroArch 之间的通信基于 UDP 协议,这种设计带来了以下特性:
sequenceDiagram
participant MCP as MCP 客户端
participant Bridge as mcp-retroarch
participant RA as RetroArch
Note over Bridge: 建立 UDP socket
Bridge->>RA: 发送命令
RA-->>Bridge: 返回响应
Note over Bridge: 查询操作
Bridge->>RA: 发送查询
Note over Bridge: 等待响应 (默认 5s 超时)
RA-->>Bridge: 返回数据关键约束:
- 大多数命令为 fire-and-forget(即发即忘),RetroArch 不返回 ACK
retroarch_write_memory是唯一返回写入字节数的命令retroarch_write_ram无确认机制,无法区分部分写入与完全失败- UDP 数据报在高负载下可能丢失(即使在本地回环接口上)
资料来源:src/retroarch.ts:40-80, src/tools.ts:100-150
连接管理
后台连接探测采用「即发即忘」模式,不会阻塞服务启动:
// src/index.ts 核心逻辑
ra.connect()
.then(() => ra.getVersion())
.then((v) => process.stderr.write(`[mcp-retroarch] connected to ${ra.describeTarget()} — RetroArch ${v}\n`))
.catch((err) => process.stderr.write(
`[mcp-retroarch] note: RetroArch not reachable yet (${ra.describeTarget()}): ${err}\n` +
` Enable Network Commands in retroarch.cfg (network_cmd_enable / network_cmd_port)\n`
));
资料来源:src/index.ts:15-25
项目配置
环境变量
| 环境变量 | 默认值 | 说明 |
|---|---|---|
RETROARCH_HOST | 127.0.0.1 | UDP 目标主机地址 |
RETROARCH_PORT | 55355 | UDP 端口,必须与 RetroArch 配置中的 network_cmd_port 匹配 |
资料来源:README.md:80-85
RetroArch 端配置
GUI 方式:
- 进入 Settings → Network → Network Commands → ON
- 确认 Network Cmd Port 为
55355(默认值)
配置文件方式(retroarch.cfg):
network_cmd_enable = "true"
network_cmd_port = "55355"
资料来源:README.md:60-70
MCP 客户端集成
Claude Code
claude mcp add retroarch --scope user mcp-retroarch
验证连接:
claude mcp list
# retroarch: mcp-retroarch - ✓ Connected
Claude Desktop
配置文件路径:
| 平台 | 路径 |
|---|---|
| macOS | ~/Library/Application Support/Claude/claude_desktop_config.json |
| Windows | %APPDATA%\Claude\claude_desktop_config.json |
| Linux | ~/.config/Claude/claude_desktop_config.json |
配置示例:
{
"mcpServers": {
"retroarch": {
"command": "mcp-retroarch"
}
}
}
资料来源:README.md:70-100
依赖与构建
项目依赖
| 依赖类型 | 包名 | 版本要求 |
|---|---|---|
| 运行时依赖 | @modelcontextprotocol/sdk | ^1.12.0 |
| 开发依赖 | @types/node | ^22.0.0 |
| 开发依赖 | typescript | ^5.5.0 |
开发命令
| 命令 | 功能 |
|---|---|
npm install | 安装依赖 |
npm run dev | TypeScript 编译监视模式 |
node .scratch/smoke.cjs | 对运行中的 RetroArch 进行冒烟测试 |
资料来源:package.json:1-30, README.md:115-125
版本历史
项目采用语义化版本控制(Semantic Versioning),当前版本为 0.1.2。主要版本变更:
| 版本 | 日期 | 关键变更 |
|---|---|---|
| 0.1.2 | 2026-05-15 | 工具描述质量重构,fire-and-forget 语义显式化 |
| 0.1.0 | - | 初始发布,基础功能实现 |
资料来源:CHANGELOG.md:1-30
已知限制
| 限制 | 原因 | 规避方案 |
|---|---|---|
| 无法直接保存到指定槽位 | NCI 协议限制 | 使用 state_slot_plus/state_slot_minus 切换到目标槽位后再保存 |
| 游戏手柄输入不可用 | NCI 未暴露此功能 | 参见 mcp-mgba(GBA 专用) |
| 截图路径无法通过命令查询 | screenshot_directory 未通过 GET_CONFIG_PARAM 暴露 | 通过 RetroArch GUI 查看:Settings → Directory → Screenshot |
资料来源:README.md:105-120
相关项目
| 项目 | 地址 | 说明 |
|---|---|---|
| mcp-mgba | Game Boy Advance | 通过 mGBA Lua 桥接,支持手柄输入和截图 |
| mcp-pine | PCSX2 等 | 通过 PINE 协议通信,仅内存和存档 |
资料来源:README.md:130-135
资料来源:[src/index.ts:1-30](), [src/tools.ts:1-100](), [src/retroarch.ts:1-80]()
系统架构
mcp-retroarch 是一个基于 Model Context Protocol (MCP) 的桥接工具,它使 MCP 客户端(如 Claude Desktop、Claude Code)能够通过标准化的 JSON-RPC 接口控制 RetroArch 模拟器。该项目充当 MCP 协议与 RetroArch 网络控制接口(NCI)之间的中间层,将高级工具调用转换为底层的 ...
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
概述
mcp-retroarch 是一个基于 Model Context Protocol (MCP) 的桥接工具,它使 MCP 客户端(如 Claude Desktop、Claude Code)能够通过标准化的 JSON-RPC 接口控制 RetroArch 模拟器。该项目充当 MCP 协议与 RetroArch 网络控制接口(NCI)之间的中间层,将高级工具调用转换为底层的 UDP 网络命令。
架构设计遵循分离关注点原则,核心组件包括 MCP 服务器层、工具定义层和 RetroArch 通信层。通信默认通过标准输入输出(stdio)传输,而与 RetroArch 的交互则通过 UDP 网络协议完成。
架构分层
mcp-retroarch 采用三层架构设计:
graph TD
subgraph "MCP 客户端层"
A["Claude Desktop / Claude Code"]
end
subgraph "MCP 服务器层 (mcp-retroarch)"
B["src/index.ts<br/>MCP 服务器入口"]
C["src/tools.ts<br/>工具定义与路由"]
D["src/retroarch.ts<br/>UDP 通信封装"]
end
subgraph "RetroArch 目标层"
E["RetroArch NCI<br/>UDP :55355"]
end
A -->|"JSON-RPC / stdio"| B
B --> C
C -->|"工具调用"| D
D -->|"UDP 网络命令"| E
style A fill:#e1f5fe
style E fill:#fff3e0各层职责
| 层级 | 文件 | 职责 |
|---|---|---|
| 服务器入口 | src/index.ts | 初始化 MCP 服务器、建立 UDP 连接探针 |
| 工具定义 | src/tools.ts | 定义所有 MCP 工具的输入模式、处理工具调用路由 |
| 通信封装 | src/retroarch.ts | 管理 UDP socket、实现命令发送与响应接收 |
| 目标设备 | RetroArch | 执行模拟器控制命令、返回状态信息 |
核心模块详解
1. RetroArch 通信模块
文件位置: src/retroarch.ts
该模块封装了所有与 RetroArch NCI 的 UDP 通信逻辑。
#### 连接管理
sequenceDiagram
participant MCP as MCP Server
participant Socket as UDP Socket
participant RA as RetroArch
MCP->>Socket: connect()
Socket->>Socket: dgram.createSocket("udp4")
Socket->>Socket: bind(0)
Note over Socket: 动态分配本地端口
Socket->>MCP: connection ready
MCP->>RA: send(command)
Socket->>RA: UDP datagram :55355
RA-->>Socket: response
Socket-->>MCP: Buffer#### 核心类:RetroArchClient
class RetroArchClient {
private socket: dgram.Socket | null = null;
private pending: ((data: Buffer) => void) | null = null;
// 发送后不等待响应(热键类命令)
async send(command: string): Promise<void>
// 发送并等待一个 UDP 响应
async query(command: string): Promise<Buffer>
// 高层命令封装
async getVersion(): Promise<string>
async getStatus(): Promise<EmuStatus>
async readMemory(addr: number, len: number): Promise<Buffer>
// ... 其他命令
}
#### 通信模式
RetroArchClient 实现两种通信模式:
1. Fire-and-Forget(单向发送)
适用于状态切换类命令,无需确认响应:
async send(command: string): Promise<void> {
if (!this.socket) await this.connect();
return new Promise((resolve, reject) => {
this.socket!.send(command, this.port, this.host, (err) =>
err ? reject(err) : resolve(),
);
});
}
2. Query-Response(请求-响应)
适用于需要获取返回数据的命令:
async query(command: string): Promise<Buffer> {
if (!this.socket) await this.connect();
if (this.pending) {
throw new Error("retroarch query already in flight (client is serial)");
}
return new Promise<Buffer>((resolve, reject) => {
let timer: NodeJS.Timeout | null = setTimeout(() => {
this.pending = null;
reject(new Error(
`RetroArch query "${command.split(" ")[0]}" timed out after ${this.timeoutMs}ms`,
));
}, this.timeoutMs);
// ...
});
}
#### 关键设计特性
| 特性 | 实现方式 | 说明 |
|---|---|---|
| 串行化 | pending 状态标志 | 阻止并发查询,确保请求-响应配对正确 |
| 超时控制 | setTimeout | 默认超时后清除 pending 并抛出错误 |
| 错误处理 | sock.once("error") | Socket 错误直接拒绝 Promise |
| 延迟初始化 | 按需 connect() | 首次命令调用时才创建 socket |
2. 工具定义模块
文件位置: src/tools.ts
该模块定义所有 MCP 工具的元数据、输入模式和处理逻辑。
#### 工具注册结构
export const tools: Tool[] = [
// 内存操作工具
{
name: "retroarch_read_memory",
description: "PURPOSE: ...\nUSAGE: ...\nBEHAVIOR: ...\nRETURNS: ...",
inputSchema: { type: "object", required: [...], properties: {...} }
},
// 模拟器控制工具
{
name: "retroarch_pause_toggle",
description: "...",
inputSchema: { type: "object", properties: {} }
},
// ... 其他工具
];
#### 工具调用路由
export async function handleToolCall(name: string, params: Record<string, unknown>) {
switch (name) {
case "retroarch_read_memory": {
const bytes = await ra.readMemory(p.address as number, p.length as number);
const hex = Array.from(bytes).map(...).join(" ");
return ok(`${addrHex(p.address as number)} [${bytes.length} bytes]:\n${hex}`);
}
case "retroarch_pause_toggle":
await ra.pauseToggle();
return ok("Pause toggled");
// ...
}
}
资料来源:src/tools.ts:1-80
3. MCP 服务器入口
文件位置: src/index.ts
async function main() {
const ra = new RetroArchClient(host, port);
const server = new Server(
{ name: "mcp-retroarch", version: "0.1.2" },
{ capabilities: { tools } },
);
// 注册工具处理
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
return handleToolCall(name, args ?? {});
});
// 启动 stdio 传输
await server.connect(new StdioServerTransport());
// 后台连接探针
ra.connect()
.then(() => ra.getVersion())
.then((v) => process.stderr.write(`[mcp-retroarch] connected to ${ra.describeTarget()} — RetroArch ${v}\n`))
.catch((err) => process.stderr.write(
`[mcp-retroarch] note: RetroArch not reachable yet (${ra.describeTarget()}): ${err}\n`,
));
}
资料来源:src/index.ts:1-35
#### 启动流程
sequenceDiagram
participant OS as 操作系统
participant MCP as MCP Server
participant RA as RetroArch
participant CLI as MCP Client
OS->>MCP: 启动进程 (mcp-retroarch)
MCP->>MCP: 初始化 RetroArchClient
MCP->>MCP: 创建 MCP Server 实例
MCP->>MCP: 注册工具处理器
MCP->>MCP: 连接 StdioServerTransport
Note over MCP: 阻塞等待 MCP 客户端连接
CLI->>MCP: tools/list 请求
MCP-->>CLI: 工具列表
CLI->>MCP: tools/call 请求
MCP->>RA: 首次命令触发连接
RA-->>MCP: 连接成功/超时
alt RetroArch 可达
MCP->>RA: 发送 UDP 命令
RA-->>MCP: 响应数据
MCP-->>CLI: JSON-RPC 响应
else RetroArch 不可达
MCP-->>CLI: 错误响应
end数据流分析
工具调用完整流程
graph LR
A["用户请求<br/>retroarch_read_memory"] --> B["MCP JSON-RPC<br/>tools/call"]
B --> C["handleToolCall<br/>工具路由"]
C --> D["ra.readMemory<br/>参数转换"]
D --> E["query()<br/>UDP 封装"]
E --> F["socket.send<br/>网络发送"]
F --> G["RetroArch NCI"]
G --> H["socket.on(message)<br/>响应接收"]
H --> I["十六进制格式化"]
I --> J["JSON-RPC 响应"]
J --> K["返回结果"]两种内存读取路径
mcp-retroarch 根据 RetroArch 核心能力提供两条内存读取路径:
graph TD
A["内存读取请求"] --> B{"核心是否支持<br/>系统内存映射?"}
B -->|是| C["READ_CORE_MEMORY<br/>retroarch_read_memory"]
C --> D["libretro 系统总线"]
D --> E["直接访问硬件地址"]
B -->|否| F["READ_CORE_RAM<br/>retroarch_read_ram"]
F --> G["CHEEVOS 地址空间"]
G --> H["成就系统兼容地址"]
style C fill:#c8e6c9
style F fill:#fff9c4| 路径 | 工具 | 地址空间 | 适用场景 |
|---|---|---|---|
| 系统内存映射 | retroarch_read_memory | libretro 系统总线 | 支持内存映射的核心(Mesen 等) |
| CHEEVOS 兼容 | retroarch_read_ram | 成就系统地址空间 | 不暴露系统映射的核心(SwanStation 等) |
配置与环境变量
graph LR
subgraph "环境变量配置"
A["RETROARCH_HOST"]
B["RETROARCH_PORT"]
end
subgraph "RetroArch 配置"
C["network_cmd_enable"]
D["network_cmd_port"]
end
A -->|"UDP 目标"| E["RetroArchClient"]
B -->|"UDP 端口"| E
C -->|"NCI 启用"| F["RetroArch"]
D -->|"端口同步"| F
E -->|"UDP :55355"| F配置参数对照表
| 环境变量 | 默认值 | RetroArch 配置项 | 说明 |
|---|---|---|---|
RETROARCH_HOST | 127.0.0.1 | 无 | UDP 目标主机地址 |
RETROARCH_PORT | 55355 | network_cmd_port | UDP 端口号(两端必须一致) |
| 无 | true | network_cmd_enable | 必须启用网络命令 |
资料来源:README.md:80-85
错误处理机制
错误分类
| 错误类型 | 触发条件 | 处理方式 |
|---|---|---|
| 连接超时 | UDP 查询超时 | 抛出 RetroArch query "XXX" timed out |
| 内存映射缺失 | 核心不暴露系统内存 | 建议使用 retroarch_read_ram |
| 地址越界 | 读取地址不在核心描述符范围内 | 返回错误信息 |
| 并发冲突 | 存在未完成的查询时再次发起查询 | 抛出 retroarch query already in flight |
错误传播流程
graph TD
A["工具调用"] --> B{"执行查询"}
B -->|成功| C["格式化响应"]
B -->|超时| D["抛出 TimeoutError"]
B -->|核心无映射| E["抛出 MemoryMapError"]
B -->|地址无效| F["抛出 AddressError"]
D --> G["返回错误响应"]
E --> G
F --> G
C --> H["返回成功结果"]依赖关系
项目依赖
{
"dependencies": {
"@modelcontextprotocol/sdk": "^1.12.0"
},
"devDependencies": {
"@types/node": "^22.0.0",
"typescript": "^5.5.0"
}
}
资料来源:package.json:15-22
依赖层级
graph BT
A["@modelcontextprotocol/sdk"] --> B["mcp-retroarch"]
B --> C["Node.js runtime"]
C --> D["UDP 网络"]
D --> E["RetroArch NCI"]版本演进
| 版本 | 日期 | 架构变更 |
|---|---|---|
| 0.1.0 | - | 初始版本,支持基础 NCI 命令 |
| 0.1.1 | 2026-05-11 | 非阻塞启动,连接探针异步化 |
| 0.1.2 | 2026-05-15 | 工具描述规范化,Fire-and-forget 语义明确 |
资料来源:CHANGELOG.md:1-35
安全性考虑
通信安全
- 本地连接: 默认连接到
127.0.0.1:55355,仅限本机通信 - 无加密: UDP 协议本身不提供加密,适用于可信网络环境
- 无认证: RetroArch NCI 不提供身份验证机制
沙箱限制
- 内存操作: 限制每次读取最多 4096 字节
- 输入验证: 工具参数在本地进行 schema 校验
- 只写模式确认:
retroarch_write_ram的写入结果需要手动验证
扩展架构可能性
当前限制
| 功能 | 限制原因 | 建议方案 |
|---|---|---|
| 手柄输入 | NCI 不暴露此功能 | 参见 mcp-mgba |
| 保存到指定槽位 | NCI 协议限制 | 使用 state_slot_plus/minus 逐步切换 |
| 截图路径查询 | NCI 未暴露配置参数 | 通过 RetroArch GUI 手动确认 |
模块化扩展
未来扩展可考虑:
- 多实例支持: 实例化多个 RetroArchClient 连接不同主机
- 命令队列: 实现请求队列以支持更高的并发度
- 自动重试: 在 UDP 超时时自动重试丢失的包
来源:https://github.com/dmang-dev/mcp-retroarch / 项目说明书
安装与配置
mcp-retroarch 是一个基于 Model Context Protocol (MCP) 的服务器,通过 RetroArch 的网络控制接口(Network Control Interface,简称 NCI)实现对模拟器的远程控制与内存读写功能。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
概述
mcp-retroarch 是一个基于 Model Context Protocol (MCP) 的服务器,通过 RetroArch 的网络控制接口(Network Control Interface,简称 NCI)实现对模拟器的远程控制与内存读写功能。
本工具允许 MCP 客户端(如 Claude Code、Claude Desktop)通过标准化的 JSON-RPC 协议与运行中的 RetroArch 实例通信,支持内存读取、存档管理、截图、控制模拟器运行状态等操作。
资料来源:README.md
系统架构
┌─────────────────┐ stdio (JSON-RPC) ┌──────────────────┐ UDP :55355 ┌─────────────────┐
│ MCP 客户端 │ ◄──────────────────────► │ mcp-retroarch │ ◄─────────────► │ RetroArch │
│ (Claude Desktop)│ │ (TypeScript) │ │ (游戏模拟器) │
└─────────────────┘ └──────────────────┘ └─────────────────┘
│ │
│ ▼
│ ┌──────────────────┐
│ │ 网络控制接口 │
│ │ (Network Cmd) │
│ └──────────────────┘
mcp-retroarch 充当 MCP 客户端与 RetroArch NCI 之间的桥接层,将 MCP 工具调用转换为 UDP 网络命令。
资料来源:src/index.ts
前置要求
环境要求
| 组件 | 版本要求 |
|---|---|
| Node.js | Node.js 运行时环境 |
| RetroArch | 启用了 Network Commands 的版本 |
| MCP 客户端 | Claude Code 或 Claude Desktop |
资料来源:package.json
安装步骤
方式一:从源码编译
# 克隆仓库
git clone https://github.com/dmang-dev/mcp-retroarch.git
cd mcp-retroarch
# 安装依赖
npm install
# 编译 TypeScript
npm run build
# 可选:链接为全局命令
npm link
开发模式下可以使用 npm run dev 启动监听模式,TypeScript 会自动重新编译。
资料来源:README.md、package.json
方式二:使用 MCP 客户端自动发现
Claude Code 支持直接引用 GitHub 仓库路径进行注册:
claude mcp add retroarch --scope user mcp-retroarch
RetroArch 配置
启用网络控制接口
RetroArch 必须启用 Network Commands 功能才能接收来自 mcp-retroarch 的命令。
#### 方式一:通过 GUI 配置
- 进入 RetroArch 主菜单
- 导航至 Settings → Network → Network Commands
- 将 Network Commands 设置为 ON
- 确认 Network Cmd Port 为
55355(这是默认值)
#### 方式二:通过配置文件配置
编辑 retroarch.cfg 文件,添加或修改以下配置项:
network_cmd_enable = "true"
network_cmd_port = "55355"
配置完成后,启动任意 libretro 核心并加载游戏。NCI 功能在启用后是持续有效的,无需额外脚本加载。
资料来源:README.md
配置参数说明
| 配置项 | 默认值 | 说明 |
|---|---|---|
network_cmd_enable | - | 必须设为 "true" 以启用 NCI |
network_cmd_port | 55355 | UDP 通信端口,必须与 MCP 客户端的 RETROARCH_PORT 环境变量一致 |
MCP 客户端配置
Claude Code
添加 mcp-retroarch 作为用户级 MCP 服务器:
claude mcp add retroarch --scope user mcp-retroarch
验证连接状态:
claude mcp list
# retroarch: mcp-retroarch - ✓ Connected
资料来源:README.md
Claude Desktop
#### 配置文件路径
根据操作系统不同,配置文件位置如下:
| 操作系统 | 配置文件路径 |
|---|---|
| macOS | ~/Library/Application Support/Claude/claude_desktop_config.json |
| Windows | %APPDATA%\Claude\claude_desktop_config.json |
| Linux | ~/.config/Claude/claude_desktop_config.json |
#### 配置示例
编辑对应操作系统的配置文件,添加 mcpServers 条目:
{
"mcpServers": {
"retroarch": {
"command": "mcp-retroarch"
}
}
}
配置完成后,需要重启 Claude Desktop 以使更改生效。
资料来源:README.md
环境变量配置
mcp-retroarch 支持通过环境变量自定义连接参数:
| 环境变量 | 默认值 | 说明 |
|---|---|---|
RETROARCH_HOST | 127.0.0.1 | RetroArch 所在主机的 IP 地址 |
RETROARCH_PORT | 55355 | UDP 端口号,必须与 RetroArch 的 network_cmd_port 匹配 |
设置示例(Linux/macOS):
export RETROARCH_HOST=127.0.0.1
export RETROARCH_PORT=55355
设置示例(Windows PowerShell):
$env:RETROARCH_HOST = "127.0.0.1"
$env:RETROARCH_PORT = "55355"
资料来源:README.md
连接流程
sequenceDiagram
participant MCP as MCP 客户端
participant MCP_RA as mcp-retroarch
participant RA as RetroArch
Note over MCP_RA: 服务器启动 (stdio)
MCP_RA->>RA: 连接探针 (UDP)
RA-->>MCP_RA: 连接成功响应
Note over MCP: 用户调用工具
MCP->>MCP_RA: retroarch_get_status
MCP_RA->>RA: GET_STATUS (UDP)
RA-->>MCP_RA: 状态响应
MCP_RA-->>MCP: JSON-RPC 响应
Note over MCP: 内存读取
MCP->>MCP_RA: retroarch_read_memory
MCP_RA->>RA: READ_CORE_MEMORY (UDP)
RA-->>MCP_RA: 内存数据
MCP_RA-->>MCP: hex dump 响应
mcp-retroarch 在启动时会自动发送一个后台连接探测,如果 RetroArch 不可达,会输出警告信息但不会阻止服务器启动。工具调用会在需要时按需建立连接。
资料来源:src/index.ts、src/retroarch.ts
可用工具一览
配置完成后,以下工具即可通过 MCP 客户端调用:
| 工具名称 | 功能说明 |
|---|---|
retroarch_get_status | 获取模拟器状态(运行/暂停/无内容)和游戏信息 |
retroarch_get_config | 读取 RetroArch 配置参数 |
retroarch_read_memory | 通过系统内存映射读取内存(推荐方式) |
retroarch_read_ram | 通过 CHEEVOS 地址空间读取内存(备用方式) |
retroarch_write_memory | 通过系统内存映射写入内存 |
retroarch_write_ram | 通过 CHEEVOS 地址空间写入内存 |
retroarch_save_state_current | 保存当前存档槽 |
retroarch_load_state_current | 加载当前存档槽 |
retroarch_load_state_slot | 加载指定存档槽 |
retroarch_state_slot_plus | 存档槽指针加一 |
retroarch_state_slot_minus | 存档槽指针减一 |
retroarch_pause_toggle | 切换暂停状态 |
retroarch_frame_advance | 推进一帧 |
retroarch_reset | 重置游戏 |
retroarch_screenshot | 截图并保存到 RetroArch 配置的截图目录 |
retroarch_show_message | 在屏幕上显示通知 |
资料来源:src/tools.ts
常见问题排查
| 问题症状 | 原因与解决方案 |
|---|---|
RetroArch query timed out | 网络命令未启用或端口不匹配。检查 retroarch.cfg 中 network_cmd_enable = "true" 和端口配置是否与 RETROARCH_PORT 环境变量一致。 |
READ_CORE_MEMORY failed: no memory map defined | 加载的 libretro 核心未暴露系统内存映射。尝试使用 retroarch_read_ram(CHEEVOS 方式)。 |
READ_CORE_MEMORY failed: no descriptor for address | 地址不在核心内存映射范围内。可能需要使用不同的核心。 |
| 截图位置与预期不符 | RetroArch 截图保存到配置的截图目录。NCI 不暴露 screenshot_directory 参数,可通过 GUI 查看:Settings → Directory → Screenshot。 |
| 无法保存到指定存档槽 | NCI 协议限制,只能保存到当前选中的槽位。需要通过 state_slot_plus/state_slot_minus 移动槽位指针。 |
资料来源:README.md
开发测试
若需对运行中的 RetroArch 进行冒烟测试,可使用项目提供的测试脚本:
node .scratch/smoke.cjs
此脚本会执行一系列基本操作以验证 mcp-retroarch 与 RetroArch 之间的通信是否正常。
资料来源:README.md
资料来源:[README.md](https://github.com/dmang-dev/mcp-retroarch/blob/main/README.md)
RetroArch 集成
mcp-retroarch 是一个基于 Model Context Protocol (MCP) 的服务器实现,用于将 AI 助手(如 Claude Code、Claude Desktop)与 RetroArch 模拟器进行集成。通过 RetroArch 的 Network Command Interface (NCI),该工具提供了对模拟器内存、存档状态、截图等核心功能的...
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
概述
mcp-retroarch 是一个基于 Model Context Protocol (MCP) 的服务器实现,用于将 AI 助手(如 Claude Code、Claude Desktop)与 RetroArch 模拟器进行集成。通过 RetroArch 的 Network Command Interface (NCI),该工具提供了对模拟器内存、存档状态、截图等核心功能的程序化访问能力。
主要功能包括:
- 读写模拟器内存(支持系统内存映射和 CHEEVOS 成就地址空间两种模式)
- 保存与加载存档状态
- 截图与屏幕消息显示
- 暂停、帧步进、复位等模拟器控制
资料来源:README.md
架构设计
系统架构图
graph TD
subgraph 客户端层
MCP[MCP 客户端<br/>Claude Code / Claude Desktop]
end
subgraph 桥接层
STDIO[stdio 通信]
JSONRPC[JSON-RPC 2.0]
mcp_server[mcp-retroarch<br/>MCP 服务器]
end
subgraph 网络层
UDP[UDP 协议<br/>端口 55355]
end
subgraph 目标层
RA[RetroArch<br/>Network Command Interface]
CORE[libretro Core<br/>+ 游戏 ROM]
end
MCP --> STDIO
STDIO --> JSONRPC
JSONRPC --> mcp_server
mcp_server --> UDP
UDP --> RA
RA --> CORE
style mcp_server fill:#e1f5fe
style RA fill:#fff3e0通信机制
mcp-retroarch 通过 UDP 协议与 RetroArch 的 NCI 进行通信。服务器维护一个序列化查询队列,确保同一时间只有一个查询处于进行中状态,避免并发冲突。
sequenceDiagram
participant MCP as MCP 客户端
participant Server as mcp-retroarch
participant RA as RetroArch NCI
participant Core as libretro Core
MCP->>Server: JSON-RPC 请求
Server->>Server: 检查 pending 状态
alt 无查询进行中
Server->>RA: UDP 命令
RA->>Core: 执行指令
Core-->>RA: 响应数据
RA-->>Server: UDP 响应
Server-->>MCP: JSON-RPC 响应
else 查询进行中
Server-->>MCP: 错误: query already in flight
end源码结构
| 文件 | 职责 |
|---|---|
src/index.ts | MCP 服务器入口,负责初始化连接探针 |
src/retroarch.ts | UDP 通信封装,处理连接、查询、发送逻辑 |
src/tools.ts | MCP 工具定义,描述每个可用命令的输入输出 |
资料来源:src/index.ts()、src/retroarch.ts()、src/tools.ts()
功能模块
内存访问
mcp-retroarch 提供两种内存访问路径,适用于不同的 libretro 核心。
#### READ_CORE_MEMORY(系统内存映射)
通过 retroarch_read_memory 和 retroarch_write_memory 工具访问。这是首选方式,当核心暴露系统内存映射时可用。
支持的地址空间示例:
| 系统 | 地址范围 | 说明 |
|---|---|---|
| SNES | 0x7E0000-0x7FFFFF | WRAM |
| GBA | 0x02000000-0x0203FFFF | EWRAM |
| Genesis | 0xFF0000-0xFFFFFF | 68K RAM |
#### READ_CORE_RAM(CHEEVOS 地址空间)
通过 retroarch_read_ram 和 retroarch_write_ram 工具访问。作为降级方案,当核心不支持内存映射时使用。
重要提示:CHEEVOS 路径与系统内存映射使用不同的地址空间,不可混用。
#### 工具参数对比
| 参数 | read_memory / write_memory | read_ram / write_ram |
|---|---|---|
| 地址空间 | libretro 系统内存映射 | CHEEVOS 成就地址空间 |
| 最大单次传输 | 4096 字节 | 4096 字节 |
| 返回确认 | write_memory 返回写入字节数 | 无确认(fire-and-forget) |
| 地址示例 | 0x7E0000 (SNES) | 因核心而异 |
资料来源:src/tools.ts()
存档管理
graph LR
A[当前槽位] -->|state_slot_plus| B[槽位 +1]
A -->|state_slot_minus| C[槽位 -1]
B -->|save_state_current| D[保存存档]
C -->|save_state_current| D
E[指定槽位] -->|load_state_slot| F[加载存档]| 工具 | 功能 | 备注 |
|---|---|---|
retroarch_save_state_current | 保存到当前槽位 | 覆盖现有存档 |
retroarch_load_state_current | 从当前槽位加载 | |
retroarch_load_state_slot | 从指定槽位加载 | N = 1-10 |
retroarch_state_slot_plus | 槽位指针 +1 | NCI 无直接设置命令 |
retroarch_state_slot_minus | 槽位指针 -1 | NCI 无直接设置命令 |
⚠️ 存档写入为破坏性操作,会无提示覆盖现有存档。建议在修改游戏状态前先调用 retroarch_save_state_current 建立回滚点。
资料来源:src/tools.ts()
模拟器控制
| 工具 | 功能 | 使用场景 |
|---|---|---|
retroarch_pause_toggle | 切换暂停状态 | 内存读写前的状态稳定 |
retroarch_frame_advance | 步进一帧 | 精确帧动画检查 |
retroarch_reset | 硬复位游戏 | 重新开始当前游戏 |
retroarch_get_status | 获取模拟器状态 | 查询运行/暂停状态、已加载 ROM 信息 |
辅助功能
| 工具 | 功能 | 返回值 |
|---|---|---|
retroarch_screenshot | 保存截图 | 截图保存至 RetroArch 配置的截图目录 |
retroarch_show_message | 显示通知 | 在模拟器窗口显示 ~3 秒的 OSD 消息 |
retroarch_get_config | 读取配置参数 | 返回 NAME = VALUE 格式 |
💡 retroarch_show_message 仅支持单行文本,换行符会导致消息截断。建议消息长度控制在 80 字符以内。
资料来源:src/tools.ts()
配置与部署
环境变量
| 变量 | 默认值 | 说明 |
|---|---|---|
RETROARCH_HOST | 127.0.0.1 | RetroArch 主机地址 |
RETROARCH_PORT | 55355 | UDP 端口,需与 network_cmd_port 匹配 |
RetroArch 端配置
方式一:GUI 配置
Settings → Network → Network Commands → ON
确认 Network Cmd Port 为 55355
方式二:配置文件
network_cmd_enable = "true"
network_cmd_port = "55355"
配置完成后启动任意 libretro 核心和游戏,NCI 即处于可用状态。
资料来源:README.md()
MCP 客户端注册
#### Claude Code
claude mcp add retroarch --scope user mcp-retroarch
验证:
claude mcp list
# retroarch: mcp-retroarch - ✓ Connected
#### Claude Desktop
编辑 claude_desktop_config.json,路径因平台而异:
| 平台 | 路径 |
|---|---|
| macOS | ~/Library/Application Support/Claude/claude_desktop_config.json |
| Windows | %APPDATA%\Claude\claude_desktop_config.json |
| Linux | ~/.config/Claude/claude_desktop_config.json |
{
"mcpServers": {
"retroarch": {
"command": "mcp-retroarch"
}
}
}
编辑后需重启 Claude Desktop。
资料来源:README.md()
已测试核心
| 系统 | 核心 | read_memory | read_ram | 说明 |
|---|---|---|---|---|
| Game Boy Advance | mgba_libretro | ✅ | ✅ | 中断向量表可见于 0x0000 |
| NES | mesen_libretro | ✅ | ✅ | 完整 16 位 NES 地址空间 |
| NES | nestopia_libretro | ❌ | ✅ | 仅 CHEEVOS,64KB 限制 |
| SNES | snes9x_libretro | ❌ | ❌ | 待补充 |
资料来源:README.md()
功能支持矩阵
| 功能 | 支持状态 | 说明 |
|---|---|---|
| 内存读写 | ✅ | 两种路径:系统内存映射(优先)和 CHEEVOS(降级) |
| 存档保存/加载 | ✅ | 当前槽位或显式槽位加载 |
| 截图 | ✅ | 保存至配置的截图目录 |
| 暂停/帧步进 | ✅ | 暂停切换 + 单帧步进 |
| 复位 | ✅ | 硬复位当前游戏 |
| 屏幕消息 | ✅ | 约 3 秒 OSD 通知 |
| 游戏手柄输入 | ❌ | NCI 不暴露此功能 |
资料来源:README.md()
故障排除
| 症状 | 原因与解决方案 |
|---|---|
RetroArch query timed out | Network Commands 未启用,或端口不匹配。确认 network_cmd_enable = "true"。UDP 在高负载下可能丢包,重试即可。 |
READ_CORE_MEMORY failed: no memory map defined | 核心未暴露系统内存映射。使用 retroarch_read_ram(CHEEVOS 路径)。SwanStation (PSX) 需使用 read_ram。 |
READ_CORE_MEMORY failed: no descriptor for address | 地址超出核心内存映射范围,或核心不支持该区域。 |
| 截图位置不符合预期 | 截图保存至 RetroArch 配置目录,NCI 不暴露 screenshot_directory。通过 GUI 确认:Settings → Directory → Screenshot。 |
| 无法直接保存到指定槽位 | NCI 限制,只能保存到当前槽位。使用 state_slot_plus/state_slot_minus 步行至目标槽位后再保存。 |
资料来源:README.md()
开发指南
快速开始
npm install
npm run dev # tsc --watch 模式
冒烟测试
node .scratch/smoke.cjs
依赖项
| 依赖 | 版本 | 用途 |
|---|---|---|
@modelcontextprotocol/sdk | ^1.12.0 | MCP 协议实现 |
typescript | ^5.5.0 | 开发依赖 |
资料来源:package.json()
版本历史
v0.1.2 (2026-05-15)
工具描述质量重构,遵循 Glama's Tool Definition Quality Score (TDQS) 标准:
- 所有工具描述重写为 PURPOSE / USAGE / BEHAVIOR / RETURNS 模板
- 明确标注 fire-and-forget 语义
- 破坏性操作(写入、重置、存档加载)均添加警告说明
- 错误条件与返回值格式明确化
资料来源:CHANGELOG.md()
相关项目
| 项目 | 说明 |
|---|---|
| mcp-mgba | GBA 专用集成,通过 mGBA Lua 桥接,支持手柄输入 |
| mcp-pine | PINE 协议模拟器(PCSX2 等),仅内存与存档 |
资料来源:README.md()
外部参考
资料来源:[README.md]()
MCP 工具参考
本页面详细描述 mcp-retroarch 提供的所有 MCP 工具,这些工具通过 RetroArch 的网络控制接口(NCI)实现对模拟器的远程控制与内存读写功能。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
架构概览
mcp-retroarch 作为 MCP 客户端与 RetroArch 实例之间的桥接层,通过标准输入/输出(stdio)上的 JSON-RPC 协议与 MCP 客户端通信,同时使用 UDP 协议与 RetroArch 的 NCI 进行交互。
graph LR
A["MCP 客户端<br/>(Claude Code 等)"] -->|"JSON-RPC<br/>stdio"| B["mcp-retroarch<br/>桥接进程"]
B -->|"UDP :55355"| C["RetroArch<br/>NCI"]
C -->|"游戏 ROM<br/>libretro Core"| D["模拟器环境"]
B -->|"连接探测"| E["后台连接状态"]通信流程
- MCP 客户端通过 stdio 发送 JSON-RPC 请求
- 桥接进程解析请求并转换为 NCI 命令
- 通过 UDP 发送至 RetroArch 并等待响应
- 将响应封装为 JSON-RPC 格式返回 MCP 客户端
资料来源:src/index.ts:1-20
工具分类总览
| 分类 | 工具数量 | 功能描述 |
|---|---|---|
| 状态查询 | 2 | 获取模拟器状态与配置参数 |
| 内存读写 | 4 | 系统内存映射与 CHEEVOS 地址空间访问 |
| 模拟器控制 | 4 | 暂停、帧推进、重置、截图 |
| 存档管理 | 4 | 存档/读档、槽位切换 |
| UI 交互 | 1 | 屏幕消息显示 |
资料来源:src/tools.ts:1-150
状态查询工具
retroarch_get_status
查询 RetroArch 当前运行状态,包括模拟器状态、系统标识、加载游戏及 CRC32 校验值。
用途:在执行内存操作或存档操作前,确认模拟器处于正确的运行状态。
参数:无
返回格式:
State: playing|paused
System: SYSTEM_ID
Game: BASENAME
CRC32: XXXXXXXX|(none reported)
无内容加载时:返回 No content loaded
case "retroarch_get_status": {
const s = await ra.getStatus();
if (s.state === "contentless") return ok("No content loaded");
return ok(
`State: ${s.state}\n` +
`System: ${s.system}\n` +
`Game: ${s.game}\n` +
`CRC32: ${s.crc32 ?? "(none reported)"}`,
);
}
资料来源:src/tools.ts:50-60
retroarch_get_config
读取 RetroArch 配置参数值。
用途:查询 RetroArch 的文件系统路径和运行时设置。
参数:
| 参数名 | 类型 | 必填 | 描述 |
|---|---|---|---|
| name | string | 是 | 配置参数名称 |
行为:
- 仅读取 RetroArch NCI 白名单中的参数
screenshot_directory未被暴露,无法通过此工具查询
返回格式:NAME = VALUE
case "retroarch_get_config": {
const v = await ra.getConfigParam(p.name as string);
return ok(`${p.name} = ${v}`);
}
资料来源:src/tools.ts:62-66
内存读写工具
mcp-retroarch 提供两套独立的内存访问 API,分别对应不同的地址空间:
graph TD
A["内存读取请求"] --> B{目标地址空间?}
B -->|"系统内存映射"| C["READ_CORE_MEMORY<br/>retroarch_read_memory"]
B -->|"CHEEVOS 地址空间"| D["READ_CORE_RAM<br/>retroarch_read_ram"]
C --> E["优先使用<br/>完整系统总线视图"]
D --> F["备选方案<br/>成就系统兼容"]资料来源:src/tools.ts:100-130
retroarch_read_memory
通过系统内存映射读取内存。
用途:首选的内存读取方式,适用于暴露完整系统内存映射的核心。
参数:
| 参数名 | 类型 | 必填 | 描述 |
|---|---|---|---|
| address | integer | 是 | 起始地址(libretro 系统内存映射地址) |
| length | integer | 是 | 读取字节数(1-4096) |
地址空间示例:
- SNES WRAM:
0x7E0000-0x7FFFFF - GBA EWRAM:
0x02000000-0x0203FFFF - Genesis 68K RAM:
0xFF0000-0xFFFFFF
返回格式:
ADDR_HEX [N bytes]:
XX XX XX XX XX XX XX XX ...
case "retroarch_read_memory": {
const bytes = await ra.readMemory(p.address as number, p.length as number);
const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0").toUpperCase()).join(" ");
return ok(`${addrHex(p.address as number)} [${bytes.length} bytes]:\n${hex}`);
}
错误处理:
no memory map defined:核心未暴露内存映射,改用retroarch_read_ramno descriptor for address:地址不在核心描述符范围内
资料来源:src/tools.ts:68-77
retroarch_write_memory
通过系统内存映射写入内存。
用途:内存作弊、游戏状态修改、调试 poke 操作。
参数:
| 参数名 | 类型 | 必填 | 描述 |
|---|---|---|---|
| address | integer | 是 | 起始地址 |
| bytes | integer[] | 是 | 字节值数组(0-255) |
行为:
- 破坏性操作:直接覆盖指定地址,无撤销机制
- 禁用硬核模式:写入后 RetroArch 自动禁用硬核模式
- 返回确认:唯一返回写入字节数确认的工具
case "retroarch_write_memory": {
const n = await ra.writeMemory(p.address as number, p.bytes as number[]);
return ok(`Wrote ${n} bytes → ${addrHex(p.address as number)}`);
}
资料来源:src/tools.ts:79-83
retroarch_read_ram
通过 CHEEVOS 地址空间读取内存。
用途:当 retroarch_read_memory 返回 "no memory map defined" 时的备选方案。
参数:
| 参数名 | 类型 | 必填 | 描述 |
|---|---|---|---|
| address | integer | 是 | CHEEVOS 地址空间起始地址 |
| length | integer | 是 | 读取字节数(1-4096) |
地址空间说明:CHEEVOS 地址遵循 RetroAchievements 规范,与 libretro 系统总线地址不同。例如 SNES CHEEVOS WRAM 地址从 0x000000 开始,而非系统总线上的 0x7E0000。
case "retroarch_read_ram": {
const bytes = await ra.readRam(p.address as number, p.length as number);
const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0").toUpperCase()).join(" ");
return ok(`${addrHex(p.address as number)} [${bytes.length} bytes, CHEEVOS]:\n${hex}`);
}
资料来源:src/tools.ts:85-91
retroarch_write_ram
通过 CHEEVOS 地址空间写入内存。
用途:配合 retroarch_read_ram 使用,作为 retroarch_write_memory 的备选方案。
参数:
| 参数名 | 类型 | 必填 | 描述 |
|---|---|---|---|
| address | integer | 是 | CHEEVOS 地址空间起始地址 |
| bytes | integer[] | 是 | 字节值数组(1-4096 个元素) |
行为:
- 无确认机制:RetroArch NCI 不对此命令返回确认
- 单向传输:即发即忘,无法区分部分写入与完全拒绝
- 验证方式:需通过后续的
retroarch_read_ram验证写入结果
case "retroarch_write_ram": {
await ra.writeRam(p.address as number, p.bytes as number[]);
return ok(`Wrote ${(p.bytes as number[]).length} bytes → ${addrHex(p.address as number)} (CHEEVOS, no ack)`);
}
资料来源:src/tools.ts:93-95
模拟器控制工具
retroarch_pause_toggle
切换 RetroArch 暂停状态。
行为:NCI 仅提供单一切换命令,无独立的暂停/恢复命令。
用途:内存检查或存档操作前,确保模拟器处于已知状态。
case "retroarch_pause_toggle": await ra.pauseToggle(); return ok("Pause toggled");
资料来源:src/tools.ts:97
retroarch_frame_advance
单帧推进。
行为:在暂停状态下执行,推进一帧后恢复暂停。
case "retroarch_frame_advance": await ra.frameAdvance(); return ok("Advanced one frame");
资料来源:src/tools.ts:98
retroarch_reset
硬重置当前游戏。
行为:重新加载当前 ROM,等同于按下重置按钮。
case "retroarch_reset": await ra.reset(); return ok("Game reset");
资料来源:src/tools.ts:99
retroarch_screenshot
保存截图。
行为:截图保存至 RetroArch 配置的截图目录。
限制:NCI 未暴露 screenshot_directory 参数,需通过 RetroArch GUI 确认路径(Settings → Directory → Screenshot)。
case "retroarch_screenshot": await ra.screenshot(); return ok("Screenshot saved to RetroArch's configured screenshot directory");
资料来源:src/tools.ts:100
存档管理工具
槽位模型说明
NCI 协议存在以下限制:
graph LR
A["save_state_current"] -->|"只能写入|当前槽位"| B["槽位指针"]
C["load_state_slot N"] -->|"可指定槽位"| B
D["state_slot_plus<br/>state_slot_minus"] -->|"移动指针"| B
B -->|"GUI 显示"| E["用户需观察确认"]| 操作 | NCI 支持 | 限制说明 |
|---|---|---|
| 保存到当前槽位 | ✅ | 只能写入 GUI 当前选择的槽位 |
| 从指定槽位加载 | ✅ | 可直接指定槽位号 |
| 读取当前槽位号 | ❌ | 无法查询当前槽位,需客户端自行跟踪 |
| 直接保存到指定槽位 | ❌ | 必须通过加减操作移动槽位指针 |
资料来源:src/tools.ts:100-115
retroarch_save_state_current
保存当前游戏状态到当前槽位。
case "retroarch_save_state_current": await ra.saveStateCurrent(); return ok("Saved to current slot");
retroarch_load_state_current
从当前槽位加载游戏状态。
case "retroarch_load_state_current": await ra.loadStateCurrent(); return ok("Loaded from current slot");
retroarch_load_state_slot
从指定槽位加载游戏状态。
参数:
| 参数名 | 类型 | 必填 | 描述 |
|---|---|---|---|
| slot | integer | 是 | 存档槽位号 |
case "retroarch_load_state_slot": await ra.loadStateSlot(p.slot as number); return ok(`Loaded from slot ${p.slot}`);
retroarch_state_slot_plus / retroarch_state_slot_minus
调整当前槽位指针。
用途:由于 NCI 无法直接保存到指定槽位,需通过这两个命令逐步移动槽位指针至目标位置。
case "retroarch_state_slot_plus": await ra.stateSlotPlus(); return ok("Slot +1");
case "retroarch_state_slot_minus": await ra.stateSlotMinus(); return ok("Slot -1");
资料来源:src/tools.ts:102-106
UI 交互工具
retroarch_show_message
在 RetroArch 窗口显示通知消息。
用途:脚本化运行时提示用户注意位置,或确认操作完成。
参数:
| 参数名 | 类型 | 必填 | 描述 |
|---|---|---|---|
| message | string | 是 | 显示的消息内容 |
case "retroarch_show_message": {
await ra.showMessage(p.message as string);
return ok(`Showed: ${p.message}`);
}
资料来源:src/tools.ts:101-104
网络通信层
UDP 查询机制
sequenceDiagram
participant MCP as MCP 客户端
participant Bridge as mcp-retroarch
participant RA as RetroArch NCI
Bridge->>RA: 发送 UDP 命令
Note over Bridge: 设置超时计时器
RA-->>Bridge: 返回响应
Bridge->>Bridge: 清除超时计时器
Bridge-->>MCP: JSON-RPC 响应关键特性:
| 特性 | 描述 |
|---|---|
| 串行查询 | 一次仅允许一个查询进行中 |
| 超时机制 | 默认超时可配置,超时后抛出错误 |
| 异步处理 | 使用 Promise 封装 UDP 双向通信 |
async query(command: string): Promise<Buffer> {
if (!this.socket) await this.connect();
if (this.pending) {
throw new Error("retroarch query already in flight (client is serial)");
}
return new Promise<Buffer>((resolve, reject) => {
let timer: NodeJS.Timeout | null = setTimeout(() => {
this.pending = null;
reject(new Error(
`RetroArch query "${command.split(" ")[0]}" timed out after ${this.timeoutMs}ms ` +
`— is RetroArch running with Network Commands enabled?`,
));
}, this.timeoutMs);
this.pending = (data) => {
if (timer) { clearTimeout(timer); timer = null; }
resolve(data);
};
this.socket!.send(command, this.port, this.host, (err) => {
if (err) {
if (timer) { clearTimeout(timer); timer = null; }
this.pending = null;
reject(err);
}
});
});
}
环境变量配置
| 环境变量 | 默认值 | 用途 |
|---|---|---|
RETROARCH_HOST | 127.0.0.1 | UDP 目标主机 |
RETROARCH_PORT | 55355 | UDP 端口(需与 network_cmd_port 匹配) |
资料来源:README.md:configuration
工具模式参考
只读检查模式
retroarch_get_status → 确认状态
retroarch_read_memory / retroarch_read_ram → 读取内存
状态修改模式
retroarch_save_state_current → 创建回滚点
retroarch_pause_toggle → 暂停模拟
retroarch_write_memory → 修改内存
存档操作模式
retroarch_get_status → 确认游戏已加载
retroarch_state_slot_plus/minus → 调整槽位
retroarch_save_state_current → 保存
或
retroarch_load_state_slot → 加载指定槽位
错误处理
| 错误信息 | 原因与解决 |
|---|---|
RetroArch query timed out | 网络命令未启用或端口不匹配;UDP 在高负载下可能丢包,可重试 |
READ_CORE_MEMORY failed: no memory map defined | 核心未暴露内存映射,改用 retroarch_read_ram |
READ_CORE_MEMORY failed: no descriptor for address | 地址超出核心内存描述符范围 |
retroarch query already in flight | 客户端串行限制,同一时间仅能有一个查询进行 |
资料来源:README.md:troubleshooting
依赖项
{
"@modelcontextprotocol/sdk": "^1.12.0"
}
资料来源:package.json:dependencies
版本历史
| 版本 | 日期 | 主要变更 |
|---|---|---|
| 0.1.2 | 2026-05-15 | 重写所有工具描述,采用 PURPOSE/USAGE/BEHAVIOR/RETURNS 模板 |
| 0.1.1 | 2026-05-11 | 非阻塞启动,后台连接探测 |
| 0.1.0 | - | 初始版本 |
资料来源:CHANGELOG.md:1-50
资料来源:[src/index.ts:1-20]()
支持的模拟器核心
mcp-retroarch 是一个基于 Model Context Protocol (MCP) 的服务器,它通过 RetroArch 的网络控制接口 (Network Control Interface, NCI) 与运行中的模拟器进行通信。该项目本身不实现任何模拟器核心,而是充当 MCP 客户端与 RetroArch 之间的桥接层,支持所有兼容 RetroArch NC...
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
概述
mcp-retroarch 是一个基于 Model Context Protocol (MCP) 的服务器,它通过 RetroArch 的网络控制接口 (Network Control Interface, NCI) 与运行中的模拟器进行通信。该项目本身不实现任何模拟器核心,而是充当 MCP 客户端与 RetroArch 之间的桥接层,支持所有兼容 RetroArch NCI 协议的核心。资料来源:README.md:1-20
核心支持程度取决于两个方面:RetroArch NCI 协议层面的通用支持,以及各 libretro 核心对内存映射的暴露程度。不同的模拟器核心在内存读取/写入功能上存在显著差异。
架构通信流程
graph TD
A["MCP 客户端<br/>(Claude Code 等)"] -->|"JSON-RPC/stdin"| B["mcp-retroarch<br/>服务器进程"]
B -->|"UDP :55355<br/>NCI 命令"| C["RetroArch<br/>网络控制接口"]
C -->|"LOADED CORE<br/>libretro 核心"| D["GBA<br/>mgba_libretro"]
C -->|"LOADED CORE<br/>libretro 核心"| E["NES<br/>mesen_libretro"]
C -->|"LOADED CORE<br/>libretro 核心"| F["SNES<br/>snes9x_libretro"]
C -->|"LOADED CORE<br/>libretro 核心"| G["PSX<br/>SwanStation"]
style B fill:#e1f5fe
style C fill:#fff3e0已验证的核心
核心兼容性总览
| 系统 | 核心 | read_memory | read_ram | 备注 |
|---|---|---|---|---|
| Game Boy Advance | mgba_libretro | ✅ | ✅ | GBA 中断向量表可见于 0x0000 (显示 d3 00 00 ea ...) |
| NES | mesen_libretro | ✅ | ✅ | 全 16 位 NES 地址空间暴露,WRAM 位于 0x0000-0x07FF 并镜像至 0x1FFF,CHEEVOS 限制在前 64 KB |
| NES | nestopia_libretro | ❌ | ✅ | 无内存映射,仅支持 CHEEVOS,64 KB 限制 |
| SNES | snes9x_libretro | ❌ | 待验证 | 需进一步测试 |
| PlayStation | SwanStation | ❌ | ✅ | 需使用 read_ram |
资料来源:README.md:80-100
Game Boy Advance (mgba_libretro)
GBA 核心是测试最充分的核心之一,其内存布局如下:
| 内存区域 | 地址范围 | 大小 | 说明 |
|---|---|---|---|
| IWRAM | 0x03000000-0x03007FFF | 32 KB | 内部工作 RAM |
| EWRAM | 0x02000000-0x0203FFFF | 256 KB | 外部工作 RAM |
| ROM | 动态映射 | 可变 | 游戏卡带 ROM |
| VRAM | 0x06000000-0x06017FFF | 96 KB | 视频 RAM |
GBA 核心通过 READ_CORE_MEMORY 命令完整暴露系统内存映射,中断向量表位于 0x0000 地址空间起始处,可通过 retroarch_read_memory 工具直接读取。资料来源:README.md:84
NES (mesen_libretro)
Mesen 核心是 NES 平台的首选推荐,它提供完整的内存映射支持:
| 地址范围 | 说明 |
|---|---|
0x0000-0x07FF | WRAM (2 KB,内部镜像至 0x0800-0x1FFF) |
0x2000-0x3FFF | PPU 寄存器 (I/O 寄存器) |
0x4000-0x401F | APU 和控制器寄存器 |
Nestopia 核心作为替代方案可用,但不支持 READ_CORE_MEMORY,只能通过 CHEEVOS 地址空间 (read_ram) 访问内存。资料来源:README.md:88-92
PlayStation (SwanStation)
对于 PlayStation 模拟,SwanStation 核心不支持 READ_CORE_MEMORY,需要使用 retroarch_read_ram 通过 CHEEVOS 地址空间读取内存。这种限制要求用户熟悉 PS1 的 CHEEVOS 地址规范。资料来源:README.md:97
内存访问 API 分层
mcp-retroarch 提供两套内存访问接口,选择取决于目标核心的能力:
graph LR
A["MCP 工具调用"] --> B{核心是否支持<br/>系统内存映射?}
B -->|是| C["retroarch_read_memory<br/>retroarch_write_memory"]
B -->|否| D["retroarch_read_ram<br/>retroarch_write_ram"]
C -->|"READ_CORE_MEMORY<br/>WRITE_CORE_MEMORY"| E["libretro 系统<br/>内存映射"]
D -->|"READ_CORE_RAM<br/>WRITE_CORE_RAM"| F["CHEEVOS<br/>地址空间"]READ_CORE_MEMORY / WRITE_CORE_MEMORY
这是首选的内存访问方式,直接映射到 libretro 核心的原生系统内存布局:
- 优势:地址符合硬件实际架构(如 GBA EWRAM 位于
0x02000000) - 限制:仅当核心广告内存映射时可用
- 返回:确认写入的字节数
资料来源:src/tools.ts:60-100
READ_CORE_RAM / WRITE_CORE_RAM
CHEEVOS 地址空间,适用于不暴露系统内存映射的核心:
- 优势:即使核心无内存映射也能工作(RetroAchievements API 广泛支持)
- 限制:地址遵循 CHEEVOS 约定,非原生系统总线地址
- 返回:不确认写入成功(fire-and-forget 语义)
资料来源:src/tools.ts:130-170
功能支持矩阵
| 功能 | GBA (mgba) | NES (Mesen) | NES (Nestopia) | SNES | PSX |
|---|---|---|---|---|---|
| 内存读取 | ✅ 系统映射 | ✅ 系统映射 | ❌ 仅 CHEEVOS | ❌ | ❌ 仅 CHEEVOS |
| 内存写入 | ✅ 系统映射 | ✅ 系统映射 | ❌ | ❌ | ❌ |
| 存档状态 | ✅ | ✅ | ✅ | ✅ | ✅ |
| 截图 | ✅ | ✅ | ✅ | ✅ | ✅ |
| 暂停/帧推进 | ✅ | ✅ | ✅ | ✅ | ✅ |
| 重置 | ✅ | ✅ | ✅ | ✅ | ✅ |
| 屏幕消息 | ✅ | ✅ | ✅ | ✅ | ✅ |
| 手柄输入 | ❌ | ❌ | ❌ | ❌ | ❌ |
资料来源:README.md:100-130
核心选择建议
内存操作场景
graph TD
A["需要内存读写"] --> B{"哪个平台?"}
B -->|GBA| C["使用 mgba_libretro<br/>首选 read_memory"]
B -->|NES| D["使用 mesen_libretro<br/>首选 read_memory"]
B -->|PSX| E["使用 SwanStation<br/>使用 read_ram"]
B -->|其他| F["查阅核心文档<br/>尝试 read_memory 后 fallback"]对于 GBA + 内存操作:强烈推荐 mgba_libretro,它完整暴露内存映射且经过充分测试。
对于 NES + 内存操作:推荐 mesen_libretro 而非 nestopia_libretro,前者支持系统内存映射,后者仅支持 CHEEVOS。
对于 PSX + 内存操作:使用 read_ram (CHEEVOS 路径),SwanStation 不暴露系统内存映射。
资料来源:README.md:90-92
环境配置
核心功能依赖于 RetroArch 网络控制接口的正确配置:
# retroarch.cfg 必要配置
network_cmd_enable = "true"
network_cmd_port = "55355"
| 环境变量 | 默认值 | 说明 |
|---|---|---|
RETROARCH_HOST | 127.0.0.1 | UDP 目标主机 |
RETROARCH_PORT | 55355 | UDP 端口(必须与 retroarch.cfg 中的 network_cmd_port 匹配) |
资料来源:README.md:60-75
已知限制
核心无关限制
- 游戏手柄输入:NCI 协议不暴露手柄输入功能。RetroArch 有独立的 "Remote RetroPad" 核心(UDP 端口 55400),但需要加载特定核心,无法驱动现有模拟核心。GBA 平台的手柄输入可参考 mcp-mgba 项目。资料来源:README.md:130-135
- 存档槽位写入:NCI 协议仅暴露"保存到当前槽位"命令,无法直接指定目标槽位。需使用
state_slot_plus/state_slot_minus遍历到目标槽位后再保存。资料来源:README.md:145-148
- 截图目录查询:NCI 不通过
GET_CONFIG_PARAM暴露screenshot_directory,需通过 RetroArch GUI (Settings → Directory → Screenshot) 确认。资料来源:README.md:140-143
特定核心限制
| 限制 | 影响核心 | 原因 |
|---|---|---|
| 无系统内存映射 | nestopia_libretro | 核心设计选择 |
| 无系统内存映射 | SwanStation | 核心设计选择 |
| 仅支持 CHEEVOS | 多个核心 | 兼容性和硬件限制 |
相关项目
mcp-retroarch 专注于通过 RetroArch NCI 控制模拟器。如需特定平台的高级功能,可参考相关项目:
| 项目 | 平台 | 特性 |
|---|---|---|
| mcp-mgba | Game Boy Advance | 通过 mGBA Lua 桥接,支持手柄输入和截图 |
| mcp-pine | PCSX2 等 | 通过 PINE 协议,支持内存和存档状态 |
资料来源:README.md:150-155
故障排除
| 症状 | 原因/解决方案 |
|---|---|
RetroArch query timed out | 网络命令未启用或端口不匹配,检查 network_cmd_enable = "true" |
READ_CORE_MEMORY failed: no memory map defined | 核心不支持系统内存映射,尝试 retroarch_read_ram |
READ_CORE_MEMORY failed: no descriptor for address | 地址不在核心内存映射范围内,需更换核心或确认地址 |
| Screenshots don't appear | 截图保存至 RetroArch 配置的截图目录,通过 GUI 确认路径 |
资料来源:README.md:110-145
资料来源:[README.md:80-100]()
内存访问机制
mcp-retroarch 通过 RetroArch 的网络控制接口(NCI)与模拟器进行通信,提供对模拟器内存的读写访问能力。该项目实现了两种独立的内存读取路径,分别对应不同的底层 API,适用于不同的应用场景和模拟器核心。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
概述
mcp-retroarch 通过 RetroArch 的网络控制接口(NCI)与模拟器进行通信,提供对模拟器内存的读写访问能力。该项目实现了两种独立的内存读取路径,分别对应不同的底层 API,适用于不同的应用场景和模拟器核心。
mcp-retroarch 的内存访问机制是 MCP 工具服务器的核心功能之一,允许 AI 客户端直接读取和修改模拟器运行时的内存状态,从而实现游戏状态检查、作弊注入、调试辅助等高级功能。
资料来源:README.md
架构概览
┌─────────────────┐ stdio/JSON-RPC ┌──────────────────┐ UDP :55355 ┌─────────────────┐
│ MCP 客户端 │ ◄────────────────────► │ mcp-retroarch │ ◄──────────────► │ RetroArch │
│ (Claude Code) │ │ (Node.js) │ │ NCI 接口 │
└─────────────────┘ └──────────────────┘ └─────────────────┘
│
┌─────────┴─────────┐
│ │
┌─────▼─────┐ ┌─────▼─────┐
│ READ_CORE │ │ READ_CORE │
│ _MEMORY │ │ _RAM │
│ (系统内存) │ │ (CHEEVOS) │
└───────────┘ └───────────┘
两种内存 API 的区分
mcp-retroarch 实现了两套独立的内存访问 API,分别对应不同的底层协议和地址空间:
| 特性 | retroarch_read_memory / retroarch_write_memory | retroarch_read_ram / retroarch_write_ram |
|---|---|---|
| 底层协议 | CMD_CORE_MEMORY(READ_CORE_MEMORY / WRITE_CORE_MEMORY) | READ_CORE_RAM(CHEEVOS API) |
| 地址空间 | libretro 核心声明的系统内存映射 | RetroAchievements 的 CHEEVOS 地址空间 |
| 适用场景 | 需要精确系统总线地址时(如 GBA EWRAM、SNES WRAM) | 核心未暴露系统内存映射时的备用方案 |
| 核心兼容性 | 仅部分核心支持(如 Mesen libretro) | 大多数核心均支持(即使没有内存映射) |
| 写入确认 | 有(返回写入字节数) | 无(fire-and-forget,无确认) |
READ_CORE_MEMORY(系统内存映射)
该 API 通过 libretro 核心声明的系统内存描述符列表进行访问,返回的地址对应真实的系统总线地址。
典型地址布局示例:
| 系统 | 地址范围 | 说明 |
|---|---|---|
| SNES | 0x7E0000-0x7FFFFF | SNES 内部 WRAM |
| GBA | 0x02000000-0x0203FFFF | GBA 外部工作内存(EWRAM) |
| Genesis | 0xFF0000-0xFFFFFF | MC68000 主内存 |
资料来源:src/tools.ts:46-48
READ_CORE_RAM(CHEEVOS 地址空间)
该 API 通过 RetroAchievements 的地址映射进行内存访问,适用于那些没有完整系统内存映射但启用了成就系统的模拟器核心。
CHEEVOS 地址空间与系统总线地址使用不同的映射规则,例如 SNES CHEEVOS 地址空间中 WRAM 起始地址为 0x000000,而非系统总线的 0x7E0000。
资料来源:src/tools.ts:74-78
核心实现
UDP 传输层
内存访问通过 UDP 数据报实现,核心代码位于 RetroArch 类中:
// 连接建立
async connect(): Promise<void> {
const sock = dgram.createSocket("udp4");
// ... socket 初始化逻辑
}
// 查询并等待响应
async query(command: string): Promise<Buffer> {
if (!this.socket) await this.connect();
if (this.pending) {
throw new Error("retroarch query already in flight");
}
// ... 超时处理和响应接收
}
内存读取实现
async readMemory(address: number, length: number): Promise<Buffer> {
const cmd = `READ_CORE_MEMORY ${address} ${length}`;
return this.query(cmd);
}
async readRam(address: number, length: number): Promise<Buffer> {
const cmd = `READ_CORE_RAM ${address} ${length}`;
return this.query(cmd);
}
资料来源:src/retroarch.ts
内存写入实现
两种写入 API 在确认机制上存在关键差异:
| API | 返回值 | 说明 |
|---|---|---|
writeMemory | 写入字节数 | RetroArch 协议会返回实际写入的字节数 |
writeRam | 无确认 | CHEEVOS API 不返回确认,属于 fire-and-forget 模式 |
资料来源:src/tools.ts:58-60
MCP 工具定义
retroarch_read_memory
{
"name": "retroarch_read_memory",
"description": "PURPOSE: 读取系统内存映射中的字节序列...",
"inputSchema": {
"type": "object",
"required": ["address", "length"],
"properties": {
"address": {
"type": "integer",
"minimum": 0,
"description": "libretro 核心系统内存映射中的起始地址..."
},
"length": {
"type": "integer",
"minimum": 1,
"maximum": 4096,
"description": "连续读取的字节数 (1-4096)"
}
}
}
}
retroarch_read_ram
{
"name": "retroarch_read_ram",
"description": "PURPOSE: 通过 CHEEVOS 地址空间读取最多 4096 字节...",
"inputSchema": {
"type": "object",
"required": ["address", "length"],
"properties": {
"address": {
"type": "integer",
"minimum": 0,
"description": "CHEEVOS(成就)地址空间中的起始地址..."
},
"length": {
"type": "integer",
"minimum": 1,
"maximum": 4096
}
}
}
}
资料来源:src/tools.ts
核心兼容性矩阵
| 系统 | 核心 | read_memory | read_ram | 备注 |
|---|---|---|---|---|
| Game Boy Advance | mgba_libretro | ✅ | ✅ | GBA 中断向量表可见于 0x0000 |
| NES | mesen_libretro | ✅ | ✅ | 完整 16 位 NES 地址空间暴露 |
| NES | nestopia_libretro | ❌ 无内存映射 | ✅ | 仅支持 CHEEVOS |
| SNES | snes9x_libretro | ❌ | ❌ | 暂不支持 |
行为特性与限制
单次调用字节限制
所有内存访问工具的单次调用最大字节数均为 4096 字节,这是 RetroArch NCI 单数据报大小的硬性限制。超出此限制的操作需要分块进行:
建议分块大小: 4 KiB (4096 bytes)
对于大型写入操作,请按 4 KiB 分块批量处理
资料来源:src/tools.ts:52
内存区域边界
RetroArch 可能在读取跨越内存区域边界时返回少于请求的字节数,响应中会报告实际返回的字节数。
// 可能返回 fewer bytes than requested
const bytes = await ra.readMemory(address, length);
// bytes.length <= length
Fire-and-forget 语义
除 writeMemory 外,大多数 NCI 命令不会返回确认。返回的成功消息仅表示 UDP 数据报已发送,不表示 RetroArch 已接收或执行了该命令:
RetroArch's NCI doesn't acknowledge most state mutations. Every affected tool's BEHAVIOR section now warns that the success message is a UDP-send confirmation only, NOT verification that RetroArch received or acted on the command.
资料来源:CHANGELOG.md
硬核模式副作用
任何内存写入操作(write_memory 或 write_ram)都会自动禁用 RetroArch 的硬核模式(Hardcore Mode),这是 RetroArch 协议层面的安全机制。
资料来源:src/tools.ts:58-60
状态修改警告
内存写入是破坏性操作,会直接覆盖从 address 开始的 N 个字节,且无撤销功能。建议在执行写入前使用 retroarch_save_state_current 创建还原点:
// 建议的工作流程
await ra.saveStateCurrent(); // 1. 保存还原点
await ra.writeMemory(address, bytes); // 2. 执行写入
// ... 验证操作 ...
await ra.loadStateCurrent(); // 3. 如需回滚
故障排查
| 症状 | 原因 / 解决方案 |
|---|---|
READ_CORE_MEMORY failed: no memory map defined | 加载的 libretro 核心未声明系统内存映射,尝试改用 retroarch_read_ram |
READ_CORE_MEMORY failed: no descriptor for address | 地址不在核心内存描述符覆盖范围内 |
| UDP 查询超时 | 确认 RetroArch 中 Network Commands 已启用,network_cmd_enable = "true" |
| 返回字节数少于请求 | 读取跨越了内存区域边界,这是协议正常行为 |
资料来源:README.md - Troubleshooting
环境配置
| 环境变量 | 默认值 | 说明 |
|---|---|---|
RETROARCH_HOST | 127.0.0.1 | UDP 目标主机 |
RETROARCH_PORT | 55355 | UDP 端口(需与 retroarch.cfg 中的 network_cmd_port 匹配) |
资料来源:[README.md](https://github.com/dmang-dev/mcp-retroarch/blob/main/README.md)
状态管理
mcp-retroarch 通过 UDP 网络协议与 RetroArch 的网络控制接口(NCI)通信,实现对模拟器的状态监控与控制。状态管理涵盖四个核心维度:连接状态、查询状态机、模拟器运行状态以及存档槽位状态。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
概述
mcp-retroarch 通过 UDP 网络协议与 RetroArch 的网络控制接口(NCI)通信,实现对模拟器的状态监控与控制。状态管理涵盖四个核心维度:连接状态、查询状态机、模拟器运行状态以及存档槽位状态。
架构概览
┌─────────────────────────────────────────────────────────────────────┐
│ mcp-retroarch │
│ ┌─────────────┐ ┌──────────────────┐ ┌────────────────────┐ │
│ │ MCP Client │───▶│ tools.ts │───▶│ retroarch.ts │ │
│ │ (Claude等) │◀───│ (工具定义) │◀───│ (NCI通信层) │ │
│ └─────────────┘ └──────────────────┘ └─────────┬─────────┘ │
│ │ │
│ ┌────────▼─────────┐ │
│ │ UDP Socket │ │
│ │ (dgram模块) │ │
│ └────────┬─────────┘ │
└───────────────────────────────────────────────────────┼─────────────┘
│
UDP :55355│
▼
┌─────────────────────┐
│ RetroArch NCI │
│ (网络控制接口) │
└─────────────────────┘
连接状态管理
连接生命周期
RetroArch 类通过 connect() 方法初始化 UDP socket,该方法返回一个 Promise,确保 socket 绑定完成后再进行后续操作。
关键状态字段:
| 字段 | 类型 | 说明 | |
|---|---|---|---|
socket | `dgram.Socket \ | null` | UDP socket 引用 |
pending | `((msg: Buffer) => void) \ | null` | 待处理的查询回调 |
host | string | 目标主机地址 | |
port | number | 目标 UDP 端口 | |
timeoutMs | number | 查询超时时间 |
初始化流程(源码第15-31行):
async connect(): Promise<void> {
return new Promise((resolve, reject) => {
const sock = dgram.createSocket("udp4");
sock.once("error", (err) => reject(err));
sock.bind(0, () => {
sock.on("message", (msg) => {
const cb = this.pending;
if (!cb) return; // 无待处理回调时丢弃
this.pending = null;
cb(msg);
});
sock.on("error", () => { /* 静默忽略后续错误 */ });
this.socket = sock;
resolve();
});
});
}
连接配置
通过环境变量控制连接目标:
| 环境变量 | 默认值 | 说明 |
|---|---|---|
RETROARCH_HOST | 127.0.0.1 | UDP 目标主机 |
RETROARCH_PORT | 55355 | UDP 端口(需与 RetroArch 配置匹配) |
资料来源:README.md:配置段落
断开连接
disconnect(): void {
this.socket?.close();
this.socket = null;
}
资料来源:src/retroarch.ts:disconnect方法
查询状态机
请求-响应模型
mcp-retroarch 实现了一个串行查询状态机:同一时间只允许一个查询在飞行中(in-flight)。这是 UDP 无连接特性的必然要求。
状态转换图:
graph TD
A[空闲] -->|query调用| B[查询中 pending]
B -->|收到响应| A
B -->|超时| C[错误: query timed out]
C -->|重试| A
A -->|query调用| B
B -->|send时socket未连接| D[自动connect]
D --> Bquery 方法实现
async query(command: string): Promise<Buffer> {
if (!this.socket) await this.connect();
if (this.pending) {
throw new Error("retroarch query already in flight (client is serial)");
}
return new Promise<Buffer>((resolve, reject) => {
let timer: NodeJS.Timeout | null = setTimeout(() => {
this.pending = null;
reject(new Error(
`RetroArch query "${command.split(" ")[0]}" timed out after ${this.timeoutMs}ms ` +
`— is RetroArch running with Network Commands enabled?`,
));
}, this.timeoutMs);
this.pending = (data) => {
if (timer) { clearTimeout(timer); timer = null; }
resolve(data);
};
this.socket!.send(command, this.port, this.host, (err) => {
if (err) {
if (timer) { clearTimeout(timer); timer = null; }
this.pending = null;
reject(err);
}
});
});
}
资料来源:src/retroarch.ts:query方法:73-99行
两种发送模式
| 模式 | 方法 | 用途 | 等待响应 |
|---|---|---|---|
| 查询模式 | query() | 读取数据(状态、内存、配置) | ✅ 等待 UDP 响应 |
| 发送模式 | send() | 发送命令(暂停、重置、截图) | ❌ 防火遗忘 |
/** 防火遗忘发送,用于热键类命令 */
async send(command: string): Promise<void> {
if (!this.socket) await this.connect();
return new Promise((resolve, reject) => {
this.socket!.send(command, this.port, this.host, (err) =>
err ? reject(err) : resolve(),
);
});
}
资料来源:src/retroarch.ts:send方法:54-61行
模拟器状态管理
状态查询
通过 getStatus() 方法获取 RetroArch 当前运行状态:
async getStatus(): Promise<EmuStatus> {
const r = (await this.query("GET_STATUS")).toString().trim();
// 响应格式: "GET_STATUS PAUSED system_id,game_basename,crc32=XXXXXXXX"
const m = r.match(/^GET_STATUS\s+(\w+)(?:\s+([^,]+),(.+?)(?:,crc32=([0-9A-Fa-f]+))?)?/);
// ...
}
资料来源:src/retroarch.ts:getStatus方法
状态类型定义
interface EmuStatus {
state: "playing" | "paused" | "contentless";
system?: string; // 系统标识符
game?: string; // 游戏文件名
crc32?: string; // CRC32 校验值
}
状态值说明
| 状态值 | 含义 | 触发条件 |
|---|---|---|
playing | 模拟器正在运行游戏 | 加载 ROM 后未暂停 |
paused | 模拟器暂停 | 用户按暂停或调用 pause_toggle |
contentless | 无内容加载 | RetroArch 在主菜单未加载任何游戏 |
存档槽位状态管理
存档操作命令
| 操作 | 命令 | 方法 | 槽位支持 |
|---|---|---|---|
| 保存当前槽 | SAVE_STATE | saveStateCurrent() | 仅当前槽 |
| 加载当前槽 | LOAD_STATE | loadStateCurrent() | 仅当前槽 |
| 加载指定槽 | LOAD_STATE N | loadStateSlot(n) | 显式槽位号 |
| 槽位+1 | STATE_SLOT_PLUS | stateSlotPlus() | 移动指针 |
| 槽位-1 | STATE_SLOT_MINUS | stateSlotMinus() | 移动指针 |
槽位指针机制
NCI 协议仅暴露当前槽位指针的移动操作,不支持直接跳转到指定槽位:
graph LR
A[槽位0] -->|STATE_SLOT_PLUS| B[槽位1]
B -->|STATE_SLOT_PLUS| C[槽位2]
C -->|STATE_SLOT_MINUS| B
B -->|SAVE_STATE| D[保存到槽位1]工具层实现
case "retroarch_save_state_current":
await ra.saveStateCurrent();
return ok("Saved to current slot");
case "retroarch_load_state_current":
await ra.loadStateCurrent();
return ok("Loaded from current slot");
case "retroarch_load_state_slot":
await ra.loadStateSlot(p.slot as number);
return ok(`Loaded from slot ${p.slot}`);
资料来源:src/tools.ts:存档相关工具:130-136行
内存访问状态
双路径内存读取
| 路径 | 命令 | 用途 | 适用场景 |
|---|---|---|---|
| READ_CORE_MEMORY | readMemory() | 系统内存映射(优先) | 支持 memory map 的核心 |
| READ_CORE_RAM | readRam() | CHEEVOS 地址空间(备用) | 无 memory map 的核心 |
写入确认机制
两种写入方法的确认行为不同:
| 方法 | 返回确认 | 说明 |
|---|---|---|
writeMemory() | ✅ 返回写入字节数 | NCI 会响应写入计数 |
writeRam() | ❌ 无确认 | 纯防火遗忘 |
case "retroarch_write_memory": {
const n = await ra.writeMemory(p.address as number, p.bytes as number[]);
return ok(`Wrote ${n} bytes → ${addrHex(p.address as number)}`);
}
case "retroarch_write_ram": {
await ra.writeRam(p.address as number, p.bytes as number[]);
return ok(`Wrote ${(p.bytes as number[]).length} bytes → ${addrHex(p.address as number)} (CHEEVOS, no ack)`);
}
资料来源:src/tools.ts:内存写入工具:45-55行
错误处理与超时
超时配置
const DEFAULT_TIMEOUT_MS = 5000;
async query(command: string): Promise<Buffer> {
// ... 5秒超时后 reject
}
错误类型
| 错误场景 | 错误信息 | 处理建议 |
|---|---|---|
| 超时 | RetroArch query "XXX" timed out after 5000ms | 确认 RetroArch 运行且网络命令已启用 |
| 并发冲突 | retroarch query already in flight | 等待前一个查询完成 |
| 连接失败 | Socket error | 检查 RETROARCH_HOST/RETROARCH_PORT |
后台连接探测
src/index.ts 在服务启动时发起异步连接探测,失败不影响服务启动:
ra.connect()
.then(() => ra.getVersion())
.then((v) => process.stderr.write(`[mcp-retroarch] connected to ${ra.describeTarget()} — RetroArch ${v}\n`))
.catch((err) => process.stderr.write(
`[mcp-retroarch] note: RetroArch not reachable yet (${ra.describeTarget()}): ${err}\n` +
`Enable Network Commands in retroarch.cfg (network_cmd_enable / network_cmd_port)\n`,
));
资料来源:src/index.ts:启动代码段
状态管理最佳实践
已知状态再操作
由于大多数 NCI 命令为防火遗忘,操作前应先查询状态:
graph TD
A[获取状态] --> B{状态是什么?}
B -->|playing| C[暂停]
B -->|paused| D[确认后再暂停]
C --> E[执行目标操作]
D --> E破坏性操作前存档
// 内存写入前保存当前状态
await ra.saveStateCurrent(); // 建立回滚点
await ra.writeMemory(addr, bytes); // 执行写入
// 出错时加载恢复
await ra.loadStateCurrent();
帧精确控制
// 确保暂停状态
const status = await ra.getStatus();
if (status.state === "playing") {
await ra.pauseToggle();
}
// 单帧步进
await ra.frameAdvance();
总结
mcp-retroarch 的状态管理建立在三个核心机制之上:
- 串行查询状态机:通过
pending回调确保 UDP 响应的正确路由 - 双模式发送:区分需要响应的
query()和防火遗忘的send() - 状态同步:工具层将 NCI 的文本响应解析为结构化状态对象供 MCP 客户端使用
资料来源:[README.md:配置段落]()
故障排除
本文档整理了 mcp-retroarch 在实际使用过程中可能遇到的常见问题、错误信息及其解决方案。mcp-retroarch 通过 RetroArch 的网络控制接口(NCI)与模拟器通信,任何影响网络连接或 NCI 配置的问题都可能导致工具调用失败。
继续阅读本节完整说明和来源证据。
故障排除
本文档整理了 mcp-retroarch 在实际使用过程中可能遇到的常见问题、错误信息及其解决方案。mcp-retroarch 通过 RetroArch 的网络控制接口(NCI)与模拟器通信,任何影响网络连接或 NCI 配置的问题都可能导致工具调用失败。
来源:https://github.com/dmang-dev/mcp-retroarch / 项目说明书
开发指南
本文档面向希望参与 mcp-retroarch 项目开发的工程师,介绍项目架构、本地开发环境配置、核心模块说明以及发布流程。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
项目概述
mcp-retroarch 是一个基于 Model Context Protocol (MCP) 的服务端实现,通过 RetroArch 的 Network Control Interface (NCI) 与模拟器通信。项目以 TypeScript 编写,通过 stdio 与 MCP 客户端交换 JSON-RPC 消息,实现对 RetroArch 的远程控制功能。
核心项目括:
| 功能类别 | 支持情况 | 说明 |
|---|---|---|
| 内存读写 | ✅ | READ_CORE_MEMORY(系统内存映射)和 READ_CORE_RAM(CHEEVOS 地址空间)两种路径 |
| 存档管理 | ✅ | 当前槽位或指定槽位加载;保存仅支持当前槽位 |
| 截图 | ✅ | 保存至 RetroArch 配置的截图目录 |
| 暂停/帧推进 | ✅ | PAUSE_TOGGLE 切换状态;FRAMEADVANCE 单步一帧 |
| 重置 | ✅ | 硬重启当前运行游戏 |
| 屏幕消息 | ✅ | 在 RetroArch 窗口显示通知 |
| 游戏手柄输入 | ❌ | NCI 协议不暴露此功能 |
资料来源:README.md: Features
项目结构
mcp-retroarch/
├── src/
│ ├── index.ts # MCP 服务端入口,stdio 通信循环
│ ├── tools.ts # 工具定义和 JSON Schema
│ └── retroarch.ts # RetroArch NCI 通信层
├── package.json
├── tsconfig.json
├── Dockerfile
├── README.md
├── CHANGELOG.md
├── glama.json # MCP 服务器元数据
└── .scratch/
└── smoke.cjs # 冒烟测试脚本
资料来源:package.json()
环境准备
前提条件
- Node.js >= 18.0.0
- npm >= 9.0.0
- RetroArch 已安装并启用 Network Commands 功能
RetroArch 配置
通过 GUI:
Settings → Network → Network Commands → ON,确认 Network Cmd Port 为 55355(默认值)。
通过 retroarch.cfg:
network_cmd_enable = "true"
network_cmd_port = "55355"
资料来源:README.md: Setup
本地开发
安装依赖
npm install
编译与监听
使用 TypeScript 编译器的 watch 模式进行持续编译:
npm run dev
该命令等价于 tsc --watch,监听 src/ 目录下所有 .ts 文件的变更并自动重新编译。
资料来源:README.md: Development
编译检查
单独运行一次 TypeScript 编译(不监听):
npx tsc
冒烟测试
在 RetroArch 运行状态下,执行项目提供的冒烟测试脚本验证通信:
node .scratch/smoke.cjs
该脚本会尝试连接 RetroArch 并执行基础查询(如 VERSION、GET_STATUS)。
资料来源:README.md: Development
架构设计
系统架构图
graph TD
subgraph "MCP 客户端"
A[Claude Code / Claude Desktop]
end
subgraph "mcp-retroarch"
B[src/index.ts<br/>MCP Server]
C[src/tools.ts<br/>Tool Definitions]
D[src/retroarch.ts<br/>NCI Client]
end
subgraph "RetroArch"
E[Network Commands<br/>UDP :55355]
end
A -->|"stdio JSON-RPC"| B
B -->|"查询工具定义"| C
B -->|"发送 NCI 命令"| D
D -->|"UDP"| E核心模块
#### 1. index.ts — 服务端入口
负责初始化 MCP SDK 并启动 stdio 通信循环。关键逻辑:
- 创建
Server实例,注册所有工具 - 启动背景连接探测(fire-and-forget),尝试连接 RetroArch 并获取版本号
- 监听 stdio 输入并转发至 MCP SDK 处理
// 背景连接探测逻辑
ra.connect()
.then(() => ra.getVersion())
.then((v) => process.stderr.write(`[mcp-retroarch] connected to ${ra.describeTarget()} — RetroArch ${v}\n`))
.catch((err) => process.stderr.write(
`[mcp-retroarch] note: RetroArch not reachable yet (${ra.describeTarget()}): ${err}\n` +
` Enable Network Commands in retroarch.cfg\n`,
));
资料来源:src/index.ts:1-20
#### 2. tools.ts — 工具定义
定义所有 MCP 工具的 JSON Schema,包含:
- 描述字段:PURPOSE / USAGE / BEHAVIOR / RETURNS 模板
- 输入参数:TypeScript 类型约束和取值范围
- 错误处理:明确标注各工具的错误条件和边界情况
工具列表:
| 工具名 | 功能 |
|---|---|
retroarch_get_status | 获取运行状态和游戏信息 |
retroarch_get_config | 读取 RetroArch 配置参数 |
retroarch_read_memory | 读取 libretro 系统内存映射 |
retroarch_read_ram | 读取 CHEEVOS 地址空间 |
retroarch_write_memory | 写入系统内存映射 |
retroarch_write_ram | 写入 CHEEVOS 地址空间 |
retroarch_pause_toggle | 切换暂停状态 |
retroarch_frame_advance | 推进一帧 |
retroarch_reset | 重置游戏 |
retroarch_screenshot | 保存截图 |
retroarch_show_message | 显示通知 |
retroarch_save_state_current | 保存到当前槽位 |
retroarch_load_state_current | 从当前槽位加载 |
retroarch_load_state_slot | 从指定槽位加载 |
retroarch_state_slot_plus | 当前槽位 +1 |
retroarch_state_slot_minus | 当前槽位 -1 |
资料来源:src/tools.ts:1-200
#### 3. retroarch.ts — NCI 通信层
处理与 RetroArch 的 UDP 通信,实现两种发送模式:
| 模式 | 方法 | 用途 |
|---|---|---|
| 发送即忘 | send(command) | 热键类命令,无需等待响应 |
| 查询等待 | query(command) | 需要读取返回值 |
关键特性:
- 序列化保证:同时只能有一个查询在飞,第二个调用会抛出错误
- 超时机制:默认超时时间可配置,超时后拒绝 pending 回调
- Socket 复用:UDP socket 创建后保持连接
async query(command: string): Promise<Buffer> {
if (!this.socket) await this.connect();
if (this.pending) {
throw new Error("retroarch query already in flight (client is serial)");
}
return new Promise<Buffer>((resolve, reject) => {
let timer: NodeJS.Timeout | null = setTimeout(() => {
this.pending = null;
reject(new Error(
`RetroArch query "${command.split(" ")[0]}" timed out after ${this.timeoutMs}ms`,
));
}, this.timeoutMs);
// ...
});
}
配置选项
环境变量
| 环境变量 | 默认值 | 说明 |
|---|---|---|
RETROARCH_HOST | 127.0.0.1 | UDP 目标主机地址 |
RETROARCH_PORT | 55355 | UDP 端口,需与 RetroArch 配置的 network_cmd_port 匹配 |
资料来源:README.md: Configuration
内存读取 API 详解
项目提供两条内存读取路径,理解其区别对正确使用至关重要:
graph LR
A[内存地址] --> B{选择路径}
B -->|"系统内存映射"| C[READ_CORE_MEMORY]
B -->|"CHEEVOS 地址"| D[READ_CORE_RAM]
C --> E[libretro 核心定义<br/>e.g. SNES WRAM 0x7E0000]
D --> F[RetroAchievements 规范<br/>e.g. SNES WRAM 0x000000]| 路径 | 工具 | 地址空间 | 适用场景 |
|---|---|---|---|
| 系统内存映射 | retroarch_read_memory | libretro 核心定义 | 首选方案,支持此路径的核心 |
| CHEEVOS | retroarch_read_ram | RetroAchievements 规范 | 无内存映射的核心(如 SwanStation PSX) |
开发工作流
1. 添加新工具
在 src/tools.ts 中按以下模板添加工具定义:
{
name: "tool_name",
description: "PURPOSE: ...\nUSAGE: ...\nBEHAVIOR: ...\nRETURNS: ...",
inputSchema: {
type: "object",
required: ["param1"],
properties: {
param1: { type: "integer", minimum: 0 },
},
additionalProperties: false,
},
}
2. 实现 NCI 命令
在 src/retroarch.ts 中添加对应方法:
async newCommand(): Promise<ReturnType> {
const r = await this.query("NEW_COMMAND");
return r.toString().trim();
}
3. 在 index.ts 中注册
在 src/index.ts 的 switch 语句中添加处理分支:
case "new_tool_name": {
const result = await ra.newCommand();
return ok(result);
}
4. 更新文档
- 更新
README.md中的工具列表 - 在
CHANGELOG.md的[Unreleased]节添加变更记录
发布流程
版本号管理
项目遵循 Semantic Versioning 规范:
- 主版本号:不兼容的 API 变更
- 次版本号:向后兼容的功能新增
- 修订号:向后兼容的问题修复
Changelog 格式
## [Unreleased]
## [0.1.2] - 2026-05-15
### Changed
- 变更描述
MCP 客户端注册
不同平台的 MCP 客户端注册方式:
| 平台 | 注册命令 |
|---|---|
| Claude Code | claude mcp add retroarch --scope user mcp-retroarch |
| Claude Desktop | 修改 claude_desktop_config.json |
资料来源:README.md: Register with your MCP client
常见问题排查
| 症状 | 原因与解决方案 |
|---|---|
RetroArch query timed out | Network Commands 未启用,或端口不匹配。确认 network_cmd_enable = "true" |
READ_CORE_MEMORY failed: no memory map defined | 核心不暴露内存映射,改用 retroarch_read_ram(CHEEVOS 路径) |
READ_CORE_MEMORY failed: no descriptor for address | 地址不在核心内存映射范围内,需更换核心或确认地址 |
| Screenshots 不在预期位置 | 检查 RetroArch GUI 中 Settings → Directory → Screenshot 的配置 |
资料来源:README.md: Troubleshooting
依赖说明
生产依赖
| 依赖 | 版本 | 用途 |
|---|---|---|
@modelcontextprotocol/sdk | ^1.12.0 | MCP 协议实现 |
开发依赖
| 依赖 | 版本 | 用途 |
|---|---|---|
@types/node | ^22.0.0 | Node.js 类型定义 |
typescript | ^5.5.0 | TypeScript 编译器 |
资料来源:package.json: dependencies & devDependencies
相关项目
| 项目 | 说明 |
|---|---|
| mcp-mgba | Game Boy Advance 支持,包含手柄输入和截图功能 |
| mcp-pine | PINE 协议模拟器(PCSX2 等),仅支持内存和存档 |
资料来源:README.md: Related
资料来源:[README.md: Features]()
失败模式与踩坑日记
保留 Doramagic 在发现、验证和编译中沉淀的项目专属风险,不把社区讨论只当作装饰信息。
安装可能改变本机 AI 工具行为,用户需要知道写入位置和回滚方法。
假设不成立时,用户拿不到承诺的能力。
新项目、停更项目和活跃项目会被混在一起,推荐信任度下降。
下游已经要求复核,不能在页面中弱化。
Pitfall Log / 踩坑日志
项目:dmang-dev/mcp-retroarch
摘要:发现 7 个潜在踩坑项,其中 0 个为 high/blocking;最高优先级:配置坑 - 可能修改宿主 AI 配置。
1. 配置坑 · 可能修改宿主 AI 配置
- 严重度:medium
- 证据强度:source_linked
- 发现:项目面向 Claude/Cursor/Codex/Gemini/OpenCode 等宿主,或安装命令涉及用户配置目录。
- 对用户的影响:安装可能改变本机 AI 工具行为,用户需要知道写入位置和回滚方法。
- 建议检查:列出会写入的配置文件、目录和卸载/回滚步骤。
- 防护动作:涉及宿主配置目录时必须给回滚路径,不能只给安装命令。
- 证据:capability.host_targets | github_repo:1234498337 | https://github.com/dmang-dev/mcp-retroarch | host_targets=mcp_host, claude
2. 能力坑 · 能力判断依赖假设
- 严重度:medium
- 证据强度:source_linked
- 发现:README/documentation is current enough for a first validation pass.
- 对用户的影响:假设不成立时,用户拿不到承诺的能力。
- 建议检查:将假设转成下游验证清单。
- 防护动作:假设必须转成验证项;没有验证结果前不能写成事实。
- 证据:capability.assumptions | github_repo:1234498337 | https://github.com/dmang-dev/mcp-retroarch | README/documentation is current enough for a first validation pass.
3. 维护坑 · 维护活跃度未知
- 严重度:medium
- 证据强度:source_linked
- 发现:未记录 last_activity_observed。
- 对用户的影响:新项目、停更项目和活跃项目会被混在一起,推荐信任度下降。
- 建议检查:补 GitHub 最近 commit、release、issue/PR 响应信号。
- 防护动作:维护活跃度未知时,推荐强度不能标为高信任。
- 证据:evidence.maintainer_signals | github_repo:1234498337 | https://github.com/dmang-dev/mcp-retroarch | last_activity_observed missing
4. 安全/权限坑 · 下游验证发现风险项
- 严重度:medium
- 证据强度:source_linked
- 发现:no_demo
- 对用户的影响:下游已经要求复核,不能在页面中弱化。
- 建议检查:进入安全/权限治理复核队列。
- 防护动作:下游风险存在时必须保持 review/recommendation 降级。
- 证据:downstream_validation.risk_items | github_repo:1234498337 | https://github.com/dmang-dev/mcp-retroarch | no_demo; severity=medium
5. 安全/权限坑 · 存在评分风险
- 严重度:medium
- 证据强度:source_linked
- 发现:no_demo
- 对用户的影响:风险会影响是否适合普通用户安装。
- 建议检查:把风险写入边界卡,并确认是否需要人工复核。
- 防护动作:评分风险必须进入边界卡,不能只作为内部分数。
- 证据:risks.scoring_risks | github_repo:1234498337 | https://github.com/dmang-dev/mcp-retroarch | no_demo; severity=medium
6. 维护坑 · issue/PR 响应质量未知
- 严重度:low
- 证据强度:source_linked
- 发现:issue_or_pr_quality=unknown。
- 对用户的影响:用户无法判断遇到问题后是否有人维护。
- 建议检查:抽样最近 issue/PR,判断是否长期无人处理。
- 防护动作:issue/PR 响应未知时,必须提示维护风险。
- 证据:evidence.maintainer_signals | github_repo:1234498337 | https://github.com/dmang-dev/mcp-retroarch | issue_or_pr_quality=unknown
7. 维护坑 · 发布节奏不明确
- 严重度:low
- 证据强度:source_linked
- 发现:release_recency=unknown。
- 对用户的影响:安装命令和文档可能落后于代码,用户踩坑概率升高。
- 建议检查:确认最近 release/tag 和 README 安装命令是否一致。
- 防护动作:发布节奏未知或过期时,安装说明必须标注可能漂移。
- 证据:evidence.maintainer_signals | github_repo:1234498337 | https://github.com/dmang-dev/mcp-retroarch | release_recency=unknown
来源:Doramagic 发现、验证与编译记录