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

生成时间：2026-05-16 05:01:44 UTC

## 目录

- [项目概述](#project-overview)
- [系统架构](#system-architecture)
- [安装与配置](#installation-configuration)
- [RetroArch 集成](#retroarch-integration)
- [MCP 工具参考](#mcp-tools-reference)
- [支持的模拟器核心](#supported-cores)
- [内存访问机制](#memory-access)
- [状态管理](#state-management)
- [故障排除](#troubleshooting)
- [开发指南](#development-guide)

<a id='project-overview'></a>

## 项目概述

### 相关页面

相关主题：[系统架构](#system-architecture), [安装与配置](#installation-configuration)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [README.md](https://github.com/dmang-dev/mcp-retroarch/blob/main/README.md)
- [src/index.ts](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/index.ts)
- [src/tools.ts](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/tools.ts)
- [src/retroarch.ts](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/retroarch.ts)
- [package.json](https://github.com/dmang-dev/mcp-retroarch/blob/main/package.json)
- [CHANGELOG.md](https://github.com/dmang-dev/mcp-retroarch/blob/main/CHANGELOG.md)
</details>

# 项目概述

## 项目简介

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]()

## 技术架构

### 整体架构图

```mermaid
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.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 实现了两种内存读取路径，以最大化兼容性：

```mermaid
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 协议，这种设计带来了以下特性：

```mermaid
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]()

### 连接管理

后台连接探测采用「即发即忘」模式，不会阻塞服务启动：

```typescript
// 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 方式**：
1. 进入 Settings → Network → Network Commands → **ON**
2. 确认 Network Cmd Port 为 `55355`（默认值）

**配置文件方式**（`retroarch.cfg`）：
```ini
network_cmd_enable = "true"
network_cmd_port   = "55355"
```

资料来源：[README.md:60-70]()

## MCP 客户端集成

### Claude Code

```bash
claude mcp add retroarch --scope user mcp-retroarch
```

验证连接：
```bash
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` |

配置示例：
```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](https://github.com/dmang-dev/mcp-mgba)（GBA 专用） |
| 截图路径无法通过命令查询 | `screenshot_directory` 未通过 `GET_CONFIG_PARAM` 暴露 | 通过 RetroArch GUI 查看：Settings → Directory → Screenshot |

资料来源：[README.md:105-120]()

## 相关项目

| 项目 | 地址 | 说明 |
|------|------|------|
| [mcp-mgba](https://github.com/dmang-dev/mcp-mgba) | Game Boy Advance | 通过 mGBA Lua 桥接，支持手柄输入和截图 |
| [mcp-pine](https://github.com/dmang-dev/mcp-pine) | PCSX2 等 | 通过 PINE 协议通信，仅内存和存档 |

资料来源：[README.md:130-135]()

---

<a id='system-architecture'></a>

## 系统架构

### 相关页面

相关主题：[项目概述](#project-overview), [MCP 工具参考](#mcp-tools-reference)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [README.md](https://github.com/dmang-dev/mcp-retroarch/blob/main/README.md)
- [src/retroarch.ts](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/retroarch.ts)
- [src/tools.ts](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/tools.ts)
- [src/index.ts](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/index.ts)
- [CHANGELOG.md](https://github.com/dmang-dev/mcp-retroarch/blob/main/CHANGELOG.md)
- [package.json](https://github.com/dmang-dev/mcp-retroarch/blob/main/package.json)

</details>

# 系统架构

## 概述

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 采用三层架构设计：

```mermaid
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 通信逻辑。

#### 连接管理

```mermaid
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`

```typescript
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（单向发送）**

适用于状态切换类命令，无需确认响应：

```typescript
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](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/retroarch.ts#L38-L45)

**2. Query-Response（请求-响应）**

适用于需要获取返回数据的命令：

```typescript
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](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/retroarch.ts#L54-L78)

#### 关键设计特性

| 特性 | 实现方式 | 说明 |
|------|----------|------|
| 串行化 | `pending` 状态标志 | 阻止并发查询，确保请求-响应配对正确 |
| 超时控制 | `setTimeout` | 默认超时后清除 pending 并抛出错误 |
| 错误处理 | `sock.once("error")` | Socket 错误直接拒绝 Promise |
| 延迟初始化 | 按需 `connect()` | 首次命令调用时才创建 socket |

### 2. 工具定义模块

**文件位置**: `src/tools.ts`

该模块定义所有 MCP 工具的元数据、输入模式和处理逻辑。

#### 工具注册结构

```typescript
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: {} }
  },
  // ... 其他工具
];
```

#### 工具调用路由

```typescript
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](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/tools.ts)

### 3. MCP 服务器入口

**文件位置**: `src/index.ts`

```typescript
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](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/index.ts)

#### 启动流程

```mermaid
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
```

## 数据流分析

### 工具调用完整流程

```mermaid
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 核心能力提供两条内存读取路径：

```mermaid
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 等） |

## 配置与环境变量

```mermaid
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](https://github.com/dmang-dev/mcp-retroarch/blob/main/README.md#L80-L85)

## 错误处理机制

### 错误分类

| 错误类型 | 触发条件 | 处理方式 |
|----------|----------|----------|
| 连接超时 | UDP 查询超时 | 抛出 `RetroArch query "XXX" timed out` |
| 内存映射缺失 | 核心不暴露系统内存 | 建议使用 `retroarch_read_ram` |
| 地址越界 | 读取地址不在核心描述符范围内 | 返回错误信息 |
| 并发冲突 | 存在未完成的查询时再次发起查询 | 抛出 `retroarch query already in flight` |

### 错误传播流程

```mermaid
graph TD
    A["工具调用"] --> B{"执行查询"}
    B -->|成功| C["格式化响应"]
    B -->|超时| D["抛出 TimeoutError"]
    B -->|核心无映射| E["抛出 MemoryMapError"]
    B -->|地址无效| F["抛出 AddressError"]
    D --> G["返回错误响应"]
    E --> G
    F --> G
    C --> H["返回成功结果"]
```

## 依赖关系

### 项目依赖

```json
{
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.12.0"
  },
  "devDependencies": {
    "@types/node": "^22.0.0",
    "typescript": "^5.5.0"
  }
}
```

**资料来源**：[package.json:15-22](https://github.com/dmang-dev/mcp-retroarch/blob/main/package.json#L15-L22)

### 依赖层级

```mermaid
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](https://github.com/dmang-dev/mcp-retroarch/blob/main/CHANGELOG.md)

## 安全性考虑

### 通信安全

- **本地连接**: 默认连接到 `127.0.0.1:55355`，仅限本机通信
- **无加密**: UDP 协议本身不提供加密，适用于可信网络环境
- **无认证**: RetroArch NCI 不提供身份验证机制

### 沙箱限制

- **内存操作**: 限制每次读取最多 4096 字节
- **输入验证**: 工具参数在本地进行 schema 校验
- **只写模式确认**: `retroarch_write_ram` 的写入结果需要手动验证

## 扩展架构可能性

### 当前限制

| 功能 | 限制原因 | 建议方案 |
|------|----------|----------|
| 手柄输入 | NCI 不暴露此功能 | 参见 [mcp-mgba](https://github.com/dmang-dev/mcp-mgba) |
| 保存到指定槽位 | NCI 协议限制 | 使用 `state_slot_plus/minus` 逐步切换 |
| 截图路径查询 | NCI 未暴露配置参数 | 通过 RetroArch GUI 手动确认 |

### 模块化扩展

未来扩展可考虑：

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

---

## 总结

mcp-retroarch 通过清晰的三层架构实现了 MCP 协议与 RetroArch NCI 之间的高效桥接。核心设计决策包括：

1. **串行 UDP 查询**: 避免并发导致的请求-响应错配
2. **按需连接**: 延迟 socket 创建，减少启动开销
3. **双内存路径**: 兼容不同能力的模拟器核心
4. **Fire-and-forget 语义**: 明确区分有响应和无响应的命令

这种设计使 mcp-retroarch 成为稳定可靠的 RetroArch 控制解决方案，同时保持了代码的简洁性和可维护性。

---

<a id='installation-configuration'></a>

## 安装与配置

### 相关页面

相关主题：[项目概述](#project-overview), [RetroArch 集成](#retroarch-integration)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [README.md](https://github.com/dmang-dev/mcp-retroarch/blob/main/README.md)
- [package.json](https://github.com/dmang-dev/mcp-retroarch/blob/main/package.json)
- [src/index.ts](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/index.ts)
- [src/tools.ts](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/tools.ts)
- [src/retroarch.ts](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/retroarch.ts)
</details>

# 安装与配置

## 概述

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

本工具允许 MCP 客户端（如 Claude Code、Claude Desktop）通过标准化的 JSON-RPC 协议与运行中的 RetroArch 实例通信，支持内存读取、存档管理、截图、控制模拟器运行状态等操作。

资料来源：[README.md](https://github.com/dmang-dev/mcp-retroarch/blob/main/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](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/index.ts)

## 前置要求

### 环境要求

| 组件 | 版本要求 |
|------|---------|
| Node.js | Node.js 运行时环境 |
| RetroArch | 启用了 Network Commands 的版本 |
| MCP 客户端 | Claude Code 或 Claude Desktop |

资料来源：[package.json](https://github.com/dmang-dev/mcp-retroarch/blob/main/package.json)

## 安装步骤

### 方式一：从源码编译

```bash
# 克隆仓库
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](https://github.com/dmang-dev/mcp-retroarch/blob/main/README.md)、[package.json](https://github.com/dmang-dev/mcp-retroarch/blob/main/package.json)

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

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

```bash
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 Port** 为 `55355`（这是默认值）

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

编辑 `retroarch.cfg` 文件，添加或修改以下配置项：

```ini
network_cmd_enable = "true"
network_cmd_port   = "55355"
```

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

资料来源：[README.md](https://github.com/dmang-dev/mcp-retroarch/blob/main/README.md)

### 配置参数说明

| 配置项 | 默认值 | 说明 |
|--------|--------|------|
| `network_cmd_enable` | - | 必须设为 `"true"` 以启用 NCI |
| `network_cmd_port` | `55355` | UDP 通信端口，必须与 MCP 客户端的 `RETROARCH_PORT` 环境变量一致 |

## MCP 客户端配置

### Claude Code

添加 mcp-retroarch 作为用户级 MCP 服务器：

```bash
claude mcp add retroarch --scope user mcp-retroarch
```

验证连接状态：

```bash
claude mcp list
# retroarch: mcp-retroarch - ✓ Connected
```

资料来源：[README.md](https://github.com/dmang-dev/mcp-retroarch/blob/main/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` 条目：

```json
{
  "mcpServers": {
    "retroarch": {
      "command": "mcp-retroarch"
    }
  }
}
```

配置完成后，需要**重启 Claude Desktop** 以使更改生效。

资料来源：[README.md](https://github.com/dmang-dev/mcp-retroarch/blob/main/README.md)

## 环境变量配置

mcp-retroarch 支持通过环境变量自定义连接参数：

| 环境变量 | 默认值 | 说明 |
|---------|--------|------|
| `RETROARCH_HOST` | `127.0.0.1` | RetroArch 所在主机的 IP 地址 |
| `RETROARCH_PORT` | `55355` | UDP 端口号，必须与 RetroArch 的 `network_cmd_port` 匹配 |

设置示例（Linux/macOS）：

```bash
export RETROARCH_HOST=127.0.0.1
export RETROARCH_PORT=55355
```

设置示例（Windows PowerShell）：

```powershell
$env:RETROARCH_HOST = "127.0.0.1"
$env:RETROARCH_PORT = "55355"
```

资料来源：[README.md](https://github.com/dmang-dev/mcp-retroarch/blob/main/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](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/index.ts)、[src/retroarch.ts](https://github.com/dmang-dev/mcp-retroarch/blob/main/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](https://github.com/dmang-dev/mcp-retroarch/blob/main/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](https://github.com/dmang-dev/mcp-retroarch/blob/main/README.md)

## 开发测试

若需对运行中的 RetroArch 进行冒烟测试，可使用项目提供的测试脚本：

```bash
node .scratch/smoke.cjs
```

此脚本会执行一系列基本操作以验证 mcp-retroarch 与 RetroArch 之间的通信是否正常。

资料来源：[README.md](https://github.com/dmang-dev/mcp-retroarch/blob/main/README.md)

---

<a id='retroarch-integration'></a>

## RetroArch 集成

### 相关页面

相关主题：[安装与配置](#installation-configuration), [故障排除](#troubleshooting)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [README.md](https://github.com/dmang-dev/mcp-retroarch/blob/main/README.md)
- [src/tools.ts](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/tools.ts)
- [src/retroarch.ts](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/retroarch.ts)
- [src/index.ts](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/index.ts)
- [package.json](https://github.com/dmang-dev/mcp-retroarch/blob/main/package.json)
- [CHANGELOG.md](https://github.com/dmang-dev/mcp-retroarch/blob/main/CHANGELOG.md)
</details>

# RetroArch 集成

## 概述

mcp-retroarch 是一个基于 Model Context Protocol (MCP) 的服务器实现，用于将 AI 助手（如 Claude Code、Claude Desktop）与 RetroArch 模拟器进行集成。通过 RetroArch 的 Network Command Interface (NCI)，该工具提供了对模拟器内存、存档状态、截图等核心功能的程序化访问能力。

主要功能包括：

- 读写模拟器内存（支持系统内存映射和 CHEEVOS 成就地址空间两种模式）
- 保存与加载存档状态
- 截图与屏幕消息显示
- 暂停、帧步进、复位等模拟器控制

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

## 架构设计

### 系统架构图

```mermaid
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 进行通信。服务器维护一个序列化查询队列，确保同一时间只有一个查询处于进行中状态，避免并发冲突。

```mermaid
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/index.ts)()、[src/retroarch.ts](src/retroarch.ts)()、[src/tools.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](src/tools.ts)()

### 存档管理

```mermaid
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](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](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
```

**方式二：配置文件**

```ini
network_cmd_enable = "true"
network_cmd_port   = "55355"
```

配置完成后启动任意 libretro 核心和游戏，NCI 即处于可用状态。

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

### MCP 客户端注册

#### Claude Code

```bash
claude mcp add retroarch --scope user mcp-retroarch
```

验证：

```bash
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` |

```json
{
  "mcpServers": {
    "retroarch": {
      "command": "mcp-retroarch"
    }
  }
}
```

编辑后需重启 Claude Desktop。

资料来源：[README.md](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](README.md)()

## 功能支持矩阵

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

资料来源：[README.md](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](README.md)()

## 开发指南

### 快速开始

```bash
npm install
npm run dev      # tsc --watch 模式
```

### 冒烟测试

```bash
node .scratch/smoke.cjs
```

### 依赖项

| 依赖 | 版本 | 用途 |
|------|------|------|
| `@modelcontextprotocol/sdk` | ^1.12.0 | MCP 协议实现 |
| `typescript` | ^5.5.0 | 开发依赖 |

资料来源：[package.json](package.json)()

## 版本历史

### v0.1.2 (2026-05-15)

工具描述质量重构，遵循 Glama's Tool Definition Quality Score (TDQS) 标准：

- 所有工具描述重写为 **PURPOSE / USAGE / BEHAVIOR / RETURNS** 模板
- 明确标注 fire-and-forget 语义
- 破坏性操作（写入、重置、存档加载）均添加警告说明
- 错误条件与返回值格式明确化

资料来源：[CHANGELOG.md](CHANGELOG.md)()

## 相关项目

| 项目 | 说明 |
|------|------|
| [mcp-mgba](https://github.com/dmang-dev/mcp-mgba) | GBA 专用集成，通过 mGBA Lua 桥接，支持手柄输入 |
| [mcp-pine](https://github.com/dmang-dev/mcp-pine) | PINE 协议模拟器（PCSX2 等），仅内存与存档 |

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

## 外部参考

- [RetroArch NCI 官方文档](https://docs.libretro.com/development/retroarch/network-control-interface/)

---

<a id='mcp-tools-reference'></a>

## MCP 工具参考

### 相关页面

相关主题：[系统架构](#system-architecture), [内存访问机制](#memory-access), [状态管理](#state-management)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [src/tools.ts](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/tools.ts)
- [src/retroarch.ts](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/retroarch.ts)
- [src/index.ts](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/index.ts)
- [package.json](https://github.com/dmang-dev/mcp-retroarch/blob/main/package.json)
- [README.md](https://github.com/dmang-dev/mcp-retroarch/blob/main/README.md)
- [CHANGELOG.md](https://github.com/dmang-dev/mcp-retroarch/blob/main/CHANGELOG.md)
</details>

# MCP 工具参考

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

## 架构概览

mcp-retroarch 作为 MCP 客户端与 RetroArch 实例之间的桥接层，通过标准输入/输出（stdio）上的 JSON-RPC 协议与 MCP 客户端通信，同时使用 UDP 协议与 RetroArch 的 NCI 进行交互。

```mermaid
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`

```typescript
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`

```typescript
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，分别对应不同的地址空间：

```mermaid
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 ...
```

```typescript
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 操作。

**参数**：

| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| address | integer | 是 | 起始地址 |
| bytes | integer[] | 是 | 字节值数组（0-255） |

**行为**：
- **破坏性操作**：直接覆盖指定地址，无撤销机制
- **禁用硬核模式**：写入后 RetroArch 自动禁用硬核模式
- **返回确认**：唯一返回写入字节数确认的工具

```typescript
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`。

```typescript
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` 验证写入结果

```typescript
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 仅提供单一切换命令，无独立的暂停/恢复命令。

**用途**：内存检查或存档操作前，确保模拟器处于已知状态。

```typescript
case "retroarch_pause_toggle":  await ra.pauseToggle();   return ok("Pause toggled");
```

资料来源：[src/tools.ts:97]()

### retroarch_frame_advance

单帧推进。

**行为**：在暂停状态下执行，推进一帧后恢复暂停。

```typescript
case "retroarch_frame_advance": await ra.frameAdvance();  return ok("Advanced one frame");
```

资料来源：[src/tools.ts:98]()

### retroarch_reset

硬重置当前游戏。

**行为**：重新加载当前 ROM，等同于按下重置按钮。

```typescript
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）。

```typescript
case "retroarch_screenshot":    await ra.screenshot();    return ok("Screenshot saved to RetroArch's configured screenshot directory");
```

资料来源：[src/tools.ts:100]()

## 存档管理工具

### 槽位模型说明

NCI 协议存在以下限制：

```mermaid
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

保存当前游戏状态到当前槽位。

```typescript
case "retroarch_save_state_current":  await ra.saveStateCurrent();          return ok("Saved to current slot");
```

### retroarch_load_state_current

从当前槽位加载游戏状态。

```typescript
case "retroarch_load_state_current":  await ra.loadStateCurrent();          return ok("Loaded from current slot");
```

### retroarch_load_state_slot

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

**参数**：

| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| slot | integer | 是 | 存档槽位号 |

```typescript
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 无法直接保存到指定槽位，需通过这两个命令逐步移动槽位指针至目标位置。

```typescript
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 | 是 | 显示的消息内容 |

```typescript
case "retroarch_show_message": {
  await ra.showMessage(p.message as string);
  return ok(`Showed: ${p.message}`);
}
```

资料来源：[src/tools.ts:101-104]()

## 网络通信层

### UDP 查询机制

```mermaid
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 双向通信 |

```typescript
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_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]()

## 依赖项

```json
{
  "@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]()

---

<a id='supported-cores'></a>

## 支持的模拟器核心

### 相关页面

相关主题：[内存访问机制](#memory-access), [故障排除](#troubleshooting)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [README.md](https://github.com/dmang-dev/mcp-retroarch/blob/main/README.md)
- [src/tools.ts](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/tools.ts)
- [src/retroarch.ts](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/retroarch.ts)
- [src/index.ts](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/index.ts)
- [CHANGELOG.md](https://github.com/dmang-dev/mcp-retroarch/blob/main/CHANGELOG.md)
</details>

# 支持的模拟器核心

## 概述

mcp-retroarch 是一个基于 Model Context Protocol (MCP) 的服务器，它通过 RetroArch 的网络控制接口 (Network Control Interface, NCI) 与运行中的模拟器进行通信。该项目本身不实现任何模拟器核心，而是充当 MCP 客户端与 RetroArch 之间的桥接层，支持所有兼容 RetroArch NCI 协议的核心。资料来源：[README.md:1-20]()

核心支持程度取决于两个方面：RetroArch NCI 协议层面的通用支持，以及各 libretro 核心对内存映射的暴露程度。不同的模拟器核心在内存读取/写入功能上存在显著差异。

## 架构通信流程

```mermaid
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 提供两套内存访问接口，选择取决于目标核心的能力：

```mermaid
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]()

## 核心选择建议

### 内存操作场景

```mermaid
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 网络控制接口的正确配置：

```ini
# 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](https://github.com/dmang-dev/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](https://github.com/dmang-dev/mcp-mgba) | Game Boy Advance | 通过 mGBA Lua 桥接，支持手柄输入和截图 |
| [mcp-pine](https://github.com/dmang-dev/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]()

---

<a id='memory-access'></a>

## 内存访问机制

### 相关页面

相关主题：[MCP 工具参考](#mcp-tools-reference), [支持的模拟器核心](#supported-cores)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [src/retroarch.ts](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/retroarch.ts)
- [src/tools.ts](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/tools.ts)
- [README.md](https://github.com/dmang-dev/mcp-retroarch/blob/main/README.md)
- [CHANGELOG.md](https://github.com/dmang-dev/mcp-retroarch/blob/main/CHANGELOG.md)
- [package.json](https://github.com/dmang-dev/mcp-retroarch/blob/main/package.json)
</details>

# 内存访问机制

## 概述

mcp-retroarch 通过 RetroArch 的网络控制接口（NCI）与模拟器进行通信，提供对模拟器内存的读写访问能力。该项目实现了两种独立的内存读取路径，分别对应不同的底层 API，适用于不同的应用场景和模拟器核心。

mcp-retroarch 的内存访问机制是 MCP 工具服务器的核心功能之一，允许 AI 客户端直接读取和修改模拟器运行时的内存状态，从而实现游戏状态检查、作弊注入、调试辅助等高级功能。

资料来源：[README.md](https://github.com/dmang-dev/mcp-retroarch/blob/main/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，无确认） |

资料来源：[README.md - Tested cores](https://github.com/dmang-dev/mcp-retroarch/blob/main/README.md)

### READ_CORE_MEMORY（系统内存映射）

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

典型地址布局示例：

| 系统 | 地址范围 | 说明 |
|------|----------|------|
| SNES | `0x7E0000-0x7FFFFF` | SNES 内部 WRAM |
| GBA | `0x02000000-0x0203FFFF` | GBA 外部工作内存（EWRAM） |
| Genesis | `0xFF0000-0xFFFFFF` | MC68000 主内存 |

资料来源：[src/tools.ts:46-48](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/tools.ts)

### READ_CORE_RAM（CHEEVOS 地址空间）

该 API 通过 RetroAchievements 的地址映射进行内存访问，适用于那些没有完整系统内存映射但启用了成就系统的模拟器核心。

CHEEVOS 地址空间与系统总线地址使用不同的映射规则，例如 SNES CHEEVOS 地址空间中 WRAM 起始地址为 `0x000000`，而非系统总线的 `0x7E0000`。

资料来源：[src/tools.ts:74-78](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/tools.ts)

## 核心实现

### UDP 传输层

内存访问通过 UDP 数据报实现，核心代码位于 `RetroArch` 类中：

```typescript
// 连接建立
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](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/retroarch.ts)

### 内存读取实现

```typescript
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](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/retroarch.ts)

### 内存写入实现

两种写入 API 在确认机制上存在关键差异：

| API | 返回值 | 说明 |
|-----|--------|------|
| `writeMemory` | 写入字节数 | RetroArch 协议会返回实际写入的字节数 |
| `writeRam` | 无确认 | CHEEVOS API 不返回确认，属于 fire-and-forget 模式 |

资料来源：[src/tools.ts:58-60](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/tools.ts)

## MCP 工具定义

### retroarch_read_memory

```json
{
  "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

```json
{
  "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](https://github.com/dmang-dev/mcp-retroarch/blob/main/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` | ❌ | ❌ | 暂不支持 |

资料来源：[README.md - Tested cores](https://github.com/dmang-dev/mcp-retroarch/blob/main/README.md)

## 行为特性与限制

### 单次调用字节限制

所有内存访问工具的单次调用最大字节数均为 **4096 字节**，这是 RetroArch NCI 单数据报大小的硬性限制。超出此限制的操作需要分块进行：

```
建议分块大小: 4 KiB (4096 bytes)
对于大型写入操作，请按 4 KiB 分块批量处理
```

资料来源：[src/tools.ts:52](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/tools.ts)

### 内存区域边界

RetroArch 可能在读取跨越内存区域边界时返回**少于请求的字节数**，响应中会报告实际返回的字节数。

```typescript
// 可能返回 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](https://github.com/dmang-dev/mcp-retroarch/blob/main/CHANGELOG.md)

### 硬核模式副作用

任何内存写入操作（`write_memory` 或 `write_ram`）都会自动禁用 RetroArch 的硬核模式（Hardcore Mode），这是 RetroArch 协议层面的安全机制。

资料来源：[src/tools.ts:58-60](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/tools.ts)

### 状态修改警告

内存写入是**破坏性操作**，会直接覆盖从 `address` 开始的 N 个字节，且无撤销功能。建议在执行写入前使用 `retroarch_save_state_current` 创建还原点：

```typescript
// 建议的工作流程
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](https://github.com/dmang-dev/mcp-retroarch/blob/main/README.md)

## 环境配置

| 环境变量 | 默认值 | 说明 |
|----------|--------|------|
| `RETROARCH_HOST` | `127.0.0.1` | UDP 目标主机 |
| `RETROARCH_PORT` | `55355` | UDP 端口（需与 `retroarch.cfg` 中的 `network_cmd_port` 匹配） |

资料来源：[README.md - Configuration](https://github.com/dmang-dev/mcp-retroarch/blob/main/README.md)

---

<a id='state-management'></a>

## 状态管理

### 相关页面

相关主题：[MCP 工具参考](#mcp-tools-reference)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [src/retroarch.ts](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/retroarch.ts)
- [src/tools.ts](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/tools.ts)
- [README.md](https://github.com/dmang-dev/mcp-retroarch/blob/main/README.md)
- [package.json](https://github.com/dmang-dev/mcp-retroarch/blob/main/package.json)
</details>

# 状态管理

## 概述

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行）：**

```typescript
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:配置段落]() 

### 断开连接

```typescript
disconnect(): void {
  this.socket?.close();
  this.socket = null;
}
```

资料来源：[src/retroarch.ts:disconnect方法]() 

## 查询状态机

### 请求-响应模型

mcp-retroarch 实现了一个**串行查询状态机**：同一时间只允许一个查询在飞行中（in-flight）。这是 UDP 无连接特性的必然要求。

**状态转换图：**

```mermaid
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 方法实现

```typescript
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()` | 发送命令（暂停、重置、截图） | ❌ 防火遗忘 |

```typescript
/** 防火遗忘发送，用于热键类命令 */
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 当前运行状态：

```typescript
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方法]() 

### 状态类型定义

```typescript
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 协议仅暴露当前槽位指针的移动操作，不支持直接跳转到指定槽位：

```mermaid
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]
```

### 工具层实现

```typescript
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()` | ❌ 无确认 | 纯防火遗忘 |

```typescript
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行]()

## 错误处理与超时

### 超时配置

```typescript
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` 在服务启动时发起异步连接探测，失败不影响服务启动：

```typescript
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 命令为防火遗忘，操作前应先查询状态：

```mermaid
graph TD
    A[获取状态] --> B{状态是什么?}
    B -->|playing| C[暂停]
    B -->|paused| D[确认后再暂停]
    C --> E[执行目标操作]
    D --> E
```

### 破坏性操作前存档

```typescript
// 内存写入前保存当前状态
await ra.saveStateCurrent();           // 建立回滚点
await ra.writeMemory(addr, bytes);     // 执行写入
// 出错时加载恢复
await ra.loadStateCurrent();
```

### 帧精确控制

```typescript
// 确保暂停状态
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 客户端使用

---

<a id='troubleshooting'></a>

## 故障排除

### 相关页面

相关主题：[RetroArch 集成](#retroarch-integration), [支持的模拟器核心](#supported-cores)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [README.md](https://github.com/dmang-dev/mcp-retroarch/blob/main/README.md)
- [src/tools.ts](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/tools.ts)
- [src/retroarch.ts](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/retroarch.ts)
- [src/index.ts](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/index.ts)
- [package.json](https://github.com/dmang-dev/mcp-retroarch/blob/main/package.json)
</details>

# 故障排除

本文档整理了 `mcp-retroarch` 在实际使用过程中可能遇到的常见问题、错误信息及其解决方案。`mcp-retroarch` 通过 RetroArch 的网络控制接口（NCI）与模拟器通信，任何影响网络连接或 NCI 配置的问题都可能导致工具调用失败。

---

## 连接与通信问题

### RetroArch 查询超时

**症状：** 调用任意工具时返回 `RetroArch query timed out` 错误。

**原因分析：**

- RetroArch 端的网络命令（Network Commands）未启用
- `mcp-retroarch` 的 `RETROARCH_PORT` 环境变量与 RetroArch 的 `network_cmd_port` 配置不匹配
- UDP 数据报在高负载下可能被丢弃（即使是本地回环地址）

**排查步骤：**

1. 确认 RetroArch 已启用网络命令功能
2. 验证端口配置一致性
3. 考虑重试机制

**解决方案：**

1. 在 RetroArch GUI 中启用：**Settings → Network → Network Commands → ON**，确认 `Network Cmd Port` 为 `55355`（默认值）
2. 或在 `retroarch.cfg` 中配置：
   ```ini
   network_cmd_enable = "true"
   network_cmd_port   = "55355"
   ```
3. 启动任意 libretro 核心和游戏后，NCI 即处于可用状态，无需额外脚本加载

环境变量配置对照表：

| 环境变量 | 默认值 | 说明 |
|---------|--------|------|
| `RETROARCH_HOST` | `127.0.0.1` | UDP 目标主机 |
| `RETROARCH_PORT` | `55355` | UDP 端口，需与 `retroarch.cfg` 中的 `network_cmd_port` 匹配 |

资料来源：[README.md:40-47](https://github.com/dmang-dev/mcp-retroarch/blob/main/README.md)

---

### RetroArch 不可达时的启动行为

**说明：** `mcp-retroarch` 服务器启动时不会因 RetroArch 不可达而失败，而是采用后台探测机制。

**启动日志示例：**

```
[mcp-retroarch] MCP server ready (stdio)
[mcp-retroarch] connected to 127.0.0.1:55355 — RetroArch <version>
```

如果 RetroArch 尚未就绪，服务器仍会启动并在标准错误输出中打印提示信息：

```
[mcp-retroarch] note: RetroArch not reachable yet (127.0.0.1:55355): <error>
             Enable Network Commands in retroarch.cfg (network_cmd_enable / network_cmd_port)
             or Settings > Network > Network Commands. Tool calls will connect on demand.
```

工具调用会在需要时按需连接，无需重启服务器。

资料来源：[src/index.ts:10-19](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/index.ts)

---

## 内存读取问题

### 错误：no memory map defined

**症状：** 调用 `retroarch_read_memory` 返回 `READ_CORE_MEMORY failed: no memory map defined`。

**原因分析：** 当前加载的 libretro 核心未通过 `READ_CORE_MEMORY` 命令公开系统内存映射。这是核心级别的限制，并非 `mcp-retroarch` 的缺陷。

**解决方案：**

切换到 `retroarch_read_ram`（CHEEVOS 路径）。许多核心即使没有完整的内存映射，也会暴露 CHEEVOS 读写接口。

| 已验证核心 | 推荐工具 | 备注 |
|-----------|----------|------|
| SwanStation (PSX) | `read_ram` | 使用 CHEEVOS 地址空间 |
| Game Boy Advance (mgba_libretro) | `read_memory` | 支持完整内存映射 |
| NES (Mesen) | `read_memory` 或 `read_ram` | 两者均支持 |
| NES (Nestopia) | `read_ram` | 仅 CHEEVOS，无内存映射 |

**核心选择建议：** 对于 NES + 内存映射需求，优先选择 Mesen 核心。

资料来源：[README.md:80-93](https://github.com/dmang-dev/mcp-retroarch/blob/main/README.md)

---

### 错误：no descriptor for address

**症状：** 调用 `retroarch_read_memory` 返回 `READ_CORE_MEMORY failed: no descriptor for address`。

**原因分析：** 请求的地址不在核心内存映射的覆盖范围内。

**可能原因：**

- 该核心不支持访问目标地址区域
- 目标地址属于系统总线之外的区域（如某些核心的视频内存）

**解决方案：**

- 尝试使用 `retroarch_read_ram` 作为替代方案
- 检查目标地址是否在目标平台的已知内存范围内（如 GBA 的 EWRAM 位于 `0x02000000-0x0203FFFF`）
- 考虑更换为支持该地址区域的核心

资料来源：[README.md:94-98](https://github.com/dmang-dev/mcp-retroarch/blob/main/README.md)

---

### 内存读取返回值说明

`retroarch_read_memory` 成功时返回格式如下：

```
ADDR_HEX [N bytes]: <hex dump>
```

其中十六进制数据采用空格分隔的大写两字符格式。

`retroarch_read_ram` 返回格式类似，但包含 `(CHEEVOS)` 标记以区分地址空间：

```
ADDR_HEX [N bytes, CHEEVOS]: <hex dump>
```

**限制：** RetroArch 可能返回少于请求的字节数，特别是在读取跨越内存区域边界时。响应会报告实际返回的字节数。

资料来源：[src/tools.ts:28-33](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/tools.ts)

---

## 存档与状态管理问题

### 无法直接保存到指定存档槽

**症状：** 需要将游戏状态保存到特定的存档槽（如槽位 5），但协议似乎不支持直接指定。

**说明：** 这是 NCI 协议本身的限制，而非 `mcp-retroarch` 的 bug。NCI 仅提供"保存到当前槽"命令，不支持指定槽位号。

**解决方案：**

使用 `state_slot_plus` 和 `state_slot_minus` 逐步移动槽位指针到目标位置，然后调用 `retroarch_save_state_current` 保存。

**操作序列示例：**

1. `retroarch_state_slot_minus` — 减少槽位指针
2. `retroarch_save_state_current` — 保存到当前槽位
3. 重复上述步骤直到到达目标槽位

**注意：** 由于 UDP 的"即发即忘"特性，大多数状态变更命令不会收到 RetroArch 的确认。调用成功仅表示 UDP 数据报已发送，不保证 RetroArch 已接收或执行。

资料来源：[README.md:99-103](https://github.com/dmang-dev/mcp-retroarch/blob/main/README.md)

---

### 存档状态加载后状态不确定

**症状：** 调用 `retroarch_load_state_current` 或 `retroarch_load_state_slot` 后，不确定当前状态是否已正确加载。

**说明：** 这些命令执行的是破坏性操作，会覆盖当前游戏状态。NCI 不返回操作结果确认。

**建议：** 在执行可能覆盖状态的操作前，先使用 `retroarch_save_state_current` 创建还原点。

资料来源：[src/tools.ts:55-56](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/tools.ts)

---

## 截图与文件系统问题

### 截图保存位置不符合预期

**症状：** 调用 `retroarch_screenshot` 后，截图未出现在预期目录。

**原因分析：** RetroArch 会将截图保存到其配置的截图目录，而非项目工作目录或固定路径。

**重要限制：** NCI 协议不通过 `GET_CONFIG_PARAM` 暴露 `screenshot_directory` 参数，因此 `mcp-retroarch` 无法动态获取该路径。

**解决方案：**

1. 在 RetroArch GUI 中确认截图目录：**Settings → Directory → Screenshot**
2. 或通过 `retroarch_get_config` 查看其他已暴露的目录配置（`savestate_directory`、`savefile_directory` 等）

**可用配置查询示例：**

| 配置项 | 返回类型 | 说明 |
|--------|----------|------|
| `screenshot_directory` | ❌ 不可用 | NCI 不暴露此项 |
| `savefile_directory` | 路径 | 存档文件目录 |
| `savestate_directory` | 路径 | 存档状态目录 |
| `system_directory` | 路径 | 系统目录 |

资料来源：[README.md:94-98](https://github.com/dmang-dev/mcp-retroarch/blob/main/README.md)

---

## 模拟器状态问题

### 暂停与帧推进行为

**说明：** RetroArch 的 NCI 仅提供 `PAUSE_TOGGLE` 命令，不提供独立的暂停/恢复命令。

**影响：** 如果不确定当前状态，调用 `PAUSE_TOGGLE` 可能产生非预期结果。

**正确使用方式：**

```mermaid
graph TD
    A[查询 retroarch_get_status] --> B{当前状态?}
    B -->|playing| C[调用 pause_toggle 暂停]
    B -->|paused| D[调用 pause_toggle 恢复]
    C --> E[执行内存操作]
    D --> E
    E --> F[可选: frame_advance 单帧推进]
    F --> G[使用 save_state_current 保存还原点]
```

**帧推进限制：** `FRAMEADVANCE` 命令仅在模拟器处于暂停状态时有效。如果模拟器正在运行，调用此命令不会有任何效果。

资料来源：[src/tools.ts:53-54](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/tools.ts)

---

## 网络与协议层问题

### UDP 通信的固有限制

**特性说明：** RetroArch 的 NCI 基于 UDP 协议，UDP 具有以下固有特性：

| 特性 | 影响 |
|------|------|
| 无连接 | 不建立握手，可能发送成功但对方未收到 |
| 无确认机制 | 大多数命令不会收到 RetroArch 的回复 |
| 可能丢包 | 高负载下数据报可能丢失 |

**工具返回值的含义：**

- `retroarch_write_memory`：唯一返回写入字节数的命令（`Wrote N bytes → ADDR_HEX`）
- 其他状态变更命令：仅表示 UDP 数据报已发送，不代表 RetroArch 已处理

资料来源：[CHANGELOG.md:17-27](https://github.com/dmang-dev/mcp-retroarch/blob/main/CHANGELOG.md)

---

### 命令处理流程

```mermaid
sequenceDiagram
    participant MCP as MCP 客户端
    participant Bridge as mcp-retroarch
    participant RA as RetroArch NCI

    MCP->>Bridge: JSON-RPC 请求
    Bridge->>Bridge: 参数验证
    Bridge->>RA: UDP 命令
    RA-->>Bridge: UDP 响应 (仅查询命令)
    Bridge-->>MCP: JSON-RPC 响应

    Note over Bridge: 即发即忘命令无响应
    MCP->>Bridge: 状态变更命令
    Bridge->>RA: UDP 命令
    Bridge-->>MCP: 发送成功确认
```

资料来源：[src/retroarch.ts:48-57](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/retroarch.ts)

---

## 开发与调试

### 本地开发环境

**项目初始化：**

```bash
npm install
npm run dev      # tsc --watch
```

**烟雾测试：** 可使用测试脚本验证与运行中的 RetroArch 的连接：

```bash
node .scratch/smoke.cjs
```

**依赖信息：**

| 依赖 | 版本 | 用途 |
|------|------|------|
| `@modelcontextprotocol/sdk` | ^1.12.0 | MCP 协议实现 |
| `@types/node` | ^22.0.0 | TypeScript 类型 |
| `typescript` | ^5.5.0 | 编译工具 |

资料来源：[package.json:18-25](https://github.com/dmang-dev/mcp-retroarch/blob/main/package.json)

---

## 快速问题对照表

| 症状 | 最可能的原因 | 首选解决方案 |
|------|-------------|-------------|
| 所有工具超时 | 网络命令未启用 | 启用 Network Commands |
| `read_memory` 失败 | 核心无内存映射 | 改用 `read_ram` |
| `read_memory` 报地址错误 | 地址超出映射范围 | 检查核心文档或换核心 |
| 截图位置不对 | 默认截图目录配置 | 查看 RetroArch GUI 目录设置 |
| 无法保存到指定槽 | NCI 协议限制 | 用 `state_slot_plus/minus` 移动指针 |
| 暂停状态不确定 | 未先查询状态 | 调用 `get_status` 确认后再操作 |

---

## 相关文档

- [RetroArch NCI 官方文档](https://docs.libretro.com/development/retroarch/network-control-interface/)
- [mcp-mgba](https://github.com/dmang-dev/mcp-mgba) — GBA 专用方案（含手柄输入）
- [mcp-pine](https://github.com/dmang-dev/mcp-pine) — PCSX2 等 PINE 协议模拟器

---

<a id='development-guide'></a>

## 开发指南

### 相关页面

相关主题：[项目概述](#project-overview)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [package.json](https://github.com/dmang-dev/mcp-retroarch/blob/main/package.json)
- [src/index.ts](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/index.ts)
- [src/tools.ts](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/tools.ts)
- [src/retroarch.ts](https://github.com/dmang-dev/mcp-retroarch/blob/main/src/retroarch.ts)
- [README.md](https://github.com/dmang-dev/mcp-retroarch/blob/main/README.md)
- [CHANGELOG.md](https://github.com/dmang-dev/mcp-retroarch/blob/main/CHANGELOG.md)
</details>

# 开发指南

本文档面向希望参与 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](https://github.com/dmang-dev/mcp-retroarch/blob/main/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`：**

```ini
network_cmd_enable = "true"
network_cmd_port   = "55355"
```

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

## 本地开发

### 安装依赖

```bash
npm install
```

### 编译与监听

使用 TypeScript 编译器的 watch 模式进行持续编译：

```bash
npm run dev
```

该命令等价于 `tsc --watch`，监听 `src/` 目录下所有 `.ts` 文件的变更并自动重新编译。

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

### 编译检查

单独运行一次 TypeScript 编译（不监听）：

```bash
npx tsc
```

### 冒烟测试

在 RetroArch 运行状态下，执行项目提供的冒烟测试脚本验证通信：

```bash
node .scratch/smoke.cjs
```

该脚本会尝试连接 RetroArch 并执行基础查询（如 `VERSION`、`GET_STATUS`）。

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

## 架构设计

### 系统架构图

```mermaid
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 处理

```typescript
// 背景连接探测逻辑
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 创建后保持连接

```typescript
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_HOST` | `127.0.0.1` | UDP 目标主机地址 |
| `RETROARCH_PORT` | `55355` | UDP 端口，需与 RetroArch 配置的 `network_cmd_port` 匹配 |

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

## 内存读取 API 详解

项目提供两条内存读取路径，理解其区别对正确使用至关重要：

```mermaid
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） |

资料来源：[src/tools.ts: 内存读写工具描述]()

## 开发工作流

### 1. 添加新工具

在 `src/tools.ts` 中按以下模板添加工具定义：

```typescript
{
  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` 中添加对应方法：

```typescript
async newCommand(): Promise<ReturnType> {
  const r = await this.query("NEW_COMMAND");
  return r.toString().trim();
}
```

### 3. 在 index.ts 中注册

在 `src/index.ts` 的 switch 语句中添加处理分支：

```typescript
case "new_tool_name": {
  const result = await ra.newCommand();
  return ok(result);
}
```

### 4. 更新文档

- 更新 `README.md` 中的工具列表
- 在 `CHANGELOG.md` 的 `[Unreleased]` 节添加变更记录

## 发布流程

### 版本号管理

项目遵循 Semantic Versioning 规范：

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

### Changelog 格式

```markdown
## [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](https://github.com/dmang-dev/mcp-mgba) | Game Boy Advance 支持，包含手柄输入和截图功能 |
| [mcp-pine](https://github.com/dmang-dev/mcp-pine) | PINE 协议模拟器（PCSX2 等），仅支持内存和存档 |

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

---

---

## Doramagic 踩坑日志

项目：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

<!-- canonical_name: dmang-dev/mcp-retroarch; human_manual_source: deepwiki_human_wiki -->
