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:#fff3e0

mcp-retroarch 采用标准的 MCP 服务端架构:服务端通过标准输入输出(stdio)与 MCP 客户端通信,JSON-RPC 2.0 格式封装所有请求和响应。内部通过 RetroArch.ts 模块维护一个 UDP socket,与目标主机上的 RetroArch 实例保持长连接。资料来源:src/index.ts:1-30

核心模块

模块文件路径职责
入口模块src/index.tsMCP 服务端初始化、后台连接探测、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_memoryread_ram备注
Game Boy Advancemgba_libretroGBA 中断向量表在 0x0000 可见
NESmesen_libretro完整 16 位 NES 地址空间暴露
NESnestopia_libretro仅 CHEEVOS,64KB 限制
SNESsnes9x_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_HOST127.0.0.1UDP 目标主机地址
RETROARCH_PORT55355UDP 端口,必须与 RetroArch 配置中的 network_cmd_port 匹配

资料来源:README.md:80-85

RetroArch 端配置

GUI 方式

  1. 进入 Settings → Network → Network Commands → ON
  2. 确认 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 devTypeScript 编译监视模式
node .scratch/smoke.cjs对运行中的 RetroArch 进行冒烟测试

资料来源:package.json:1-30, README.md:115-125

版本历史

项目采用语义化版本控制(Semantic Versioning),当前版本为 0.1.2。主要版本变更:

版本日期关键变更
0.1.22026-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-mgbaGame Boy Advance通过 mGBA Lua 桥接,支持手柄输入和截图
mcp-pinePCSX2 等通过 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)之间的中间层,将高级工具调用转换为底层的 ...

章节 相关页面

继续阅读本节完整说明和来源证据。

章节 各层职责

继续阅读本节完整说明和来源证据。

章节 1. RetroArch 通信模块

继续阅读本节完整说明和来源证据。

章节 2. 工具定义模块

继续阅读本节完整说明和来源证据。

概述

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(),
    );
  });
}

资料来源src/retroarch.ts:38-45

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);
    // ...
  });
}

资料来源src/retroarch.ts:54-78

#### 关键设计特性

特性实现方式说明
串行化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_memorylibretro 系统总线支持内存映射的核心(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_HOST127.0.0.1UDP 目标主机地址
RETROARCH_PORT55355network_cmd_portUDP 端口号(两端必须一致)
truenetwork_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.12026-05-11非阻塞启动,连接探针异步化
0.1.22026-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 手动确认

模块化扩展

未来扩展可考虑:

  1. 多实例支持: 实例化多个 RetroArchClient 连接不同主机
  2. 命令队列: 实现请求队列以支持更高的并发度
  3. 自动重试: 在 UDP 超时时自动重试丢失的包

来源:https://github.com/dmang-dev/mcp-retroarch / 项目说明书

安装与配置

mcp-retroarch 是一个基于 Model Context Protocol (MCP) 的服务器,通过 RetroArch 的网络控制接口(Network Control Interface,简称 NCI)实现对模拟器的远程控制与内存读写功能。

章节 相关页面

继续阅读本节完整说明和来源证据。

章节 环境要求

继续阅读本节完整说明和来源证据。

章节 方式一:从源码编译

继续阅读本节完整说明和来源证据。

章节 方式二:使用 MCP 客户端自动发现

继续阅读本节完整说明和来源证据。

概述

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.jsNode.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.mdpackage.json

方式二:使用 MCP 客户端自动发现

Claude Code 支持直接引用 GitHub 仓库路径进行注册:

claude mcp add retroarch --scope user mcp-retroarch

RetroArch 配置

启用网络控制接口

RetroArch 必须启用 Network Commands 功能才能接收来自 mcp-retroarch 的命令。

#### 方式一:通过 GUI 配置

  1. 进入 RetroArch 主菜单
  2. 导航至 Settings → Network → Network Commands
  3. Network Commands 设置为 ON
  4. 确认 Network Cmd Port55355(这是默认值)

#### 方式二:通过配置文件配置

编辑 retroarch.cfg 文件,添加或修改以下配置项:

network_cmd_enable = "true"
network_cmd_port   = "55355"

配置完成后,启动任意 libretro 核心并加载游戏。NCI 功能在启用后是持续有效的,无需额外脚本加载。

资料来源:README.md

配置参数说明

配置项默认值说明
network_cmd_enable-必须设为 "true" 以启用 NCI
network_cmd_port55355UDP 通信端口,必须与 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_HOST127.0.0.1RetroArch 所在主机的 IP 地址
RETROARCH_PORT55355UDP 端口号,必须与 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.tssrc/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.cfgnetwork_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.tsMCP 服务器入口,负责初始化连接探针
src/retroarch.tsUDP 通信封装,处理连接、查询、发送逻辑
src/tools.tsMCP 工具定义,描述每个可用命令的输入输出

资料来源:src/index.ts()、src/retroarch.ts()、src/tools.ts()

功能模块

内存访问

mcp-retroarch 提供两种内存访问路径,适用于不同的 libretro 核心。

#### READ_CORE_MEMORY(系统内存映射)

通过 retroarch_read_memoryretroarch_write_memory 工具访问。这是首选方式,当核心暴露系统内存映射时可用。

支持的地址空间示例:

系统地址范围说明
SNES0x7E0000-0x7FFFFFWRAM
GBA0x02000000-0x0203FFFFEWRAM
Genesis0xFF0000-0xFFFFFF68K RAM

#### READ_CORE_RAM(CHEEVOS 地址空间)

通过 retroarch_read_ramretroarch_write_ram 工具访问。作为降级方案,当核心不支持内存映射时使用。

重要提示:CHEEVOS 路径与系统内存映射使用不同的地址空间,不可混用。

#### 工具参数对比

参数read_memory / write_memoryread_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槽位指针 +1NCI 无直接设置命令
retroarch_state_slot_minus槽位指针 -1NCI 无直接设置命令
⚠️ 存档写入为破坏性操作,会无提示覆盖现有存档。建议在修改游戏状态前先调用 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_HOST127.0.0.1RetroArch 主机地址
RETROARCH_PORT55355UDP 端口,需与 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_memoryread_ram说明
Game Boy Advancemgba_libretro中断向量表可见于 0x0000
NESmesen_libretro完整 16 位 NES 地址空间
NESnestopia_libretro仅 CHEEVOS,64KB 限制
SNESsnes9x_libretro待补充

资料来源:README.md()

功能支持矩阵

功能支持状态说明
内存读写两种路径:系统内存映射(优先)和 CHEEVOS(降级)
存档保存/加载当前槽位或显式槽位加载
截图保存至配置的截图目录
暂停/帧步进暂停切换 + 单帧步进
复位硬复位当前游戏
屏幕消息约 3 秒 OSD 通知
游戏手柄输入NCI 不暴露此功能

资料来源:README.md()

故障排除

症状原因与解决方案
RetroArch query timed outNetwork 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.0MCP 协议实现
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-mgbaGBA 专用集成,通过 mGBA Lua 桥接,支持手柄输入
mcp-pinePINE 协议模拟器(PCSX2 等),仅内存与存档

资料来源:README.md()

外部参考

资料来源:[README.md]()

MCP 工具参考

本页面详细描述 mcp-retroarch 提供的所有 MCP 工具,这些工具通过 RetroArch 的网络控制接口(NCI)实现对模拟器的远程控制与内存读写功能。

章节 相关页面

继续阅读本节完整说明和来源证据。

章节 通信流程

继续阅读本节完整说明和来源证据。

章节 retroarchgetstatus

继续阅读本节完整说明和来源证据。

章节 retroarchgetconfig

继续阅读本节完整说明和来源证据。

架构概览

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["后台连接状态"]

通信流程

  1. MCP 客户端通过 stdio 发送 JSON-RPC 请求
  2. 桥接进程解析请求并转换为 NCI 命令
  3. 通过 UDP 发送至 RetroArch 并等待响应
  4. 将响应封装为 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 的文件系统路径和运行时设置。

参数

参数名类型必填描述
namestring配置参数名称

行为

  • 仅读取 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

通过系统内存映射读取内存。

用途:首选的内存读取方式,适用于暴露完整系统内存映射的核心。

参数

参数名类型必填描述
addressinteger起始地址(libretro 系统内存映射地址)
lengthinteger读取字节数(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_ram
  • no descriptor for address:地址不在核心描述符范围内

资料来源:src/tools.ts:68-77

retroarch_write_memory

通过系统内存映射写入内存。

用途:内存作弊、游戏状态修改、调试 poke 操作。

参数

参数名类型必填描述
addressinteger起始地址
bytesinteger[]字节值数组(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" 时的备选方案。

参数

参数名类型必填描述
addressintegerCHEEVOS 地址空间起始地址
lengthinteger读取字节数(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 的备选方案。

参数

参数名类型必填描述
addressintegerCHEEVOS 地址空间起始地址
bytesinteger[]字节值数组(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

从指定槽位加载游戏状态。

参数

参数名类型必填描述
slotinteger存档槽位号
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 窗口显示通知消息。

用途:脚本化运行时提示用户注意位置,或确认操作完成。

参数

参数名类型必填描述
messagestring显示的消息内容
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);
      }
    });
  });
}

资料来源:src/retroarch.ts:80-110

环境变量配置

环境变量默认值用途
RETROARCH_HOST127.0.0.1UDP 目标主机
RETROARCH_PORT55355UDP 端口(需与 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.22026-05-15重写所有工具描述,采用 PURPOSE/USAGE/BEHAVIOR/RETURNS 模板
0.1.12026-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...

章节 相关页面

继续阅读本节完整说明和来源证据。

章节 核心兼容性总览

继续阅读本节完整说明和来源证据。

章节 Game Boy Advance (mgbalibretro)

继续阅读本节完整说明和来源证据。

章节 NES (mesenlibretro)

继续阅读本节完整说明和来源证据。

概述

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_memoryread_ram备注
Game Boy Advancemgba_libretroGBA 中断向量表可见于 0x0000 (显示 d3 00 00 ea ...)
NESmesen_libretro全 16 位 NES 地址空间暴露,WRAM 位于 0x0000-0x07FF 并镜像至 0x1FFF,CHEEVOS 限制在前 64 KB
NESnestopia_libretro无内存映射,仅支持 CHEEVOS,64 KB 限制
SNESsnes9x_libretro待验证需进一步测试
PlayStationSwanStation需使用 read_ram

资料来源:README.md:80-100

Game Boy Advance (mgba_libretro)

GBA 核心是测试最充分的核心之一,其内存布局如下:

内存区域地址范围大小说明
IWRAM0x03000000-0x03007FFF32 KB内部工作 RAM
EWRAM0x02000000-0x0203FFFF256 KB外部工作 RAM
ROM动态映射可变游戏卡带 ROM
VRAM0x06000000-0x06017FFF96 KB视频 RAM

GBA 核心通过 READ_CORE_MEMORY 命令完整暴露系统内存映射,中断向量表位于 0x0000 地址空间起始处,可通过 retroarch_read_memory 工具直接读取。资料来源:README.md:84

NES (mesen_libretro)

Mesen 核心是 NES 平台的首选推荐,它提供完整的内存映射支持:

地址范围说明
0x0000-0x07FFWRAM (2 KB,内部镜像至 0x0800-0x1FFF)
0x2000-0x3FFFPPU 寄存器 (I/O 寄存器)
0x4000-0x401FAPU 和控制器寄存器

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)SNESPSX
内存读取✅ 系统映射✅ 系统映射❌ 仅 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_HOST127.0.0.1UDP 目标主机
RETROARCH_PORT55355UDP 端口(必须与 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-mgbaGame Boy Advance通过 mGBA Lua 桥接,支持手柄输入和截图
mcp-pinePCSX2 等通过 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,适用于不同的应用场景和模拟器核心。

章节 相关页面

继续阅读本节完整说明和来源证据。

章节 READCOREMEMORY(系统内存映射)

继续阅读本节完整说明和来源证据。

章节 READCORERAM(CHEEVOS 地址空间)

继续阅读本节完整说明和来源证据。

章节 UDP 传输层

继续阅读本节完整说明和来源证据。

概述

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_memoryretroarch_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,无确认)

资料来源:README.md - Tested cores

READ_CORE_MEMORY(系统内存映射)

该 API 通过 libretro 核心声明的系统内存描述符列表进行访问,返回的地址对应真实的系统总线地址。

典型地址布局示例:

系统地址范围说明
SNES0x7E0000-0x7FFFFFSNES 内部 WRAM
GBA0x02000000-0x0203FFFFGBA 外部工作内存(EWRAM)
Genesis0xFF0000-0xFFFFFFMC68000 主内存

资料来源: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");
  }
  // ... 超时处理和响应接收
}

资料来源:src/retroarch.ts:16-58

内存读取实现

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_memoryread_ram备注
Game Boy Advancemgba_libretroGBA 中断向量表可见于 0x0000
NESmesen_libretro完整 16 位 NES 地址空间暴露
NESnestopia_libretro❌ 无内存映射仅支持 CHEEVOS
SNESsnes9x_libretro暂不支持

资料来源:README.md - Tested cores

行为特性与限制

单次调用字节限制

所有内存访问工具的单次调用最大字节数均为 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_memorywrite_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_HOST127.0.0.1UDP 目标主机
RETROARCH_PORT55355UDP 端口(需与 retroarch.cfg 中的 network_cmd_port 匹配)

资料来源:README.md - Configuration

资料来源:[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`待处理的查询回调
hoststring目标主机地址
portnumber目标 UDP 端口
timeoutMsnumber查询超时时间

初始化流程(源码第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_HOST127.0.0.1UDP 目标主机
RETROARCH_PORT55355UDP 端口(需与 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 --> B

query 方法实现

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_STATEsaveStateCurrent()仅当前槽
加载当前槽LOAD_STATEloadStateCurrent()仅当前槽
加载指定槽LOAD_STATE NloadStateSlot(n)显式槽位号
槽位+1STATE_SLOT_PLUSstateSlotPlus()移动指针
槽位-1STATE_SLOT_MINUSstateSlotMinus()移动指针

槽位指针机制

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_MEMORYreadMemory()系统内存映射(优先)支持 memory map 的核心
READ_CORE_RAMreadRam()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 的状态管理建立在三个核心机制之上:

  1. 串行查询状态机:通过 pending 回调确保 UDP 响应的正确路由
  2. 双模式发送:区分需要响应的 query() 和防火遗忘的 send()
  3. 状态同步:工具层将 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 项目开发的工程师,介绍项目架构、本地开发环境配置、核心模块说明以及发布流程。

章节 相关页面

继续阅读本节完整说明和来源证据。

章节 前提条件

继续阅读本节完整说明和来源证据。

章节 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 Port55355(默认值)。

通过 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 并执行基础查询(如 VERSIONGET_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 通信循环。关键逻辑:

  1. 创建 Server 实例,注册所有工具
  2. 启动背景连接探测(fire-and-forget),尝试连接 RetroArch 并获取版本号
  3. 监听 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);
    // ...
  });
}

资料来源:src/retroarch.ts:30-60

配置选项

环境变量

环境变量默认值说明
RETROARCH_HOST127.0.0.1UDP 目标主机地址
RETROARCH_PORT55355UDP 端口,需与 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_memorylibretro 核心定义首选方案,支持此路径的核心
CHEEVOSretroarch_read_ramRetroAchievements 规范无内存映射的核心(如 SwanStation PSX)

资料来源:src/tools.ts: 内存读写工具描述

开发工作流

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. 更新文档

发布流程

版本号管理

项目遵循 Semantic Versioning 规范:

  • 主版本号:不兼容的 API 变更
  • 次版本号:向后兼容的功能新增
  • 修订号:向后兼容的问题修复

Changelog 格式

## [Unreleased]

## [0.1.2] - 2026-05-15

### Changed
- 变更描述

MCP 客户端注册

不同平台的 MCP 客户端注册方式:

平台注册命令
Claude Codeclaude mcp add retroarch --scope user mcp-retroarch
Claude Desktop修改 claude_desktop_config.json

资料来源:README.md: Register with your MCP client

常见问题排查

症状原因与解决方案
RetroArch query timed outNetwork 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.0MCP 协议实现

开发依赖

依赖版本用途
@types/node^22.0.0Node.js 类型定义
typescript^5.5.0TypeScript 编译器

资料来源:package.json: dependencies & devDependencies

相关项目

项目说明
mcp-mgbaGame Boy Advance 支持,包含手柄输入和截图功能
mcp-pinePINE 协议模拟器(PCSX2 等),仅支持内存和存档

资料来源:README.md: Related

资料来源:[README.md: Features]()

失败模式与踩坑日记

保留 Doramagic 在发现、验证和编译中沉淀的项目专属风险,不把社区讨论只当作装饰信息。

medium 可能修改宿主 AI 配置

安装可能改变本机 AI 工具行为,用户需要知道写入位置和回滚方法。

medium 能力判断依赖假设

假设不成立时,用户拿不到承诺的能力。

medium 维护活跃度未知

新项目、停更项目和活跃项目会被混在一起,推荐信任度下降。

medium 下游验证发现风险项

下游已经要求复核,不能在页面中弱化。

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 发现、验证与编译记录