Doramagic Project Pack · Human Manual
mcp-pine
Related topics: System Architecture, Emulator Setup
Project Overview
Related topics: System Architecture, Emulator Setup
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Related Pages
Related topics: System Architecture, Emulator Setup
Project Overview
mcp-pine is a Model Context Protocol (MCP) server that bridges AI coding assistants (primarily Claude) with PINE-enabled PlayStation emulators. It exposes a suite of memory reading, memory writing, and save state management tools, enabling AI agents to introspect and manipulate live game state in real-time.
Project Purpose and Scope
The project serves as an intermediary layer between an AI assistant and emulator internals. Instead of requiring manual memory hunting or external tools, developers and power users can query game state through natural language, with the MCP server translating those requests into PINE protocol calls.
Core capabilities include:
- Reading memory at arbitrary addresses (8/16/32/64-bit values and bulk byte ranges)
- Writing memory values to live game state
- Querying emulator metadata (game title, serial, CRC, status)
- Saving and loading emulator states to named slots
- Supporting multiple emulator backends through a unified interface
Sources: README.md:1
System Architecture
graph TD
subgraph "MCP Host (Claude)"
A[Claude AI Assistant]
end
subgraph "mcp-pine"
B[MCP Server<br/>src/index.ts]
C[Tool Handler<br/>src/tools.ts]
D[PINE Client<br/>src/pine.ts]
end
subgraph "Emulator"
E[PCSX2 / RPCS3 / Duckstation]
F[PINE Server]
end
A -->|MCP Protocol| B
B --> C
C --> D
D -->|PINE Wire Protocol| F
F --> E
style A fill:#e1f5fe
style E fill:#fff3e0
style D fill:#e8f5e9The system consists of three main layers:
| Layer | Component | Purpose |
|---|---|---|
| MCP Host | Claude Desktop / Claude Code | User interface; issues tool calls via MCP |
| Bridge | mcp-pine (Node.js) | Translates MCP requests to PINE protocol; manages connection lifecycle |
| Emulator | PCSX2 / RPCS3 / Duckstation | Executes game; exposes PINE server for IPC |
Sources: README.md:23-51
Supported Emulators
| Emulator | Platform | Status | Notes |
|---|---|---|---|
| PCSX2 | Windows / Linux / macOS | Primary | Full PINE support; default target |
| RPCS3 | Windows / Linux | Compatible | IPC implementation mirrors PINE; wire-level compatibility untested |
| Duckstation | Multi-platform | Compatible | Varies by build; requires PINE-enabled build |
PCSX2 is the reference implementation. It is recommended to use PCSX2 1.7.x Qt or newer with PINE enabled via Settings → Advanced → Enable PINE Server.
Sources: README.md:29-47
MCP Tools Reference
The server exposes 13 MCP tools organized into four functional categories.
Connectivity & Introspection
| Tool | Description |
|---|---|
pine_ping | Returns emulator version string. Use to verify connectivity. |
pine_get_info | Batch-retrieves title, serial, disc CRC, game version, and status in one round-trip. |
pine_get_status | Returns emulator run state: running, paused, shutdown, or unknown. |
Sources: src/tools.ts:45-85
Memory Reads
| Tool | Width | Alignment | Use Case |
|---|---|---|---|
pine_read8 | 8-bit | None | Status flags, counters, single bytes |
pine_read16 | 16-bit | 2-byte | 16-bit counters, flags |
pine_read32 | 32-bit | 4-byte | Pointers, flags, game state values |
pine_read64 | 64-bit | 8-byte | Large IDs, EE pointers, double-word state |
pine_read_range | Variable | Adaptive | Bulk reads up to 4096 bytes; uses largest aligned width at each step |
Important: PCSX2 does not enforce alignment. Unaligned multi-byte reads silently return corrupted data from the aligned address below. Use pine_read_range for unaligned spans.
Sources: src/tools.ts:87-200
Memory Writes
| Tool | Width | Destructive | Notes |
|---|---|---|---|
pine_write8 | 8-bit | Yes | Single-byte overwrite |
pine_write16 | 16-bit | Yes | Requires 2-byte alignment |
pine_write32 | 32-bit | Yes | Requires 4-byte alignment |
pine_write64 | 64-bit | Yes | Requires 8-byte alignment; values encoded as decimal strings |
Sources: src/tools.ts:200-260
Save State Management
| Tool | Description |
|---|---|
pine_save_state | Serializes current emulator state to a numbered slot (0-255). Overwrites existing slot. |
pine_load_state | Restores emulator state from a numbered slot. |
Save state files are stored in emulator-specific locations:
- PCSX2 Windows:
%USERPROFILE%\Documents\PCSX2\sstates - PCSX2 Linux:
~/.config/PCSX2/sstates - Filename format:
<serial> (<crc>).<slot>.p2s
Sources: src/tools.ts:260-310
Configuration
Environment Variables
| Variable | Default | Platform | Purpose |
|---|---|---|---|
PINE_TARGET | pcsx2 | Linux/macOS | Emulator name; used as Unix socket filename prefix (<target>.sock.<slot>) |
PINE_SLOT | 28011 | All | PINE slot number (also port for TCP on Windows) |
PINE_HOST | 127.0.0.1 | Windows | TCP host override |
PINE_SOCKET_PATH | Auto-resolved | Unix | Full socket path override |
PINE_PIPELINE_BATCH | 1 | All | Number of serial reads to batch; higher values risk desyncing PCSX2's PINE server |
Sources: README.md:52-60
Socket Resolution
| Platform | Transport | Path Resolution |
|---|---|---|
| Linux/macOS | Unix Domain Socket | $XDG_RUNTIME_DIR/<target>.sock.<slot> → $TMPDIR/<target>.sock.<slot> → /tmp/<target>.sock.<slot> |
| Windows | TCP | 127.0.0.1:<PINE_SLOT> |
Sources: src/pine.ts:1-80
PINE Protocol Implementation
The PineClient class implements the binary PINE wire format:
sequenceDiagram
participant C as MCP Bridge
participant P as PineClient
participant E as Emulator PINE Server
C->>P: pine_read32(0x00100000)
P->>P: Buffer.alloc(9)<br/>[4-byte size][opcode][4-byte addr]
P->>E: TCP/Unix Socket
E-->>P: Reply frame
P->>P: Parse response
P-->>C: number valueWire format per call:
| Field | Size | Encoding |
|---|---|---|
| Frame size | 4 bytes | UInt32LE (includes opcode + payload) |
| Opcode | 1 byte | UInt8 |
| Payload | Variable | Little-endian |
Opcodes:
| Opcode | Name | Direction | Payload |
|---|---|---|---|
| 0x01 | Version | Read | None |
| 0x02 | Title | Read | None |
| 0x03 | ID | Read | None |
| 0x04 | UUID | Read | None |
| 0x05 | GameVersion | Read | None |
| 0x06 | SaveState | Write | 1 byte (slot) |
| 0x07 | LoadState | Write | 1 byte (slot) |
| 0x08 | Read8 | Read | 4 bytes (address) |
| 0x09 | Read16 | Read | 4 bytes (address) |
| 0x0A | Read32 | Read | 4 bytes (address) |
| 0x0B | Read64 | Read | 4 bytes (address) |
| 0x0C | Write8 | Write | 1 byte (value) + 4 bytes (address) |
| 0x0D | Write16 | Write | 2 bytes (value) + 4 bytes (address) |
| 0x0E | Write32 | Write | 4 bytes (value) + 4 bytes (address) |
| 0x0F | Status | Read | None |
| 0x10 | Write64 | Write | 8 bytes (value) + 4 bytes (address) |
Sources: src/pine.ts:81-200
Installation
Package Distribution
| Method | Command | Use Case |
|---|---|---|
| npm global | npm install -g mcp-pine | System-wide installation |
| npx (on-demand) | npx -y mcp-pine | Temporary testing |
| Source | git clone + npm install | Development |
Sources: README.md:61-72
Claude Desktop Integration
Add to claude_desktop_config.json:
{
"mcpServers": {
"pine": {
"command": "mcp-pine"
}
}
}
Config location by platform:
| Platform | Path |
|---|---|
| macOS | ~/Library/Application Support/Claude/claude_desktop_config.json |
| Windows | %APPDATA%\Claude\claude_desktop_config.json |
| Linux | ~/.config/Claude/claude_desktop_config.json |
Sources: README.md:14-35
Technical Constraints and Known Issues
PCSX2 PINE Server Fragility
PCSX2's PINE server has a fragile request queue. Dropping any single request (which occurs silently when too many requests are pipelined) desyncs the reply pipeline. Once desynced, every subsequent reply is misaligned, causing all tools to timeout until the emulator is restarted.
| Setting | Value | Latency | Safety |
|---|---|---|---|
PINE_PIPELINE_BATCH=1 | Serial | ~52ms for 4096 bytes | Safe (default) |
PINE_PIPELINE_BATCH=2+ | Batched | Lower | Risk of desync |
The default serial mode completes a full 4KB read in under two emulated frames, making pipelining unnecessary for most use cases.
Sources: CHANGELOG.md:15-40
Troubleshooting Reference
| Symptom | Cause | Fix |
|---|---|---|
Cannot reach PINE server | Emulator not running, PINE disabled, port mismatch | Check PINE_SLOT and emulator settings |
PINE FAIL response (0xFF) | No game loaded, address unmapped | Load a game; verify address range |
| Reads return zeros | Address in unallocated region | Try 0x00100000 (EE main RAM) |
| Values look corrupted | Endianness mismatch | PINE returns little-endian |
PINE call timed out (10s) after heavy use | PCSX2 PINE server desynced | Fully restart PCSX2 |
Sources: README.md:95-120
Dependencies
{
"dependencies": {
"@modelcontextprotocol/sdk": "^1.12.0"
},
"devDependencies": {
"@types/node": "^22.0.0",
"typescript": "^5.5.0"
}
}
The project targets Node.js and uses TypeScript for type safety. The @modelcontextprotocol/sdk package provides the MCP server infrastructure.
Sources: package.json:1-30
Related Projects
| Project | Repository | Description |
|---|---|---|
| mcp-mgba | dmang-dev/mcp-mgba | Sister MCP server for mGBA (Game Boy Advance); includes button input and screenshot support |
| PINE Protocol | GovanifY/pine | Underlying IPC specification |
Sources: README.md:140-145
Sources: README.md:1
Installation Guide
Related topics: Configuration Reference, Troubleshooting Guide
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Related Pages
Related topics: Configuration Reference, Troubleshooting Guide
Installation Guide
This guide covers all supported methods for installing and configuring mcp-pine, a Model Context Protocol (MCP) server that bridges MCP clients to emulators via the PINE protocol.
Overview
mcp-pine enables AI assistants like Claude to interact with PlayStation emulators (PCSX2, RPCS3, Duckstation) through the PINE IPC protocol. It exposes memory read/write tools, save state management, and emulator introspection as MCP tools.
| Component | Technology | Purpose |
|---|---|---|
| MCP Server | TypeScript + Node.js | Bridges MCP clients to PINE protocol |
| Transport | stdio | Standard MCP communication |
| Emulator IPC | PINE protocol | Binary memory/state access |
| Connections | TCP/Unix Socket | Platform-specific transport |
Sources: README.md:1-10
Installation Methods
Method 1: Global npm Install
Install globally via npm for system-wide access:
npm install -g mcp-pine
This makes mcp-pine available as a command-line tool accessible from any terminal session.
Sources: README.md:44-47
Method 2: Direct Execution with npx
Run without installation using npx:
npx -y mcp-pine
This downloads and executes the package temporarily, useful for testing or one-off usage.
Sources: README.md:49-51
Method 3: Clone and Develop
For development or customization:
git clone https://github.com/dmang-dev/mcp-pine
cd mcp-pine
npm install # also runs the build via the `prepare` hook
For live development with TypeScript watching:
npm run dev # tsc --watch
Sources: README.md:53-60
Emulator Setup
PCSX2 (Recommended)
PCSX2 1.7.x and newer include native PINE server support:
- Launch PCSX2 (1.7.x Qt or newer)
- Navigate to Settings → Advanced → Enable PINE Server
- Default slot is 28011
Note: If the option is in a different submenu, search settings for "PINE".
Once enabled, PINE is always-on—no scripts or console commands required. Load any game to start interacting.
Sources: README.md:23-33
RPCS3
RPCS3 uses an IPC implementation that mirrors PINE's opcode set:
- Navigate to Configuration → Advanced → Enable IPC server
- Note the configured port
- Run with:
PINE_TARGET=rpcs3 PINE_SLOT=<port> mcp-pine
Note: Wire-level compatibility with RPCS3 hasn't been thoroughly tested. File issues if problems occur.
Sources: README.md:35-44
Duckstation
Duckstation builds vary in PINE support:
- Verify your build includes a PINE server
- Set
PINE_TARGET=duckstation PINE_SLOT=<port>
Sources: README.md:46-48
MCP Client Registration
Claude Code (CLI)
Register for user-wide access:
claude mcp add pine --scope user mcp-pine
Verify the connection:
claude mcp list
# pine: mcp-pine - ✓ Connected
Sources: README.md:11-17
Claude Desktop
Edit the configuration file for your platform:
| Platform | Path |
|---|---|
| macOS | ~/Library/Application Support/Claude/claude_desktop_config.json |
| Windows | %APPDATA%\Claude\claude_desktop_config.json |
| Linux | ~/.config/Claude/claude_desktop_config.json |
Add the server configuration:
{
"mcpServers": {
"pine": {
"command": "mcp-pine"
}
}
}
Restart Claude Desktop after editing.
Sources: README.md:19-32
Other MCP Clients
mcp-pine speaks standard MCP over stdio. Run the binary and connect any compatible client:
mcp-pine
Sources: README.md:45-46
Configuration
Environment Variables
| Env Variable | Default | Purpose |
|---|---|---|
PINE_TARGET | pcsx2 | Emulator name—used as Unix socket prefix on Linux/macOS (<target>.sock.<slot>). Ignored on Windows (TCP only). |
PINE_SLOT | 28011 | PINE slot number—also the TCP port on Windows. |
PINE_HOST | 127.0.0.1 | Override the connection host |
PINE_SOCKET_PATH | Auto-resolved | Override the Unix socket path |
PINE_PIPELINE_BATCH | 1 | Number of pipelined requests (see Performance Notes) |
Sources: README.md:34-43
Socket Path Resolution
Connection transport varies by platform:
graph TD
A[Start] --> B{Platform?}
B -->|Windows| C[TCP 127.0.0.1:PINE_SLOT]
B -->|Linux/macOS| D{Look for XDG_RUNTIME_DIR}
D -->|Set| E[Use $XDG_RUNTIME_DIR/<target>.sock.<slot>]
D -->|Not Set| F{ TMPDIR set?}
F -->|Yes| G[Use $TMPDIR/<target>.sock.<slot>]
F -->|No| H[Use /tmp/<target>.sock.<slot>]Sources: src/pine.ts:60-75
Smoke Testing
Test against a running emulator:
node .scratch/smoke.cjs
Sources: README.md:66-69
Troubleshooting
Common Issues
| Symptom | Cause / Fix |
|---|---|
Cannot reach PINE server | Emulator isn't running, PINE isn't enabled in settings, or slot/port doesn't match. Check PINE_SLOT. |
PINE FAIL response (0xFF) | Emulator rejected request—usually no game loaded or address is unmapped. |
| Reads return zeros | Address is in an unallocated region. Try 0x00100000 first (almost always inside EE RAM). |
| Tool calls work but values look corrupted | Check endianness—PINE returns little-endian. Use read_range-style byte reads for strings. |
pine_ping times out after heavy use | PCSX2 PINE server is wedged. Only a full emulator restart recovers. Reconnecting alone won't help. |
Sources: README.md:68-78
PCSX2 Pipeline Drop Bug
PCSX2's PINE server has a fragile request queue. If more than ~6 in-flight requests are pipelined, it silently drops requests, desyncing the reply pipeline. Once desynced, every reply is misaligned—only an emulator restart fixes this.
| Operation | Latency | Notes |
|---|---|---|
| Full 4096-byte read | ~52 ms | Fully serial requests over loopback TCP |
| Pipelined requests | Faster but risky | PINE_PIPELINE_BATCH=2 for experimental use |
Sources: src/pine.ts:115-125
Project Structure
mcp-pine/
├── src/
│ ├── index.ts # Main entry point, MCP server setup
│ ├── pine.ts # PINE protocol client implementation
│ └── tools.ts # MCP tool definitions
├── .scratch/
│ └── smoke.cjs # Quick smoke test script
├── package.json # Dependencies and scripts
└── README.md # Documentation
Dependencies
| Package | Version | Purpose |
|---|---|---|
@modelcontextprotocol/sdk | ^1.12.0 | MCP server framework |
@types/node | ^22.0.0 | TypeScript types |
typescript | ^5.5.0 | Build tooling |
Sources: package.json:8-14
See Also
- mcp-mgba — Sister MCP server for mGBA (includes button input + screenshot)
- PINE protocol spec — Underlying IPC standard
- docs/RECIPES.md — Cookbook for common workflows (RAM hunting, struct decoding, snapshot-experiment-restore)
Sources: README.md:1-10
Emulator Setup
Related topics: Configuration Reference, Troubleshooting Guide
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Related Pages
Related topics: Configuration Reference, Troubleshooting Guide
Emulator Setup
This page documents how to configure and connect PlayStation 2 emulators to the mcp-pine MCP server. mcp-pine acts as a bridge between AI assistants (via the Model Context Protocol) and the PINE IPC (PlayStation 2 Interactive Interface) server embedded in compatible emulators.
Overview
mcp-pine requires a running emulator with PINE server support enabled. The server communicates with the emulator through either TCP (Windows) or Unix domain sockets (Linux/macOS), allowing AI assistants to read/write memory, query game metadata, and manage save states.
Supported emulators:
| Emulator | Platform | PINE Support | Notes |
|---|---|---|---|
| PCSX2 (1.7.x Qt+) | All | Native | Full support; recommended |
| RPCS3 | All | Partial | IPC implementation mirrors PINE; wire compatibility untested |
| Duckstation | All | Varies | Build-dependent; check your version |
Sources: README.md:1-50
Architecture
The connection flow between mcp-pine and the emulator follows a client-server pattern:
graph LR
subgraph "Host System"
A["Claude / MCP Client"] --> B["mcp-pine"]
B --> C["Emulator PINE Server"]
end
D["PlayStation 2 Game"] --> C
C -->|TCP 127.0.0.1:28011<br/>Unix Socket<br/>/tmp/pcsx2.sock.28011| B
style C fill:#90EE90
style B fill:#87CEEBConnection transport selection:
- Windows: TCP socket to
127.0.0.1:<PINE_SLOT> - Linux/macOS: Unix domain socket at
<TMPDIR or /tmp>/<PINE_TARGET>.sock.<PINE_SLOT>
Sources: src/pine.ts:150-200
Configuration
Environment Variables
| Variable | Default | Purpose |
|---|---|---|
PINE_TARGET | pcsx2 | Emulator name—used as Unix socket filename prefix on Linux/macOS |
PINE_SLOT | 28011 | PINE slot number (port on Windows, socket suffix on Unix) |
PINE_PIPELINE_BATCH | 1 | Number of serial PINE requests to batch (higher = faster but riskier) |
Sources: README.md:60-75
Socket Path Resolution
The PineClient class resolves the socket path based on platform and options:
graph TD
A["PineClient constructor"] --> B{opts.kind explicitly set?}
B -->|Yes| C["Use opts.host/opts.path"]
B -->|No| D{Platform = Windows?}
D -->|Yes| E["TCP: 127.0.0.1:PINE_SLOT"]
D -->|No| F{Platform = Linux/macOS?}
F -->|Yes| G["Unix: XDG_RUNTIME_DIR<br/>or TMPDIR<br/>/<target>.sock.<slot>"]
F -->|No| H["TCP fallback: localhost:PINE_SLOT"]
style G fill:#90EE90
style E fill:#87CEEBSources: src/pine.ts:100-180
PCSX2 Setup
PCSX2 provides native, stable PINE server support starting from version 1.7.x Qt.
Prerequisites
- PCSX2 1.7.x (Qt build) or newer
- A loaded PlayStation 2 game
Configuration Steps
- Launch PCSX2
- Navigate to Settings → Advanced → Enable PINE Server
- The option location may vary slightly by build version
- Use the settings search function to find "PINE" if the menu structure differs
- Verify the default slot is 28011
- Load any PlayStation 2 game
No console commands or external scripts are required—PINE is always-on once the toggle is enabled.
Sources: README.md:30-45
RPCS3 Setup
RPCS3 implements its own IPC mechanism that mirrors PINE's opcode set, but wire-level compatibility with this client has not been thoroughly tested.
Configuration Steps
- Navigate to Configuration → Advanced → Enable IPC server
- Note the configured port number
- Run mcp-pine with the target override:
PINE_TARGET=rpcs3 PINE_SLOT=<port> mcp-pine
Note: Full compatibility is not guaranteed. If issues arise, please file a bug report with connection logs and emulator version information.
Sources: README.md:46-55
Duckstation Setup
Duckstation includes PINE server support in some builds, but availability varies by version and distribution.
Configuration
If your Duckstation build includes PINE support:
PINE_TARGET=duckstation PINE_SLOT=<port> mcp-pine
Note: Check your Duckstation build version to confirm PINE server availability.
Sources: README.md:56-60
MCP Client Registration
After the emulator is configured, register mcp-pine with your MCP client.
Claude Code (CLI)
claude mcp add pine --scope user mcp-pine
Verify registration:
claude mcp list
# pine: mcp-pine - ✓ Connected
Sources: README.md:85-95
Claude Desktop
Edit the configuration file at your platform-specific location:
| Platform | Path |
|---|---|
| macOS | ~/Library/Application Support/Claude/claude_desktop_config.json |
| Windows | %APPDATA%\Claude\claude_desktop_config.json |
| Linux | ~/.config/Claude/claude_desktop_config.json |
Add the mcp-pine server entry:
{
"mcpServers": {
"pine": {
"command": "mcp-pine"
}
}
}
Restart Claude Desktop after editing.
Sources: README.md:96-115
Available Tools
Once connected, the following tools are available through the MCP interface:
| Tool | Purpose | Call Type |
|---|---|---|
pine_ping | Verify connection to emulator | 1 round-trip |
pine_get_info | Get game title, serial, CRC, version, and status | 5 parallel calls |
pine_get_status | Get emulator run state (running/paused/shutdown) | 1 round-trip |
pine_read8/16/32/64 | Read bytes from EE memory | 1 round-trip each |
pine_read_range | Bulk read up to 4096 bytes | Serial sequence |
pine_write8/16/32/64 | Write bytes to EE memory | 1 round-trip each |
pine_save_state | Save emulator state to slot | 1 round-trip |
pine_load_state | Load emulator state from slot | 1 round-trip |
Address Parameter Notes
All address parameters reference the EE main address space. Key memory regions:
| Region | Address Range | Description |
|---|---|---|
| EE Main RAM | 0x00100000 – 0x01FFFFFF | Primary game state memory (99% of use cases) |
| IOP RAM | 0x1C000000+ | Input/Output Processor memory |
| Scratchpad | 0x70000000 – 0x70003FFF | Fast local memory |
Alignment requirements:
| Width | Alignment | Notes |
|---|---|---|
| 8-bit | None | Safe to use at any address |
| 16-bit | 2-byte | Misaligned access silently corrupts data |
| 32-bit | 4-byte | Misaligned access silently corrupts data |
| 64-bit | 8-byte | Misaligned access silently corrupts data |
Important: PCSX2's PINE implementation does NOT enforce alignment. Misaligned multi-byte access returns bytes from the aligned address below, silently corrupting values.
Sources: src/tools.ts:1-100
Troubleshooting
Common Issues
| Symptom | Cause | Fix |
|---|---|---|
Cannot reach PINE server | Emulator not running, PINE not enabled, or slot mismatch | Check PINE_SLOT matches emulator setting |
PINE FAIL response (0xFF) | No game loaded, or address unmapped | Load a game; try 0x00100000 first |
| Reads return zeros | Address in unallocated region | Use 0x00100000 (EE RAM base) |
| Values look corrupted | Wrong endianness or alignment | PINE returns little-endian; use pine_read_range for byte-level control |
PINE call timed out (10s) after heavy use | PCSX2 PINE queue desync | Full PCSX2 restart required—reconnecting alone won't help |
Sources: README.md:130-150
PCSX2 PINE Queue Desync
PCSX2's PINE server has a fragile request queue. If more than approximately 6 in-flight requests are issued, the server silently drops requests, corrupting the reply pipeline alignment. Once desynced, every subsequent reply is misaligned, causing timeouts on even simple operations like pine_ping.
Symptoms:
- Fresh
pine_pingcalls timing out after heavy use - Intermittent read/write failures
- Non-deterministic response values
Fix:
- Fully restart PCSX2
- Reconnect mcp-pine
- Reduce
PINE_PIPELINE_BATCHto1(default)
Sources: README.md:155-165 Sources: src/pine.ts:60-90
Performance Considerations
pine_read_range issues PINE calls serially by default to avoid queue desync. Measured performance on PCSX2 v2.6.3:
| Operation | Latency |
|---|---|
| Full 4096-byte read | ~52 ms |
| Per 64-bit word | ~13 ms |
Loopback TCP is fast enough that serial reads complete in less than two emulated frames. For lower latency with acceptable risk, set PINE_PIPELINE_BATCH=2 or higher.
Warning: Higher batch values increase the probability of triggering PCSX2's queue drop behavior.
Sources: README.md:166-175
Development Setup
For testing against a running emulator:
git clone https://github.com/dmang-dev/mcp-pine
cd mcp-pine
npm install # Runs build via prepare hook
# Development mode with TypeScript watching
npm run dev
# Smoke test
node .scratch/smoke.cjs
The project builds using TypeScript 5.5+ with Node 22+ support.
Sources: README.md:175-190 Sources: package.json:1-30
Sources: README.md:1-50
System Architecture
Related topics: Memory Operations, Savestate Management, System Query Tools
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Related Pages
Related topics: Memory Operations, Savestate Management, System Query Tools
System Architecture
Overview
mcp-pine is an MCP (Model Context Protocol) server that bridges AI assistants (such as Claude) with PlayStation 2 emulators (PCSX2, RPCS3, Duckstation) via the PINE (Platform Independent Networked Environment) protocol. It exposes emulator memory, save states, and game information as MCP tools, enabling AI-driven game analysis, memory hacking, and automation workflows.
The architecture follows a layered client-server model with three distinct subsystems: the MCP protocol layer, the PINE protocol client, and the cross-platform transport layer.
High-Level Architecture
graph TD
subgraph "MCP Host (Claude)"
A["Claude AI"]
end
subgraph "mcp-pine"
B["MCP Server<br/>(@modelcontextprotocol/sdk)"]
C["PineClient<br/>(Protocol Layer)"]
D["Transport Layer<br/>(TCP / Unix Socket)"]
end
subgraph "Emulator"
E["PINE Server"]
F["EE Memory<br/>IOP Memory<br/>Save States"]
end
A <-->|"MCP stdio"| B
B <-->|"Tool Calls"| C
C <-->|"PINE Protocol"| D
D <-->|"TCP:127.0.0.1<br/>Unix:/tmp/*.sock"| E
E <--> FSources: README.md - Project overview and configuration documentation
Component Architecture
Layer 1: MCP Server
The MCP server layer implements the Model Context Protocol specification using the @modelcontextprotocol/sdk. It registers tool handlers and routes incoming tool calls to the PINE client.
| Component | File | Responsibility |
|---|---|---|
registerTools() | src/tools.ts | Registers all 13 MCP tools with the SDK |
| Tool dispatcher | src/tools.ts | Routes CallToolRequest to corresponding PINE operations |
| Response formatter | src/tools.ts | Formats PINE responses for MCP protocol |
Sources: src/tools.ts - MCP tool registration and handler implementation
Layer 2: PINE Protocol Client
The PineClient class (src/pine.ts) implements the binary PINE wire format specification. It handles request encoding, response decoding, and protocol state management.
graph LR
A["Tool Request"] --> B["Encode Opcode<br/>+ Payload"]
B --> C["Buffer<br/>(4-byte size prefix)"]
C --> D["Socket Write"]
D --> E["Response Read"]
E --> F["Parse Size"]
F --> G["Extract Opcode<br/>+ Data"]
G --> H["Decode Value"]
H --> I["Return to Tool"]Sources: src/pine.ts - PINE client implementation
#### Opcode Mapping
| Operation | Opcode | Direction | Description |
|---|---|---|---|
getVersion | Op.Version | Read | Emulator version string |
getTitle | Op.Title | Read | Game title |
getId | Op.ID | Read | Game serial number |
getUuid | Op.UUID | Read | Disc CRC hash |
getGameVersion | Op.GameVersion | Read | Game version string |
getStatus | Op.Status | Read | Emulator state (running/paused/shutdown) |
read8/16/32/64 | Op.Read* | Read | Memory reads at specified width |
write8/16/32/64 | Op.Write* | Write | Memory writes at specified width |
saveState | Op.SaveState | Write | Save to slot (0-255) |
loadState | Op.LoadState | Write | Load from slot (0-255) |
Sources: src/pine.ts - Opcode constants and operation implementations
Layer 3: Transport Layer
The transport layer provides cross-platform connectivity using the appropriate socket type for each operating system.
graph TD
A["Platform Detection"] --> B{OS?}
B -->|Windows| C["TCP Socket<br/>127.0.0.1:SLOT"]
B -->|Linux/macOS| D["Unix Domain Socket"]
D --> E["$XDG_RUNTIME_DIR<br/>/<target>.sock.<slot>"]
D --> F["$TMPDIR<br/>/<target>.sock.<slot>"]
D --> G["/tmp<br/>/<target>.sock.<slot>"]Sources: src/pine.ts - resolveSocket() function and transport abstraction
#### Socket Resolution Strategy
| Priority | Linux/macOS Path | Windows |
|---|---|---|
| 1 | $XDG_RUNTIME_DIR/<target>.sock.<slot> | TCP 127.0.0.1:<slot> |
| 2 | $TMPDIR/<target>.sock.<slot> | — |
| 3 | /tmp/<target>.sock.<slot> | — |
Sources: README.md - Configuration and transport documentation
Request Queue Management
The PineClient maintains an internal request queue to handle concurrent tool calls and ensure responses are matched to their originating requests.
sequenceDiagram
participant Tool1 as Tool Call 1
participant Tool2 as Tool Call 2
participant Queue as Request Queue
participant Client as PineClient
participant Socket as Network Socket
Tool1->>Queue: Enqueue request
Tool2->>Queue: Enqueue request
Queue->>Socket: Write Request 1
Queue->>Socket: Write Request 2
Socket-->>Client: Receive Response 1
Client->>Queue: Resolve Promise 1
Socket-->>Client: Receive Response 2
Client->>Queue: Resolve Promise 2Sources: src/pine.ts - Request queue implementation
Pending Request Structure
interface Pending {
resolve: (value: Buffer) => void;
reject: (err: Error) => void;
}
Each pending request holds references to its resolve and reject functions, allowing the response handler to deliver results to the correct caller even when multiple requests are in-flight.
Sources: src/pine.ts - Pending interface definition
Bulk Read Implementation
The readRange() method implements efficient bulk memory reads by combining multiple aligned PINE operations.
graph TD
A["readRange(addr, length)"] --> B["Calculate Steps"]
B --> C{"Aligned?"}
C -->|8-byte| D["read64"]
C -->|4-byte| E["read32"]
C -->|2-byte| F["read16"]
C -->|1-byte| G["read8"]
D --> H["PIPELINE_BATCH"]
E --> H
F --> H
G --> H
H --> I["Promise.all(batch)"]
I --> J{"More steps?"}
J -->|Yes| H
J -->|No| K["Return Buffer"]Sources: src/pine.ts - readRange() implementation
Pipelining Configuration
| Env Variable | Default | Effect |
|---|---|---|
PINE_PIPELINE_BATCH | 1 | Number of concurrent requests (1=serial, 2+=parallel) |
Warning: PCSX2's PINE server silently drops requests when too many are pipelined (~7+ in-flight). This desyncs the reply pipeline, causing all subsequent requests to time out until emulator restart.
Sources: src/pine.ts - Pipeline batch configuration
Tool Registration Flow
graph LR
A["MCP Server<br/>Instantiation"] --> B["PineClient<br/>Creation"]
B --> C["registerTools()<br/>called"]
C --> D["ListToolsRequestHandler<br/>registered"]
C --> E["CallToolRequestHandler<br/>registered"]
F["MCP Host<br/>connects"] --> G["ListTools<br/>request"]
G --> H["TOOLS[]<br/>schema returned"]
F --> I["Tool Call<br/>invoked"]
I --> J["switch(name)<br/>dispatch"]
J --> K["PineClient<br/>operation"]Sources: src/tools.ts - Tool registration and dispatch
Memory Address Space
The PINE protocol operates on PlayStation 2 memory regions. All address parameters use absolute byte addresses in the EE (Emotion Engine) main address space.
| Region | Address Range | Size | Description |
|---|---|---|---|
| EE Main RAM | 0x00100000 - 0x01FFFFFF | ~31 MB | Primary game state storage |
| IOP RAM | 0x1C000000+ | Variable | I/O Processor memory |
| Scratchpad | Special range | 16 KB | Fast-access cache |
Alignment Note: Multi-byte reads/writes (16/32/64-bit) must be aligned to their width. PINE on PCSX2 does NOT enforce alignment — unaligned access silently returns corrupted data.
Sources: src/tools.ts - Address parameter descriptions
64-bit Value Handling
JavaScript's Number type loses precision beyond 2^53. The implementation handles 64-bit values as decimal strings.
| Operation | Input Format | Output Format | Reason |
|---|---|---|---|
pine_write64 | Decimal string ("18446744073709551615") | — | Preserve full u64 range |
pine_read64 | — | Decimal string | Preserve full u64 range |
Sources: src/tools.ts - 64-bit parameter descriptions
Configuration Environment Variables
| Variable | Default | Platform | Purpose |
|---|---|---|---|
PINE_TARGET | pcsx2 | All | Emulator name prefix for Unix socket path |
PINE_SLOT | 28011 | All | PINE slot number (also port on Windows) |
PINE_HOST | 127.0.0.1 | Windows | TCP host override |
PINE_SOCKET_PATH | Auto | All | Custom Unix socket path |
PINE_PIPELINE_BATCH | 1 | All | Concurrent request batching |
PIPELINE_BATCH | 1 | All | Alias for PINE_PIPELINE_BATCH |
Sources: README.md - Configuration table
Error Handling
| Error | Cause | Fix |
|---|---|---|
Cannot reach PINE server | Emulator not running or PINE disabled | Check PINE_SLOT matches emulator settings |
PINE FAIL response (0xFF) | No game loaded or address unmapped | Load a game; verify address range |
| Reads return zeros | Address in unallocated region | Try 0x00100000 first (EE RAM base) |
| Values look corrupted | Endianness mismatch | PINE returns little-endian; check parsing |
PINE call timed out (10s) | PCSX2 PINE server wedged | Full emulator restart required |
Sources: README.md - Troubleshooting section
Lazy Reconnect Behavior
The MCP server implements lazy reconnection — if the emulator is restarted, the PINE client automatically reconnects without requiring the MCP host to restart.
graph TD
A["Tool Call"] --> B{"Connected?"}
B -->|No| C["Connect()"]
C --> D{"Success?"}
D -->|Yes| E["Send Request"]
D -->|No| F["Error (retry next call)"]
B -->|Yes| E
E --> G["Response"]Sources: src/pine.ts - Connection management
Package Dependencies
| Package | Version | Purpose |
|---|---|---|
@modelcontextprotocol/sdk | ^1.12.0 | MCP protocol implementation |
typescript | ^5.5.0 | TypeScript compilation (dev) |
@types/node | ^22.0.0 | Node.js type definitions (dev) |
Sources: package.json - Dependencies section
Supported Emulators
| Emulator | Platform | Status | Notes |
|---|---|---|---|
| PCSX2 | All | Primary | Full PINE support; Qt 1.7.x+ |
| RPCS3 | All | Experimental | IPC implementation mirrors PINE; wire compatibility untested |
| Duckstation | All | Varies | Depends on build; PINE server availability varies |
Sources: README.md - Emulator setup documentation
Source: https://github.com/dmang-dev/mcp-pine / Human Manual
Configuration Reference
Related topics: Emulator Setup, System Architecture
Continue reading this section for the full explanation and source context.
Related Pages
Related topics: Emulator Setup, System Architecture
Configuration Reference
Overview
The mcp-pine project provides a Model Context Protocol (MCP) server that bridges MCP clients (such as Claude Code or Claude Desktop) to PlayStation 2/3 emulators via the PINE (Platform Independent Networked Environment) protocol. Configuration is driven entirely through environment variables, allowing users to customize connection targets, communication protocols, and runtime behavior without modifying code.
This reference documents all supported environment variables, their default values, acceptable ranges, and behavioral implications.
Source: https://github.com/dmang-dev/mcp-pine / Human Manual
Memory Operations
Related topics: Savestate Management, System Query Tools
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Related Pages
Related topics: Savestate Management, System Query Tools
Memory Operations
Overview
Memory Operations in mcp-pine provide read and write access to the emulated PlayStation 2's (PS2) Emotion Engine (EE) address space through the PINE (Plugin Interface for Networked Emulation) protocol. These operations enable AI assistants and MCP clients to inspect and modify game state in real-time while an emulator runs.
The primary use cases include:
- Game state inspection — reading RAM to discover health, score, position, or other variables
- Cheat development — writing values to memory addresses to create or test cheats
- Debugging — inspecting memory regions during gameplay to understand game logic
- Save state management — snapshotting and restoring emulator state through specific slots
Memory operations in mcp-pine are exposed as MCP tools that bridge the AI assistant to the underlying PINE protocol implementation in src/pine.ts.
Sources: src/tools.ts:1-50
Architecture
Component Hierarchy
graph TD
subgraph "MCP Client Layer"
A["AI Assistant / Claude"]
end
subgraph "MCP Bridge (mcp-pine)"
B["src/tools.ts<br/>Tool Definitions"]
C["src/pine.ts<br/>PineClient"]
D["PINE Protocol Encoder/Decoder"]
end
subgraph "Transport Layer"
E["Unix Socket<br/>Linux/macOS"]
F["TCP Socket<br/>Windows"]
end
subgraph "Emulator"
G["PCSX2 / RPCS3 / Duckstation<br/>PINE Server"]
end
A --> B
B --> C
C --> D
D --> E
D --> F
E --> G
F --> GRequest-Response Flow
sequenceDiagram
participant AI as AI Assistant
participant MCP as MCP Bridge
participant PC as PineClient
participant PINE as PINE Server
AI->>MCP: pine_read32(0x00100000)
MCP->>PC: read32() call
PC->>PINE: Op.Read32 + addr (12 bytes)
PINE-->>PC: Reply frame (12 bytes)
PC-->>MCP: number value
MCP-->>AI: "0x00100000: 1234 (0x4D2)"Key Components
| Component | File | Responsibility |
|---|---|---|
PineClient | src/pine.ts | Socket management, request queuing, protocol encoding/decoding |
| Tool definitions | src/tools.ts | MCP tool schemas, parameter validation, response formatting |
| Opcode enum | src/pine.ts | PINE protocol opcodes (Op.Read32, Op.Write8, etc.) |
Sources: src/pine.ts:1-80
Read Operations
mcp-pine provides four aligned read operations for accessing EE memory:
| Tool | Width | Opcode | Alignment |
|---|---|---|---|
pine_read8 | 8-bit / 1 byte | Op.Read8 | None required |
pine_read16 | 16-bit / 2 bytes | Op.Read16 | 2-byte |
pine_read32 | 32-bit / 4 bytes | Op.Read32 | 4-byte |
pine_read64 | 64-bit / 8 bytes | Op.Read64 | 8-byte |
All read operations return values as little-endian unsigned integers. The return format follows a consistent pattern: ADDR_HEX: VAL_DEC (0xVAL_HEX).
Address Parameter
The address parameter for all read/write operations accepts an absolute byte address in the EE main address space:
Absolute byte address in the EE main address space (NOT a per-domain offset).
Pass as a number; hex literals like 0x00200000 are fine.
PS2 Memory Landmarks:
| Region | Address Range | Description |
|---|---|---|
| EE Main RAM | 0x00100000 - 0x01FFFFFF | Where 99% of game state lives |
| IOP RAM | 0x1C000000+ | Input/Output Processor memory |
| Scratchpad | 0x70000000 - 0x70003FFF | Fast 16KB scratchpad |
Sources: src/tools.ts:30-45
Usage Examples
// Read 32-bit value at game state pointer
pine_read32(0x00200000)
// Read 8-bit byte for flag detection
pine_read8(0x00201004)
// Read 64-bit EE pointer
pine_read64(0x00203000)
Alignment Caveats
Important: PINE on PCSX2 does NOT enforce alignment. Unaligned multi-byte access returns whatever bytes are at the aligned address below, silently corrupting the value.
| Operation | Required Alignment | Misaligned Behavior |
|---|---|---|
read8 | None | Correct |
read16 | 2-byte | Returns wrong value silently |
read32 | 4-byte | Returns wrong value silently |
read64 | 8-byte | Returns wrong value silently |
If you need an unaligned multi-byte read, use pine_read_range and assemble the bytes yourself.
Sources: src/tools.ts:30-40
Write Operations
Write operations modify memory in the emulated EE address space:
| Tool | Width | Opcode | Value Encoding |
|---|---|---|---|
pine_write8 | 8-bit | Op.Write8 | Number 0-255 |
pine_write16 | 16-bit | Op.Write16 | Number 0-65535 |
pine_write32 | 32-bit | Op.Write32 | Number 0-4294967295 |
pine_write64 | 64-bit | Op.Write64 | Decimal string 0-18446744073709551615 |
Destructive Behavior Notes
- Writes bypass TLB protection and DMA mediation
- Writes to read-only regions (BIOS, IOP ROM) are silently dropped
- No undo mechanism — changes are permanent until emulator restart or explicit restoration
64-bit Value Encoding
64-bit values use decimal string encoding because JavaScript's number type cannot represent the full uint64 range (max safe integer is 2^53 - 1):
// Write maximum 64-bit value
pine_write64(0x00200000, "18446744073709551615")
The schema enforces this with pattern: "^[0-9]+$" to reject non-numeric input.
Sources: src/tools.ts:140-180
Usage Examples
// Infinite health cheat
pine_write32(0x00201000, 9999)
// Set RGBA color (packed 32-bit)
pine_write32(0x00300000, 0xFFFFFFFF)
// Write large 64-bit ID
pine_write64(0x00204000, "12345678901234567")
Bulk Read Operation
`pine_read_range`
Reads up to 4096 consecutive bytes in a single tool call. Since PINE has no native bulk read opcode, this is implemented client-side as a serial sequence of aligned reads.
Implementation Strategy:
graph TD
A["read_range(addr, length)"] --> B["Calculate read steps"]
B --> C{"Remaining bytes?"}
C -->|≥8 & addr%8==0| D["read64"]
C -->|≥4 & addr%4==0| E["read32"]
C -->|≥2 & addr%2==0| F["read16"]
C -->|else| G["read8"]
D --> H["cursor += 8"]
E --> I["cursor += 4"]
F --> J["cursor += 2"]
G --> K["cursor += 1"]
H --> C
I --> C
J --> C
K --> C
C -->|0| L["Return Buffer"]Performance Characteristics:
| Configuration | Measured Time (4KB) |
|---|---|
Serial (PINE_PIPELINE_BATCH=1) | ~52 ms |
With pipelining (PINE_PIPELINE_BATCH=2+) | Lower latency, higher risk |
Measured on PCSX2 v2.6.3 over loopback TCP.
Pipelining and PCSX2 Fragility
PCSX2's PINE server has a fragile request queue: dropping any single request silently desyncs the reply pipeline, causing all subsequent requests to timeout until the emulator restarts.
sequenceDiagram
participant Client
participant PCSX2 as PCSX2 PINE Server
Note over Client,PCSX2: Normal operation (≤6 in-flight)
Client->>PCSX2: Request 1
Client->>PCSX2: Request 2
Client->>PCSX2: Request 3
PCSX2-->>Client: Reply 1
PCSX2-->>Client: Reply 2
PCSX2-->>Client: Reply 3
Note over Client,PCSX2: Pipeline saturation (≥7 in-flight)
Client->>PCSX2: Request 1
Client->>PCSX2: Request 2
Client->>PCSX2: Request 3
Client->>PCSX2: Request 4
Client->>PCSX2: Request 5
Client->>PCSX2: Request 6
Client->>PCSX2: Request 7
PCSX2->>PCSX2: Dropped!
Note over Client,PCSX2: All subsequent replies misaligned!Recommendation: Use serial mode by default. Only set PINE_PIPELINE_BATCH=2 or higher if you can tolerate occasional emulator restarts.
Sources: src/pine.ts:60-100
Save State Operations
The save state operations provide snapshot and restore functionality through PINE slots:
| Tool | Opcode | Description |
|---|---|---|
pine_save_state | Op.SaveState | Save current emulator state to slot |
pine_load_state | Op.LoadState | Load emulator state from slot |
Slot Numbering
- Range: 0-255
- PCSX2 convention: Slots 0-9 mapped to F1-F10 in GUI
- Storage location: Per-game savestate folder
Slot File Naming
<serial> (<crc>).<slot>.p2s
Example: SCUS-97211 (ABC12345).3.p2s
Sources: src/tools.ts:180-220
Error Handling
Connection Failures
| Error | Cause | Fix |
|---|---|---|
Cannot reach PINE server | Emulator not running, PINE disabled, wrong slot | Check PINE_SLOT setting |
PINE FAIL response (0xFF) | Emulator rejected request | Ensure game loaded, check address validity |
| Timeout (10s) | Reply dropped | Restart emulator if PCSX2 PINE is wedged |
Data Corruption
| Symptom | Cause | Fix |
|---|---|---|
| Reads return zeros | Address in unallocated region | Try 0x00100000 first |
| Values look corrupted | Wrong endianness interpretation | PINE returns little-endian |
| Silent wrong values | Unaligned access | Align addresses or use read_range |
Sources: README.md:80-100
Configuration
Environment Variables
| Variable | Default | Purpose |
|---|---|---|
PINE_TARGET | pcsx2 | Emulator name prefix for Unix socket path |
PINE_SLOT | 28011 | PINE slot / port number |
PINE_PIPELINE_BATCH | 1 | Parallel request count (use with caution) |
Socket Path Resolution
graph TD
A["Start"] --> B{"OS?"}
B -->|Windows| C["TCP 127.0.0.1:<PINE_SLOT>"]
B -->|Linux/macOS| D{"XDG_RUNTIME_DIR set?"}
D -->|Yes| E["$XDG_RUNTIME_DIR/<PINE_TARGET>.sock.<PINE_SLOT>"]
D -->|No| F{"TMPDIR set?"}
F -->|Yes| G["$TMPDIR/<PINE_TARGET>.sock.<PINE_SLOT>"]
F -->|No| H["/tmp/<PINE_TARGET>.sock.<PINE_SLOT>"]Sources: src/pine.ts:20-50
Tool Reference Summary
| Tool | Input Schema | Return Format | ||
|---|---|---|---|---|
pine_ping | None | OK — emulator: <version> | ||
pine_get_info | None | Multi-line status with title, serial, CRC, version | ||
pine_get_status | None | `Status: running\ | paused\ | shutdown` |
pine_read8 | {address: integer} | ADDR_HEX: VAL (0xVAL_HEX) | ||
pine_read16 | {address: integer} | ADDR_HEX: VAL (0xVAL_HEX) | ||
pine_read32 | {address: integer} | ADDR_HEX: VAL (0xVAL_HEX) | ||
pine_read64 | {address: integer} | ADDR_HEX: VAL (0xVAL_HEX) | ||
pine_read_range | {address: integer, length: 1-4096} | Raw bytes (base64 or hex) | ||
pine_write8 | {address: integer, value: number} | Wrote VAL → ADDR | ||
pine_write16 | {address: integer, value: number} | Wrote VAL → ADDR | ||
pine_write32 | {address: integer, value: number} | Wrote VAL → ADDR | ||
pine_write64 | {address: integer, value: string} | Wrote VAL → ADDR | ||
pine_save_state | {slot: 0-255} | Saved state to slot <N> | ||
pine_load_state | {slot: 0-255} | Loaded state from slot <N> |
Sources: src/tools.ts:50-250
Sources: src/tools.ts:1-50
Savestate Management
Related topics: Memory Operations, System Query Tools
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Related Pages
Related topics: Memory Operations, System Query Tools
Savestate Management
Overview
Savestate Management provides MCP clients with the ability to programmatically save and load emulator states for PlayStation 2 games running in PCSX2 (or other PINE-compatible emulators). This enables workflows such as checkpoint-based experimentation, automated testing, and stateful memory analysis where the user can snapshot game state, perform operations, and restore to a known baseline.
The feature is implemented through two MCP tools (pine_save_state and pine_load_state) that wrap the PINE protocol's SaveState and LoadState opcodes. These tools interact directly with the emulator's running instance, writing savestate files to the emulator's native storage location.
Architecture
Component Overview
graph TD
A["MCP Client<br/>(Claude Code, Claude Desktop)"] --> B["mcp-pine Bridge"]
B --> C["PineClient<br/>(src/pine.ts)"]
C --> D["PINE Protocol<br/>(TCP/Unix Socket)"]
D --> E["PCSX2 PINE Server"]
E --> F["Savestate Files<br/>(~/.config/PCSX2/sstates)"]
G["src/tools.ts"] -->|Tool Definitions| BSavestate Workflow
sequenceDiagram
participant MCP as MCP Client
participant Bridge as mcp-pine Bridge
participant Pine as PineClient
participant Emu as PCSX2 PINE Server
participant FS as File System
Note over MCP, FS: Save State Flow
MCP->>Bridge: pine_save_state(slot: N)
Bridge->>Pine: saveState(slot)
Pine->>Emu: PINE SaveState opcode + slot byte
Emu->>FS: Write <serial>.<crc>.<slot>.p2s
Emu-->>Pine: PINE OK response
Pine-->>Bridge: Promise resolves
Bridge-->>MCP: "State saved to slot N"
Note over MCP, FS: Load State Flow
MCP->>Bridge: pine_load_state(slot: N)
Bridge->>Pine: loadState(slot)
Pine->>Emu: PINE LoadState opcode + slot byte
Emu->>FS: Read <serial>.<crc>.<slot>.p2s
Emu-->>Pine: PINE OK response
Pine-->>Bridge: Promise resolves
Bridge-->>MCP: "State loaded from slot N"MCP Tools
pine_save_state
Saves the current emulator state to a specified slot.
| Property | Value |
|---|---|
| Tool Name | pine_save_state |
| Source | src/tools.ts:1-20 |
Parameters:
| Parameter | Type | Constraints | Description |
|---|---|---|---|
slot | integer | 0-255 | Save state slot number |
Description (from source):
PURPOSE: Atomically snapshot the full emulator state (CPU registers, RAM, VRAM,
SPU, device states) to the specified slot and persist it to disk. USAGE:
Save checkpoints before risky operations (memory edits, patch applications,
speedrun route branches). Use pine_load_state to restore. Slot 0 is
conventional for "current run", slots 1-9 map to F1-F10 in PCSX2's GUI.
BEHAVIOR: DESTRUCTIVE — overwrites any existing savestate in the same
slot without prompting. Requires a game to be loaded; returns PINE FAIL
if invoked from the BIOS. Emulator retains the saved data across sessions
(survives emulator restart). RETURNS: "State saved to slot N" on success.
pine_load_state
Restores the emulator state from a specified slot.
| Property | Value |
|---|---|
| Tool Name | pine_load_state |
| Source | src/tools.ts:1-20 |
Parameters:
| Parameter | Type | Constraints | Description |
|---|---|---|---|
slot | integer | 0-255 | Save state slot number |
Description (from source):
PURPOSE: Atomically restore the full emulator state (CPU registers, RAM, VRAM,
SPU, device states) from a previously saved slot. USAGE: Restore after
experimental memory edits, reset to a known checkpoint, or implement
trial-and-error workflows (save → modify → load → try again).
BEHAVIOR: DESTRUCTIVE — immediately overwrites all emulator state with
the slot's contents. Does not verify the slot contains a valid savestate
before restoring (PINE FAIL if slot is empty/missing). The restored state
reflects the exact game position at the time of save, including any
unsaved progress. RETURNS: "State loaded from slot N" on success.
Implementation
PineClient Methods
The PineClient class in src/pine.ts implements the savestate operations:
async saveState(slot: number): Promise<void> {
const args = Buffer.alloc(1); args.writeUInt8(slot, 0);
await this.call(Op.SaveState, args);
}
async loadState(slot: number): Promise<void> {
const args = Buffer.alloc(1); args.writeUInt8(slot, 0);
await this.call(Op.LoadState, args);
}
Sources: src/pine.ts:1-10
Tool Registration
The tools are registered in registerTools() which handles the MCP request routing:
case "pine_save_state": {
await pine.saveState(p.slot as number);
return ok(`State saved to slot ${p.slot}`);
}
case "pine_load_state": {
await pine.loadState(p.slot as number);
return ok(`State loaded from slot ${p.slot}`);
}
Sources: src/tools.ts:1-20
Slot Management
Slot Number Conventions
| Slot Range | Conventional Use | GUI Mapping |
|---|---|---|
| 0 | Current run / temporary checkpoint | Manual |
| 1-9 | Quick save slots | F1-F10 |
| 10-99 | Scene-specific saves | Named folders |
| 100-255 | Long-term snapshots | Archive |
PCSX2 conventionally uses slots 0-9 mapped to function keys F1-F10 in the GUI. The PINE protocol accepts the full 0-255 range, but slots beyond 9 are only accessible programmatically.
File Location
Savestate files are stored in the emulator's per-game savestate folder:
| Platform | Path |
|---|---|
| Linux | ~/.config/PCSX2/sstates/ |
| macOS | ~/.config/PCSX2/sstates/ |
| Windows | %USERPROFILE%\Documents\PCSX2\sstates\ |
Filename format: <serial> (<crc>).<slot>.p2s
Usage Patterns
Checkpoint Workflow
graph LR
A[Game Running] --> B["pine_save_state<br/>slot=0"]
B --> C[Perform Memory Edits]
C --> D{Results OK?}
D -->|Yes| E[Continue Playing]
D -->|No| F["pine_load_state<br/>slot=0"]
F --> AMemory Analysis Workflow
graph TD
A[Save Baseline] -->|"slot=0"| B
B[Find Target Address] -->|"pine_read32"| C
C[Edit Memory] -->|"pine_write32"| D
D[Verify Change] -->|"pine_read32"| E
E{Correct?} -->|Yes| F[Continue]
E{No} -->|"pine_load_state slot=0"| BExample Tool Calls
Save state:
{
"name": "pine_save_state",
"arguments": { "slot": 0 }
}
Load state:
{
"name": "pine_load_state",
"arguments": { "slot": 0 }
}
Error Handling
Common Failure Modes
| Error | Cause | Fix |
|---|---|---|
| PINE FAIL response | No game loaded, or slot is invalid | Load a game before saving |
| Slot contains garbage | Corrupted savestate file | Delete file and re-save |
| Load restores wrong game | Serial/CRC mismatch | Use correct game or different slot |
Connection Requirements
The emulator must be running with PINE enabled:
- Launch PCSX2 (1.7.x Qt or newer)
- Navigate to Settings → Advanced → Enable PINE Server
- Default slot is 28011 (configurable via
PINE_SLOT)
Sources: README.md:1-20
Configuration
Savestate operations are affected by the following environment variables:
| Variable | Default | Effect |
|---|---|---|
PINE_SLOT | 28011 | Sets the PINE slot used for communication |
PINE_TARGET | pcsx2 | Emulator name prefix for Unix socket path |
PINE_TIMEOUT | 10000 (ms) | Per-call timeout; affects savestate operations |
Related Tools
| Tool | Purpose |
|---|---|
pine_get_info | Get game metadata before saving (title, serial, CRC) |
pine_get_status | Check if emulator is running/paused before operations |
pine_read_* | Read memory from restored state |
pine_write_* | Modify memory after loading a state |
See Also
- mcp-mgba — sister MCP server for mGBA with button input support
- PINE protocol spec — underlying IPC standard
Source: https://github.com/dmang-dev/mcp-pine / Human Manual
System Query Tools
Related topics: Memory Operations, Savestate Management
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Related Pages
Related topics: Memory Operations, Savestate Management
System Query Tools
The System Query Tools in mcp-pine provide MCP (Model Context Protocol) clients with a comprehensive interface to query and interact with PINE-enabled PlayStation 2 emulators. These tools expose low-level emulator introspection capabilities—including game metadata retrieval, memory reading/writing, and save state management—through the standardized MCP tool interface.
Overview
mcp-pine bridges the gap between AI assistants (primarily Claude) and PlayStation 2 emulators by implementing the PINE (Platform for Interactive Nintendo/Network Environments) protocol. The System Query Tools form the public API layer that MCP clients interact with.
Architecture
graph TD
subgraph "MCP Client Layer"
A[Claude / MCP Host]
end
subgraph "mcp-pine Bridge"
B[tools.ts<br/>Tool Definitions]
C[pine.ts<br/>PineClient]
end
subgraph "Emulator Layer"
D[PCSX2 / RPCS3 / Duckstation]
end
A -->|CallToolRequest| B
B -->|PINE Protocol| C
C -->|TCP / Unix Socket| D
style B fill:#f9f,color:#000
style C fill:#9ff,color:#000Tool Categories
| Category | Tools | Purpose |
|---|---|---|
| Connectivity | pine_ping | Verify emulator connection and retrieve version |
| Introspection | pine_get_info, pine_get_status | Query game metadata and emulator state |
| Memory Read | pine_read8, pine_read16, pine_read32, pine_read64, pine_read_range | Read from emulator's EE address space |
| Memory Write | pine_write8, pine_write16, pine_write32, pine_write64 | Write to emulator's memory |
| State Management | pine_save_state, pine_load_state | Manipulate emulator save states |
Sources: src/tools.ts:47-140
Connectivity & Introspection
pine_ping
A lightweight health check that retrieves the emulator's PINE protocol version.
{
name: "pine_ping",
description:
"PURPOSE: Verify connectivity to the emulator's PINE server and retrieve its version string. " +
"USAGE: Call first when starting a session or debugging connection issues. Unlike pine_get_info, " +
"this makes only one PINE round-trip (Status opcode) — fastest way to confirm the link is alive. " +
"BEHAVIOR: No side effects. Issues PINE Version opcode (0x08). Times out after ~10s if the " +
"emulator is unresponsive or the PINE queue is desynced. " +
"RETURNS: 'OK — emulator: VERSION_STRING'.",
inputSchema: { type: "object", properties: {} },
}
Sources: src/tools.ts:48-54
Returns: OK — emulator: <version_string>
pine_get_info
Retrieves comprehensive game and emulator metadata in a single batched call.
{
name: "pine_get_info",
description:
"PURPOSE: Fetch all available game metadata (title, serial, disc CRC, game version, run state) in one tool call. " +
"USAGE: Use at session start to identify the loaded game. For repeated status-only checks, pine_get_status is cheaper (1 opcode vs 5). " +
"BEHAVIOR: No side effects — pure reads. Batches PINE opcodes: Title (0x01), ID (0x02), UUID (0x03), GameVersion (0x05), Status (0x0F). " +
"Each field falls back to '(unavailable)' if the emulator returns PINE FAIL. " +
"RETURNS: Multi-line string with Title, Serial, Disc CRC, Game version, Status.",
inputSchema: { type: "object", properties: {} },
}
Sources: src/tools.ts:56-62
Implementation:
case "pine_get_info": {
const [title, id, uuid, gameVer, status] = await Promise.all([
pine.getTitle().catch(() => "(unavailable)"),
pine.getId().catch(() => "(unavailable)"),
pine.getUuid().catch(() => "(unavailable)"),
pine.getGameVersion().catch(() => "(unavailable)"),
pine.getStatus(),
]);
return ok(
`Title: ${title}\n` +
`Serial: ${id}\n` +
`Disc CRC: ${uuid}\n` +
`Game version: ${gameVer}\n` +
`Status: ${status}`,
);
}
Sources: src/tools.ts:16-27
Returns:
Title: <game title>
Serial: <game serial>
Disc CRC: <disc CRC UUID>
Game version: <version string>
Status: <running|paused|shutdown|unknown>
pine_get_status
Queries only the emulator's run state, optimized for repeated checks.
{
name: "pine_get_status",
description:
"PURPOSE: Get the emulator run state — 'running', 'paused', 'shutdown', or 'unknown'. " +
"USAGE: Cheap (1 PINE round-trip) check before timing-sensitive sequences — writes work while paused but only take visible effect after unpause. " +
"For game metadata (title, serial, CRC) use pine_get_info. PINE has no pause/resume opcode; this tool only reports state. " +
"BEHAVIOR: No side effects. Issues PINE Status opcode (0x0F), decodes the 32-bit response (0=running, 1=paused, 2=shutdown). " +
"RETURNS: 'Status: STATE' where STATE ∈ {running, paused, shutdown, unknown}.",
inputSchema: { type: "object", properties: {} },
}
Sources: src/tools.ts:95-101
Status Mapping:
| PINE Response | Emulator Status |
|---|---|
0 | running |
1 | paused |
2 | shutdown |
| Other | unknown |
Sources: src/pine.ts:20-22
Memory Read Operations
Single-Value Reads
The pine_read8, pine_read16, pine_read32, and pine_read64 tools read fixed-width values from the emulator's EE (Emotion Engine) address space.
case "pine_read8": return ok(`${addrHex(addr())}: ${fmtHex(await pine.read8(addr()))}`);
case "pine_read16": return ok(`${addrHex(addr())}: ${fmtHex(await pine.read16(addr()))}`);
case "pine_read32": return ok(`${addrHex(addr())}: ${fmtHex(await pine.read32(addr()))}`);
case "pine_read64": return ok(`${addrHex(addr())}: ${fmtHex(await pine.read64(addr()))}`);
Sources: src/tools.ts:29-32
#### Address Parameter
const ADDRESS_PARAM_DESC = (widthBytes: number) => {
const alignNote = widthBytes === 1
? "No alignment requirement for byte access."
: `MUST be ${widthBytes}-byte aligned (address % ${widthBytes} === 0). PINE on PCSX2 does NOT enforce ` +
`alignment — unaligned ${widthBytes * 8}-bit access typically returns whatever bytes are at the ` +
`aligned address below, silently corrupting the value. If you need an unaligned multi-byte read, ` +
`use pine_read_range and assemble the bytes yourself.`;
return (
`Absolute byte address in the EE main address space (NOT a per-domain offset).
...
Useful range: 0x00100000-0x01FFFFFF for EE main RAM (where 99% of game state lives).`
);
};
Sources: src/tools.ts:1-18
#### Alignment Requirements
| Tool | Width | Alignment | Corruption Risk |
|---|---|---|---|
pine_read8 | 1 byte | None | None |
pine_read16 | 2 bytes | 2-byte | Silently returns wrong value |
pine_read32 | 4 bytes | 4-byte | Silently returns wrong value |
pine_read64 | 8 bytes | 8-byte | Silently returns wrong value |
Sources: src/tools.ts:3-9
#### Tool Descriptions
pine_read8
description:
"PURPOSE: Read an unsigned 8-bit byte from the emulator's EE address space at the given absolute address. " +
"USAGE: Use for single-byte fields — status flags, counters, 8-bit enums, character bytes..."
pine_read16
description:
"PURPOSE: Read an unsigned 16-bit little-endian value from the emulator's EE address space..."
pine_read32
description:
"PURPOSE: Read an unsigned 32-bit little-endian value from the emulator's EE address space..." +
"little-endian (LSB at `address`, MSB at `address+3`). Address MUST be 4-byte aligned..."
pine_read64
description:
"PURPOSE: Read an unsigned 64-bit little-endian value from the emulator's EE address space..." +
"The PS2 EE is a 128-bit MIPS (Emotion Engine), and a lot of game state genuinely lives in 64-bit slots..."
Sources: src/tools.ts:103-134
Returns Format: ADDR_HEX: VAL_DEC (0xVAL_HEX)
64-bit Value Encoding: Due to JavaScript's number precision limit (2^53), 64-bit values are returned as decimal strings to preserve full precision.
pine_read_range (Bulk Read)
Reads a contiguous byte span up to 4096 bytes in a single tool call.
{
name: "pine_read_range",
description:
"PURPOSE: Read a contiguous byte span (1-4096 bytes) from the emulator's EE address space in one tool call. " +
"USAGE: Use for structs, strings, buffers, or any multi-byte region. Prefer over multiple single reads to minimize round-trips (1 call vs N). " +
"BEHAVIOR: No side effects — pure read. Internally dispatches read64/read32/read16/read8 calls, choosing the largest aligned width at each step...",
}
Sources: src/tools.ts:136-140
#### Implementation
async readRange(address: number, length: number): Promise<Buffer> {
const out = Buffer.alloc(length);
let cursor = address;
let outOffset = 0;
let remaining = length;
while (remaining > 0) {
let n: 1 | 2 | 4 | 8;
if (cursor % 8 === 0 && remaining >= 8) n = 8;
else if (cursor % 4 === 0 && remaining >= 4) n = 4;
else if (cursor % 2 === 0 && remaining >= 2) n = 2;
else n = 1;
steps.push({ op: n, addr: cursor, outOffset });
cursor += n;
outOffset += n;
remaining -= n;
}
// ...
}
Sources: src/pine.ts:24-45
#### Pipelining Behavior
graph LR
A[read_range call] --> B{env: PINE_PIPELINE_BATCH}
B -->|1| C[Fully Serial]
B -->|2+| D[Batched Pipeline]
C --> E[Safe<br/>~52ms for 4KB]
D --> F[Fast but risky<br/>May desync PCSX2]Sources: src/pine.ts:47-60
Performance: ~52 ms for full 4096-byte read on PCSX2 v2.6.3 over loopback TCP.
Pipeline Warning: PCSX2's PINE server has a fragile request queue. Dropping ANY request (occurs with ~7+ in-flight requests) desyncs the reply pipeline, causing all subsequent requests to timeout. Default is fully serial (PINE_PIPELINE_BATCH=1).
Memory Write Operations
pine_write8 through pine_write64
Write fixed-width little-endian values to the emulator's EE address space.
case "pine_write8": {
await pine.write8(addr(), p.value as number);
return ok(`Wrote ${fmtHex(p.value as number)} → ${addrHex(addr())}`);
}
case "pine_write16": {
await pine.write16(addr(), p.value as number);
return ok(`Wrote ${fmtHex(p.value as number)} → ${addrHex(addr())}`);
}
case "pine_write32": {
await pine.write32(addr(), p.value as number);
return ok(`Wrote ${fmtHex(p.value as number)} → ${addrHex(addr())}`);
}
Sources: src/tools.ts:34-41
#### Write Tool Parameters
| Parameter | Type | Description |
|---|---|---|
address | integer | Absolute byte address in EE address space |
value | integer | Value to write (decimal or hex) |
#### Alignment Requirements
Same as read operations—multi-byte writes must be aligned, or data corruption occurs silently.
#### Behavior Notes
- DESTRUCTIVE: Overwrites existing memory contents
- Side effects persist after the call completes
- Writes work while emulator is paused but changes only take effect after unpause
- Returns PINE FAIL on unmapped addresses
Sources: src/tools.ts:75-82
Save State Management
pine_save_state
Saves the current emulator state to a numbered slot.
{
name: "pine_save_state",
description:
"PURPOSE: Capture a full emulator snapshot and write it to a numbered save-state slot. " +
"USAGE: Use before experimental memory writes or to bookmark a checkpoint for later restoration. " +
"Save states are emulator-process snapshots — they persist across emulator restarts. " +
"BEHAVIOR: DESTRUCTIVE: Overwrites any existing state in the target slot. Issues PINE SaveState opcode (0x10). " +
"Slot files are stored in PCSX2's per-game savestate folder...",
inputSchema: {
type: "object",
required: ["slot"],
properties: {
slot: { type: "integer", minimum: 0, maximum: 255, description: SLOT_PARAM_DESC },
},
additionalProperties: false,
},
}
Sources: src/tools.ts:143-152
#### Slot Parameter
const SLOT_PARAM_DESC =
"Save state slot number (0-255). PCSX2 conventionally uses slots 0-9 (mapped to F1-F10 in the GUI), " +
"but the PINE protocol accepts the full 0-255 range. Slot files live in PCSX2's per-game savestate " +
"folder (typically %USERPROFILE%/Documents/PCSX2/sstates on Windows, ~/.config/PCSX2/sstates on Linux) " +
"with filenames like '<serial> (<crc>).<slot>.p2s'. Slot numbers are independent of any path.";
Sources: src/tools.ts:21-25
pine_load_state
Restores the emulator state from a numbered slot.
{
name: "pine_load_state",
description:
"PURPOSE: Load a previously saved emulator snapshot from a save-state slot. " +
"USAGE: Use to restore a checkpoint (e.g., after experimental memory writes that didn't work as expected). " +
"BEHAVIOR: DESTRUCTIVE: Replaces the current running state with the saved snapshot. " +
"Issues PINE LoadState opcode (0x11). Returns PINE FAIL if the slot is empty or corrupted.",
inputSchema: {
type: "object",
required: ["slot"],
properties: {
slot: { type: "integer", minimum: 0, maximum: 255, description: SLOT_PARAM_DESC },
},
additionalProperties: false,
},
}
Sources: src/tools.ts:154-163
#### Implementation
async saveState(slot: number): Promise<void> {
const args = Buffer.alloc(1); args.writeUInt8(slot, 0);
await this.call(Op.SaveState, args);
}
async loadState(slot: number): Promise<void> {
const args = Buffer.alloc(1); args.writeUInt8(slot, 0);
await this.call(Op.LoadState, args);
}
Sources: src/pine.ts:15-20
Tool Registration
Tools are registered with the MCP server through registerTools():
export function registerTools(server: Server, pine: PineClient): void {
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
server.setRequestHandler(CallToolRequestSchema, async (req) => {
const { name, arguments: args = {} } = req.params;
const p = args as Record<string, unknown>;
const addr = () => p.address as number;
switch (name) {
case "pine_ping": {
const v = await pine.getVersion();
return ok(`OK — emulator: ${v}`);
}
// ... other cases
}
});
}
Sources: src/tools.ts:158-173
Tool Definition Schema
Each tool follows a consistent structure:
| Field | Purpose |
|---|---|
name | Unique identifier for the tool |
description | PURPOSE / USAGE / BEHAVIOR / RETURNS template |
inputSchema | JSON Schema for parameters |
Error Handling
All tools implement consistent error handling:
| Error Condition | Response |
|---|---|
| Connection failure | Tool call rejects with error |
| PINE FAIL response | Error message returned |
| Timeout (10s default) | Tool call rejects |
| Invalid address | PINE FAIL response |
const ok = (text: string) => {
return { content: [{ type: "text" as const, text }] };
};
Sources: src/tools.ts:144-147
Configuration
| Environment Variable | Default | Purpose |
|---|---|---|
PINE_TARGET | pcsx2 | Emulator name for socket path |
PINE_SLOT | 28011 | PINE slot / port number |
PINE_PIPELINE_BATCH | 1 | Read range batch size |
Sources: README.md
Sources: src/tools.ts:47-140
Development Guide
Related topics: Troubleshooting Guide, Project Overview
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Related Pages
Related topics: Troubleshooting Guide, Project Overview
Development Guide
Overview
This guide covers the development workflow, architecture, and contribution guidelines for mcp-pine, an MCP (Model Context Protocol) server that bridges AI assistants like Claude to PlayStation 2/3 emulators via the PINE protocol.
The project enables read/write access to emulator memory, save state management, and game information retrieval through standardized MCP tool calls.
Project Structure
mcp-pine/
├── src/
│ ├── index.ts # Entry point, MCP server initialization
│ ├── pine.ts # PineClient - low-level PINE protocol implementation
│ └── tools.ts # MCP tool definitions and handlers
├── .scratch/
│ └── smoke.cjs # Quick smoke test script
├── package.json
├── tsconfig.json
└── Dockerfile
Key Modules
| Module | Responsibility |
|---|---|
src/index.ts | MCP server bootstrap, connection lifecycle, signal handling |
src/pine.ts | Socket communication, PINE opcodes, read/write operations |
src/tools.ts | Tool definitions, input schemas, result formatting |
Architecture
System Components
graph TD
A["MCP Client<br/>(Claude, etc.)"] --> B["mcp-pine<br/>MCP Server"]
B --> C["PineClient<br/>(TCP/Unix Socket)"]
C --> D["Emulator PINE Server<br/>(PCSX2/RPCS3/Duckstation)"]
B --> E["Tool Definitions<br/>(src/tools.ts)"]
C --> F["PINE Protocol<br/>(src/pine.ts)"]PINE Protocol Flow
sequenceDiagram
participant MCP as MCP Client
participant Bridge as mcp-pine
participant PINE as PINE Server
participant Emu as Emulator
MCP->>Bridge: call_tool(pine_read32)
Bridge->>PINE: TCP/Unix Socket
PINE->>Emu: Memory Read Request
Emu-->>PINE: Memory Value
PINE-->>Bridge: PINE Response Frame
Bridge-->>MCP: {content: text}Development Setup
Prerequisites
| Requirement | Version | Notes |
|---|---|---|
| Node.js | ≥ 22.0.0 | LTS recommended |
| npm | Bundled with Node | For package management |
| PCSX2 | ≥ 1.7.x | PINE-enabled build |
| TypeScript | ^5.5.0 | Development dependency |
Installation
# Clone the repository
git clone https://github.com/dmang-dev/mcp-pine
cd mcp-pine
# Install dependencies (runs build via prepare hook)
npm install
The prepare script in package.json automatically runs the build after installation:
"scripts": {
"prepare": "tsc"
}
Sources: package.json:8-10
Development Workflow
# Watch mode - recompile on file changes
npm run dev
# Manual build (if not using dev)
npm run build # or npx tsc
# Quick smoke test against running emulator
node .scratch/smoke.cjs
Sources: README.md:1
Source Code Analysis
Entry Point (src/index.ts)
The entry point initializes the MCP server with the SDK, creates a PineClient instance based on environment variables, and registers all tools.
Key initialization flow:
- Parse environment variables for connection options
- Create
PineClientwith resolved socket/TCP descriptor - Connect to emulator's PINE server
- Register tools with the MCP server
- Start server on stdio
PineClient (src/pine.ts)
The PineClient class handles low-level protocol communication.
Connection Modes:
| Platform | Method | Path/Address |
|---|---|---|
| Linux/macOS | Unix Socket | $XDG_RUNTIME_DIR/<target>.sock.<slot> |
| Linux fallback | Unix Socket | /tmp/<target>.sock.<slot> |
| Windows | TCP | 127.0.0.1:<slot> |
Supported Operations:
| Opcode | Operation | Description |
|---|---|---|
| 0x01 | Version | Get emulator PINE version |
| 0x02 | Title | Get loaded game title |
| 0x03 | ID | Get game serial ID |
| 0x04 | UUID | Get disc CRC |
| 0x05 | GameVersion | Get game version string |
| 0x10 | Status | Get emulator status |
| 0x20 | Read8 | Read 8-bit value |
| 0x21 | Read16 | Read 16-bit value |
| 0x22 | Read32 | Read 32-bit value |
| 0x23 | Read64 | Read 64-bit value |
| 0x30 | Write8 | Write 8-bit value |
| 0x31 | Write16 | Write 16-bit value |
| 0x32 | Write32 | Write 32-bit value |
| 0x33 | Write64 | Write 64-bit value |
| 0x40 | SaveState | Save to slot |
| 0x41 | LoadState | Load from slot |
Tool Definitions (src/tools.ts)
Each MCP tool follows the TDQS (Tool Definition Quality Score) rubric with explicit:
- PURPOSE: Single action sentence
- USAGE: When to use vs. sibling tools
- BEHAVIOR: Side effects, error conditions
- RETURNS: Exact output shape
Tool Categories:
| Category | Tools |
|---|---|
| Connectivity | pine_ping, pine_get_info |
| Read | pine_read8, pine_read16, pine_read32, pine_read64, pine_read_range |
| Write | pine_write8, pine_write16, pine_write32, pine_write64 |
| State | pine_save_state, pine_load_state |
Configuration
Environment Variables
| Variable | Default | Description |
|---|---|---|
PINE_TARGET | pcsx2 | Emulator name prefix for Unix socket path |
PINE_SLOT | 28011 | PINE slot number (also TCP port on Windows) |
PINE_PIPELINE_BATCH | 1 | Number of pipelined requests (see Warning below) |
XDG_RUNTIME_DIR | - | Unix socket directory (Linux/macOS) |
TMPDIR | - | Unix socket fallback directory |
Sources: README.md:1
Alignment Requirements
Critical: PINE on PCSX2 does NOT enforce alignment. Multi-byte access at unaligned addresses silently returns corrupted data from the aligned-below address.
| Access Width | Alignment Required |
|---|---|
| 8-bit (write8/read8) | None |
| 16-bit (write16/read16) | 2-byte |
| 32-bit (write32/read32) | 4-byte |
| 64-bit (write64/read64) | 8-byte |
Testing
Smoke Test
Run the provided smoke test against a running emulator:
# Start PCSX2 with PINE enabled, load a game
node .scratch/smoke.cjs
Manual Verification
# Test connection
claude mcp add pine --scope user mcp-pine
claude mcp list
# pine: mcp-pine - ✓ Connected
# Run a ping
claude -p "call pine_ping"
Building
TypeScript Compilation
npm run build
# or
npx tsc
Output is placed in dist/ directory with the same structure as src/.
Docker
A Dockerfile is provided for containerized deployment:
docker build -t mcp-pine .
docker run mcp-pine
Known Limitations
PCSX2 PINE Server Fragility
PCSX2's PINE server has a fragile request queue that drops requests silently when too many are pipelined. This desyncs the reply pipeline, causing all subsequent requests to time out until emulator restart.
| Setting | Safety | Latency |
|---|---|---|
PINE_PIPELINE_BATCH=1 | Safe (default) | ~52ms for 4KB read |
PINE_PIPELINE_BATCH=2+ | Risky | Lower latency |
Sources: src/pine.ts:150-160
Error Modes
| Symptom | Cause | Fix |
|---|---|---|
Cannot reach PINE server | Emulator not running, PINE disabled, wrong slot | Check PINE_SLOT |
PINE FAIL response | No game loaded, unmapped address | Load game, check address |
| Reads return zeros | Unallocated memory region | Try 0x00100000 (EE RAM) |
| Tool calls work but values corrupted | Endianness mismatch | PINE returns little-endian |
PINE call timed out (10s) after heavy use | PCSX2 PINE queue desync | Full emulator restart |
Emulator Support
PCSX2 (Primary)
- Launch PCSX2 1.7.x Qt or newer
- Settings → Advanced → Enable PINE Server
- Default slot is 28011
- Load any game
Sources: README.md:1
RPCS3 (Experimental)
PINE_TARGET=rpcs3 PINE_SLOT=<port> mcp-pine
Wire-level compatibility with PINE protocol not thoroughly tested.
Duckstation
PINE_TARGET=duckstation PINE_SLOT=<port> mcp-pine
Requires PINE-enabled Duckstation build.
Contribution Guidelines
- Tool descriptions: Follow TDQS rubric in
src/tools.ts - Error handling: All async operations must handle rejection
- Alignment: Document alignment requirements in parameter descriptions
- Testing: Verify changes against live PCSX2 before submitting
- Documentation: Update CHANGELOG.md with changes
Version History
| Version | Date | Key Changes |
|---|---|---|
| 0.2.1 | 2026-05-15 | Tool description quality pass |
| 0.2.0 | 2026-05-10 | Bulk read, 10s timeout, robustness |
| 0.1.0 | Earlier | Initial release |
Sources: CHANGELOG.md:1-30
Sources: package.json:8-10
Troubleshooting Guide
Related topics: Emulator Setup, Configuration Reference
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Continue reading this section for the full explanation and source context.
Related Pages
Related topics: Emulator Setup, Configuration Reference
Troubleshooting Guide
This guide covers common issues encountered when using mcp-pine, an MCP server that bridges the Model Context Protocol to the PINE IPC system used by PlayStation 2/3 emulators like PCSX2, RPCS3, and Duckstation.
Connection Troubleshooting
Cannot Reach PINE Server
Symptom: The MCP client reports it cannot connect to the PINE server.
Root Causes:
| Cause | Description | Fix |
|---|---|---|
| Emulator not running | PCSX2/RPCS3/Duckstation is not launched | Start the emulator with a game loaded |
| PINE not enabled | The PINE server toggle is off in emulator settings | Enable it in emulator settings |
| Slot/Port mismatch | PINE_SLOT doesn't match the emulator's configured port | Verify PINE_SLOT matches emulator configuration |
| Game not loaded | Some PINE operations require an active game session | Load any game before using tools |
Verification Steps:
# Check if mcp-pine is recognized by Claude Code
claude mcp list
# Expected output: pine: mcp-pine - ✓ Connected
# Test connection manually
PINE_TARGET=pcsx2 PINE_SLOT=28011 node .scratch/smoke.cjs
Sources: README.md:70-73
Transport Layer Differences
| Platform | Transport | Path/Address |
|---|---|---|
| Windows | TCP | 127.0.0.1:<PINE_SLOT> |
| Linux/macOS | Unix Socket | $XDG_RUNTIME_DIR/<PINE_TARGET>.sock.<PINE_SLOT> with $TMPDIR//tmp fallbacks |
The connection transport is automatically selected based on the operating system. Windows uses TCP loopback while Unix-like systems use Unix domain sockets.
Sources: src/pine.ts:80-88
PINE Response Errors
PINE FAIL Response (0xFF)
Symptom: Tool returns "PINE FAIL response" error.
Common Causes:
- No game loaded — Most PINE operations require an active game session
- Unmapped address — The memory address doesn't map to any allocated region
- Invalid opcode — The emulator doesn't support the requested operation
Diagnostic Approach:
// Try reading from a known-good address first (EE main RAM)
const test = await pine.read32(0x00100000);
The address 0x00100000 is almost always inside loaded EE RAM and serves as a good sanity check.
Sources: README.md:74-76
Reads Return Zeros
Symptom: Memory reads return all zeros instead of expected values.
Cause: The address falls in an unallocated memory region.
Solution: Start with 0x00100000 (EE main RAM base). The valid EE main RAM range is 0x00100000 to 0x01FFFFFF.
// Verify address is in valid range
// EE main RAM: 0x00100000 - 0x01FFFFFF
// IOP RAM: 0x1C000000+
// Scratchpad: 0x1F800000 - 0x1F8003FF
Sources: src/tools.ts:55-60
Corrupted Values / Wrong Data
Symptom: Read values appear garbled or incorrect.
Root Cause: Endianness mismatch.
PINE returns data in little-endian format. When interpreting multi-byte values or strings, ensure your code handles little-endian encoding.
// For strings, use read_range (byte-level read)
const bytes = await pine.readRange(0x00100000, 64);
const str = bytes.toString('utf8').replace(/\0+$/, '');
// For multi-byte integers, PINE handles conversion automatically
const value = await pine.read32(0x00100000); // Already little-endian decoded
Sources: README.md:77-80
PCSX2-Specific Issues
PINE Server Wedging
Symptom: pine_ping times out after heavy use, even though the emulator appears to be running.
Root Cause: PCSX2's PINE server has a fragile request queue that silently drops requests when too many are pipelined. As few as 7 in-flight requests can trigger this behavior. When any request is dropped, the reply pipeline becomes desynchronized — every subsequent reply is delivered to the wrong waiting client.
graph TD
A[Normal Operation] -->|~7 in-flight requests| B[Request Dropped]
B --> C[Reply Pipeline Desync]
C --> D[All Replies Misaligned]
D --> E[Every Call Times Out]
E --> F[Emulator Must Be Fully Restarted]Recovery Steps:
- Fully quit PCSX2 (not just close the game)
- Restart PCSX2
- Load the game
- Reconnect the MCP client
Note: Simply reconnecting the client does NOT fix the desync — the corruption is on the emulator side.
Sources: README.md:83-91, CHANGELOG.md:45-52
Bulk Read Performance
Symptom: pine_read_range feels slow compared to mGBA's read_range.
Explanation: PINE has no native bulk read opcode. The client issues reads serially by default, choosing the largest aligned width at each step (read64 on 8-byte boundaries, falling back to 32/16/8).
| Operation | Latency | Notes |
|---|---|---|
| Full 4096-byte read | ~52 ms | Measured on PCSX2 v2.6.3 over loopback |
| Frame budget at 60fps | ~16.6 ms | Bulk read takes ~3 frames |
Optimization Option: Set PINE_PIPELINE_BATCH=2 to enable limited pipelining. This reduces latency but risks desyncing PCSX2's PINE server if the emulator's queue becomes overloaded.
# More aggressive pipelining (at your own risk)
PINE_PIPELINE_BATCH=2 mcp-pine
Sources: README.md:92-95, src/pine.ts:95-105
Memory Access Issues
Alignment Requirements
Critical: Multi-byte memory operations have alignment requirements that PCSX2 does NOT enforce.
| Operation | Alignment | Unaligned Behavior |
|---|---|---|
pine_read8 / pine_write8 | None | Works correctly |
pine_read16 / pine_write16 | 2-byte | Silently returns corrupted data |
pine_read32 / pine_write32 | 4-byte | Silently returns corrupted data |
pine_read64 / pine_write64 | 8-byte | Silently returns corrupted data |
Safe Alternative: For unaligned access, use pine_read_range and assemble the bytes manually.
// Unaligned 32-bit read at 0x00100003
const bytes = await pine.readRange(0x00100003, 4);
const value = bytes.readUInt32LE(0);
Sources: src/tools.ts:42-50
64-bit Value Precision
JavaScript loses precision for integers beyond 2^53. For 64-bit operations, values are exchanged as decimal strings.
// Writing a large 64-bit value
await pine.write64(0x00100000, "18446744073709551615" as unknown as bigint);
// Reading returns string-encoded decimal
const largeValue = await pine.read64(0x00100000);
// Returns: "18446744073709551615" (as string)
Sources: CHANGELOG.md:28-30
Timeout Issues
10-Second Call Timeout
Every PINE call has a 10-second timeout. If the emulator drops a reply (e.g., due to the wedging issue), the call rejects cleanly rather than hanging indefinitely.
Timeouts indicate:
- PCSX2 PINE server is wedged (most common)
- Network/connection interruption
- Emulator is unresponsive
If you experience frequent timeouts:
- Check if PCSX2's PINE server has wedged
- Verify no other tool is aggressive pipelining
- Consider restarting the emulator
Sources: CHANGELOG.md:26-29
Emulator-Specific Setup
PCSX2 (Recommended)
- Launch PCSX2 1.7.x Qt or newer
- Navigate to Settings → Advanced → Enable PINE Server
- Default slot is 28011 — only change
PINE_SLOTif you configure a different port - Load any game
PINE is always-on once enabled; no scripts or console commands needed.
Sources: README.md:44-54
RPCS3
RPCS3 uses its own IPC implementation that mirrors PINE's opcode set. Wire-level compatibility hasn't been thoroughly tested.
``bash PINE_TARGET=rpcs3 PINE_SLOT=<port> mcp-pine ``
- Enable IPC server in Configuration → Advanced
- Note the configured port
- Run with:
Sources: README.md:58-65
Duckstation
Duckstation includes a PINE server only in specific builds. Check whether your build includes it:
- Set
PINE_TARGET=duckstation - Configure
PINE_SLOTto match the port shown in Duckstation's settings - If connection fails, your build likely doesn't include PINE support
Sources: README.md:5-9
Environment Variables Reference
| Variable | Default | Purpose |
|---|---|---|
PINE_TARGET | pcsx2 | Emulator name — used as prefix in Unix socket path on Linux/macOS |
PINE_SLOT | 28011 | PINE slot / port number |
PINE_HOST | 127.0.0.1 | TCP host override (Windows always uses this) |
PINE_SOCKET_PATH | auto | Full Unix socket path override |
PINE_PIPELINE_BATCH | 1 | Number of serial reads to batch together (higher = faster but riskier) |
Sources: README.md:34-40
Diagnostic Checklist
When reporting issues, include the following information:
- [ ] Emulator name and version (e.g., PCSX2 v2.6.3)
- [ ] Operating system and version
- [ ]
PINE_TARGETandPINE_SLOTvalues - [ ] Whether a game is currently loaded
- [ ] Exact error message received
- [ ] Steps to reproduce
- [ ] Whether restarting the emulator resolves the issue
Getting Help
If issues persist after following this guide:
``bash node .scratch/smoke.cjs ``
- Search existing GitHub Issues
- File a new issue with diagnostic information
- Include the output of the smoke test:
Sources: README.md:1-3, package.json:8-9
Source: https://github.com/dmang-dev/mcp-pine / Human Manual
Doramagic Pitfall Log
Source-linked risks stay visible on the manual page so the preview does not read like a recommendation.
First-time setup may fail or require extra isolation and rollback planning.
Users may get misleading failures or incomplete behavior unless configuration is checked carefully.
The project should not be treated as fully validated until this signal is reviewed.
Users cannot judge support quality until recent activity, releases, and issue response are checked.
Doramagic Pitfall Log
Doramagic extracted 8 source-linked risk signals. Review them before installing or handing real data to the project.
1. Installation risk: Submit listing PR to punkpeye/awesome-mcp-servers
- Severity: medium
- Finding: Installation risk is backed by a source signal: Submit listing PR to punkpeye/awesome-mcp-servers. Treat it as a review item until the current version is checked.
- User impact: First-time setup may fail or require extra isolation and rollback planning.
- Recommended check: Open the linked source, confirm whether it still applies to the current version, and keep the first run isolated.
- Evidence: Source-linked evidence: https://github.com/dmang-dev/mcp-pine/issues/2
2. Configuration risk: Configuration risk needs validation
- Severity: medium
- Finding: Configuration risk is backed by a source signal: Configuration risk needs validation. Treat it as a review item until the current version is checked.
- User impact: Users may get misleading failures or incomplete behavior unless configuration is checked carefully.
- Recommended check: Open the linked source, confirm whether it still applies to the current version, and keep the first run isolated.
- Evidence: capability.host_targets | github_repo:1234265030 | https://github.com/dmang-dev/mcp-pine | host_targets=mcp_host, claude
3. Capability assumption: README/documentation is current enough for a first validation pass.
- Severity: medium
- Finding: README/documentation is current enough for a first validation pass.
- User impact: The project should not be treated as fully validated until this signal is reviewed.
- Recommended check: Open the linked source, confirm whether it still applies to the current version, and keep the first run isolated.
- Evidence: capability.assumptions | github_repo:1234265030 | https://github.com/dmang-dev/mcp-pine | README/documentation is current enough for a first validation pass.
4. Maintenance risk: Maintainer activity is unknown
- Severity: medium
- Finding: Maintenance risk is backed by a source signal: Maintainer activity is unknown. Treat it as a review item until the current version is checked.
- User impact: Users cannot judge support quality until recent activity, releases, and issue response are checked.
- Recommended check: Open the linked source, confirm whether it still applies to the current version, and keep the first run isolated.
- Evidence: evidence.maintainer_signals | github_repo:1234265030 | https://github.com/dmang-dev/mcp-pine | last_activity_observed missing
5. Security or permission risk: no_demo
- Severity: medium
- Finding: no_demo
- User impact: The project may affect permissions, credentials, data exposure, or host boundaries.
- Recommended check: Open the linked source, confirm whether it still applies to the current version, and keep the first run isolated.
- Evidence: downstream_validation.risk_items | github_repo:1234265030 | https://github.com/dmang-dev/mcp-pine | no_demo; severity=medium
6. Security or permission risk: no_demo
- Severity: medium
- Finding: no_demo
- User impact: The project may affect permissions, credentials, data exposure, or host boundaries.
- Recommended check: Open the linked source, confirm whether it still applies to the current version, and keep the first run isolated.
- Evidence: risks.scoring_risks | github_repo:1234265030 | https://github.com/dmang-dev/mcp-pine | no_demo; severity=medium
7. Maintenance risk: issue_or_pr_quality=unknown
- Severity: low
- Finding: issue_or_pr_quality=unknown。
- User impact: Users cannot judge support quality until recent activity, releases, and issue response are checked.
- Recommended check: Open the linked source, confirm whether it still applies to the current version, and keep the first run isolated.
- Evidence: evidence.maintainer_signals | github_repo:1234265030 | https://github.com/dmang-dev/mcp-pine | issue_or_pr_quality=unknown
8. Maintenance risk: release_recency=unknown
- Severity: low
- Finding: release_recency=unknown。
- User impact: Users cannot judge support quality until recent activity, releases, and issue response are checked.
- Recommended check: Open the linked source, confirm whether it still applies to the current version, and keep the first run isolated.
- Evidence: evidence.maintainer_signals | github_repo:1234265030 | https://github.com/dmang-dev/mcp-pine | release_recency=unknown
Source: Doramagic discovery, validation, and Project Pack records
Community Discussion Evidence
These external discussion links are review inputs, not standalone proof that the project is production-ready.
Count of project-level external discussion links exposed on this manual page.
Open the linked issues or discussions before treating the pack as ready for your environment.
Community Discussion Evidence
Doramagic exposes project-level community discussion separately from official documentation. Review these links before using mcp-pine with real data or production workflows.
- Submit listing PR to punkpeye/awesome-mcp-servers - github / github_issue
- v0.3.0 — target-aware tool descriptions - github / github_release
- Configuration risk needs validation - GitHub / issue
Source: Project Pack community evidence and pitfall evidence