Doramagic Project Pack · Human Manual

mcp-pine

Related topics: System Architecture, Emulator Setup

Project Overview

Related topics: System Architecture, Emulator Setup

Section Related Pages

Continue reading this section for the full explanation and source context.

Section Connectivity & Introspection

Continue reading this section for the full explanation and source context.

Section Memory Reads

Continue reading this section for the full explanation and source context.

Section Memory Writes

Continue reading this section for the full explanation and source context.

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

The system consists of three main layers:

LayerComponentPurpose
MCP HostClaude Desktop / Claude CodeUser interface; issues tool calls via MCP
Bridgemcp-pine (Node.js)Translates MCP requests to PINE protocol; manages connection lifecycle
EmulatorPCSX2 / RPCS3 / DuckstationExecutes game; exposes PINE server for IPC

Sources: README.md:23-51

Supported Emulators

EmulatorPlatformStatusNotes
PCSX2Windows / Linux / macOSPrimaryFull PINE support; default target
RPCS3Windows / LinuxCompatibleIPC implementation mirrors PINE; wire-level compatibility untested
DuckstationMulti-platformCompatibleVaries 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

ToolDescription
pine_pingReturns emulator version string. Use to verify connectivity.
pine_get_infoBatch-retrieves title, serial, disc CRC, game version, and status in one round-trip.
pine_get_statusReturns emulator run state: running, paused, shutdown, or unknown.

Sources: src/tools.ts:45-85

Memory Reads

ToolWidthAlignmentUse Case
pine_read88-bitNoneStatus flags, counters, single bytes
pine_read1616-bit2-byte16-bit counters, flags
pine_read3232-bit4-bytePointers, flags, game state values
pine_read6464-bit8-byteLarge IDs, EE pointers, double-word state
pine_read_rangeVariableAdaptiveBulk 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

ToolWidthDestructiveNotes
pine_write88-bitYesSingle-byte overwrite
pine_write1616-bitYesRequires 2-byte alignment
pine_write3232-bitYesRequires 4-byte alignment
pine_write6464-bitYesRequires 8-byte alignment; values encoded as decimal strings

Sources: src/tools.ts:200-260

Save State Management

ToolDescription
pine_save_stateSerializes current emulator state to a numbered slot (0-255). Overwrites existing slot.
pine_load_stateRestores 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

VariableDefaultPlatformPurpose
PINE_TARGETpcsx2Linux/macOSEmulator name; used as Unix socket filename prefix (<target>.sock.<slot>)
PINE_SLOT28011AllPINE slot number (also port for TCP on Windows)
PINE_HOST127.0.0.1WindowsTCP host override
PINE_SOCKET_PATHAuto-resolvedUnixFull socket path override
PINE_PIPELINE_BATCH1AllNumber of serial reads to batch; higher values risk desyncing PCSX2's PINE server

Sources: README.md:52-60

Socket Resolution

PlatformTransportPath Resolution
Linux/macOSUnix Domain Socket$XDG_RUNTIME_DIR/<target>.sock.<slot>$TMPDIR/<target>.sock.<slot>/tmp/<target>.sock.<slot>
WindowsTCP127.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 value

Wire format per call:

FieldSizeEncoding
Frame size4 bytesUInt32LE (includes opcode + payload)
Opcode1 byteUInt8
PayloadVariableLittle-endian

Opcodes:

OpcodeNameDirectionPayload
0x01VersionReadNone
0x02TitleReadNone
0x03IDReadNone
0x04UUIDReadNone
0x05GameVersionReadNone
0x06SaveStateWrite1 byte (slot)
0x07LoadStateWrite1 byte (slot)
0x08Read8Read4 bytes (address)
0x09Read16Read4 bytes (address)
0x0ARead32Read4 bytes (address)
0x0BRead64Read4 bytes (address)
0x0CWrite8Write1 byte (value) + 4 bytes (address)
0x0DWrite16Write2 bytes (value) + 4 bytes (address)
0x0EWrite32Write4 bytes (value) + 4 bytes (address)
0x0FStatusReadNone
0x10Write64Write8 bytes (value) + 4 bytes (address)

Sources: src/pine.ts:81-200

Installation

Package Distribution

MethodCommandUse Case
npm globalnpm install -g mcp-pineSystem-wide installation
npx (on-demand)npx -y mcp-pineTemporary testing
Sourcegit clone + npm installDevelopment

Sources: README.md:61-72

Claude Desktop Integration

Add to claude_desktop_config.json:

{
  "mcpServers": {
    "pine": {
      "command": "mcp-pine"
    }
  }
}

Config location by platform:

PlatformPath
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.

SettingValueLatencySafety
PINE_PIPELINE_BATCH=1Serial~52ms for 4096 bytesSafe (default)
PINE_PIPELINE_BATCH=2+BatchedLowerRisk 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

SymptomCauseFix
Cannot reach PINE serverEmulator not running, PINE disabled, port mismatchCheck PINE_SLOT and emulator settings
PINE FAIL response (0xFF)No game loaded, address unmappedLoad a game; verify address range
Reads return zerosAddress in unallocated regionTry 0x00100000 (EE main RAM)
Values look corruptedEndianness mismatchPINE returns little-endian
PINE call timed out (10s) after heavy usePCSX2 PINE server desyncedFully 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

ProjectRepositoryDescription
mcp-mgbadmang-dev/mcp-mgbaSister MCP server for mGBA (Game Boy Advance); includes button input and screenshot support
PINE ProtocolGovanifY/pineUnderlying IPC specification

Sources: README.md:140-145

Sources: README.md:1

Installation Guide

Related topics: Configuration Reference, Troubleshooting Guide

Section Related Pages

Continue reading this section for the full explanation and source context.

Section Method 1: Global npm Install

Continue reading this section for the full explanation and source context.

Section Method 2: Direct Execution with npx

Continue reading this section for the full explanation and source context.

Section Method 3: Clone and Develop

Continue reading this section for the full explanation and source context.

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.

ComponentTechnologyPurpose
MCP ServerTypeScript + Node.jsBridges MCP clients to PINE protocol
TransportstdioStandard MCP communication
Emulator IPCPINE protocolBinary memory/state access
ConnectionsTCP/Unix SocketPlatform-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 1.7.x and newer include native PINE server support:

  1. Launch PCSX2 (1.7.x Qt or newer)
  2. Navigate to Settings → Advanced → Enable PINE Server
  3. 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:

  1. Navigate to Configuration → Advanced → Enable IPC server
  2. Note the configured port
  3. 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:

  1. Verify your build includes a PINE server
  2. 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:

PlatformPath
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 VariableDefaultPurpose
PINE_TARGETpcsx2Emulator name—used as Unix socket prefix on Linux/macOS (<target>.sock.<slot>). Ignored on Windows (TCP only).
PINE_SLOT28011PINE slot number—also the TCP port on Windows.
PINE_HOST127.0.0.1Override the connection host
PINE_SOCKET_PATHAuto-resolvedOverride the Unix socket path
PINE_PIPELINE_BATCH1Number 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

SymptomCause / Fix
Cannot reach PINE serverEmulator 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 zerosAddress is in an unallocated region. Try 0x00100000 first (almost always inside EE RAM).
Tool calls work but values look corruptedCheck endianness—PINE returns little-endian. Use read_range-style byte reads for strings.
pine_ping times out after heavy usePCSX2 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.

OperationLatencyNotes
Full 4096-byte read~52 msFully serial requests over loopback TCP
Pipelined requestsFaster but riskyPINE_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

PackageVersionPurpose
@modelcontextprotocol/sdk^1.12.0MCP server framework
@types/node^22.0.0TypeScript types
typescript^5.5.0Build 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

Section Related Pages

Continue reading this section for the full explanation and source context.

Section Environment Variables

Continue reading this section for the full explanation and source context.

Section Socket Path Resolution

Continue reading this section for the full explanation and source context.

Section Prerequisites

Continue reading this section for the full explanation and source context.

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:

EmulatorPlatformPINE SupportNotes
PCSX2 (1.7.x Qt+)AllNativeFull support; recommended
RPCS3AllPartialIPC implementation mirrors PINE; wire compatibility untested
DuckstationAllVariesBuild-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:#87CEEB

Connection 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

VariableDefaultPurpose
PINE_TARGETpcsx2Emulator name—used as Unix socket filename prefix on Linux/macOS
PINE_SLOT28011PINE slot number (port on Windows, socket suffix on Unix)
PINE_PIPELINE_BATCH1Number 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:#87CEEB

Sources: 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

  1. Launch PCSX2
  2. 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
  1. Verify the default slot is 28011
  2. 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

  1. Navigate to Configuration → Advanced → Enable IPC server
  2. Note the configured port number
  3. 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:

PlatformPath
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:

ToolPurposeCall Type
pine_pingVerify connection to emulator1 round-trip
pine_get_infoGet game title, serial, CRC, version, and status5 parallel calls
pine_get_statusGet emulator run state (running/paused/shutdown)1 round-trip
pine_read8/16/32/64Read bytes from EE memory1 round-trip each
pine_read_rangeBulk read up to 4096 bytesSerial sequence
pine_write8/16/32/64Write bytes to EE memory1 round-trip each
pine_save_stateSave emulator state to slot1 round-trip
pine_load_stateLoad emulator state from slot1 round-trip

Address Parameter Notes

All address parameters reference the EE main address space. Key memory regions:

RegionAddress RangeDescription
EE Main RAM0x001000000x01FFFFFFPrimary game state memory (99% of use cases)
IOP RAM0x1C000000+Input/Output Processor memory
Scratchpad0x700000000x70003FFFFast local memory

Alignment requirements:

WidthAlignmentNotes
8-bitNoneSafe to use at any address
16-bit2-byteMisaligned access silently corrupts data
32-bit4-byteMisaligned access silently corrupts data
64-bit8-byteMisaligned 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

SymptomCauseFix
Cannot reach PINE serverEmulator not running, PINE not enabled, or slot mismatchCheck PINE_SLOT matches emulator setting
PINE FAIL response (0xFF)No game loaded, or address unmappedLoad a game; try 0x00100000 first
Reads return zerosAddress in unallocated regionUse 0x00100000 (EE RAM base)
Values look corruptedWrong endianness or alignmentPINE returns little-endian; use pine_read_range for byte-level control
PINE call timed out (10s) after heavy usePCSX2 PINE queue desyncFull 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_ping calls timing out after heavy use
  • Intermittent read/write failures
  • Non-deterministic response values

Fix:

  1. Fully restart PCSX2
  2. Reconnect mcp-pine
  3. Reduce PINE_PIPELINE_BATCH to 1 (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:

OperationLatency
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

Section Related Pages

Continue reading this section for the full explanation and source context.

Section Layer 1: MCP Server

Continue reading this section for the full explanation and source context.

Section Layer 2: PINE Protocol Client

Continue reading this section for the full explanation and source context.

Section Layer 3: Transport Layer

Continue reading this section for the full explanation and source context.

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 <--> F

Sources: 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.

ComponentFileResponsibility
registerTools()src/tools.tsRegisters all 13 MCP tools with the SDK
Tool dispatchersrc/tools.tsRoutes CallToolRequest to corresponding PINE operations
Response formattersrc/tools.tsFormats 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

OperationOpcodeDirectionDescription
getVersionOp.VersionReadEmulator version string
getTitleOp.TitleReadGame title
getIdOp.IDReadGame serial number
getUuidOp.UUIDReadDisc CRC hash
getGameVersionOp.GameVersionReadGame version string
getStatusOp.StatusReadEmulator state (running/paused/shutdown)
read8/16/32/64Op.Read*ReadMemory reads at specified width
write8/16/32/64Op.Write*WriteMemory writes at specified width
saveStateOp.SaveStateWriteSave to slot (0-255)
loadStateOp.LoadStateWriteLoad 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

PriorityLinux/macOS PathWindows
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 2

Sources: 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 VariableDefaultEffect
PINE_PIPELINE_BATCH1Number 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.

RegionAddress RangeSizeDescription
EE Main RAM0x00100000 - 0x01FFFFFF~31 MBPrimary game state storage
IOP RAM0x1C000000+VariableI/O Processor memory
ScratchpadSpecial range16 KBFast-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.

OperationInput FormatOutput FormatReason
pine_write64Decimal string ("18446744073709551615")Preserve full u64 range
pine_read64Decimal stringPreserve full u64 range

Sources: src/tools.ts - 64-bit parameter descriptions

Configuration Environment Variables

VariableDefaultPlatformPurpose
PINE_TARGETpcsx2AllEmulator name prefix for Unix socket path
PINE_SLOT28011AllPINE slot number (also port on Windows)
PINE_HOST127.0.0.1WindowsTCP host override
PINE_SOCKET_PATHAutoAllCustom Unix socket path
PINE_PIPELINE_BATCH1AllConcurrent request batching
PIPELINE_BATCH1AllAlias for PINE_PIPELINE_BATCH

Sources: README.md - Configuration table

Error Handling

ErrorCauseFix
Cannot reach PINE serverEmulator not running or PINE disabledCheck PINE_SLOT matches emulator settings
PINE FAIL response (0xFF)No game loaded or address unmappedLoad a game; verify address range
Reads return zerosAddress in unallocated regionTry 0x00100000 first (EE RAM base)
Values look corruptedEndianness mismatchPINE returns little-endian; check parsing
PINE call timed out (10s)PCSX2 PINE server wedgedFull 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

PackageVersionPurpose
@modelcontextprotocol/sdk^1.12.0MCP protocol implementation
typescript^5.5.0TypeScript compilation (dev)
@types/node^22.0.0Node.js type definitions (dev)

Sources: package.json - Dependencies section

Supported Emulators

EmulatorPlatformStatusNotes
PCSX2AllPrimaryFull PINE support; Qt 1.7.x+
RPCS3AllExperimentalIPC implementation mirrors PINE; wire compatibility untested
DuckstationAllVariesDepends 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

Section Related Pages

Continue reading this section for the full explanation and source context.

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

Section Related Pages

Continue reading this section for the full explanation and source context.

Section Component Hierarchy

Continue reading this section for the full explanation and source context.

Section Request-Response Flow

Continue reading this section for the full explanation and source context.

Section Key Components

Continue reading this section for the full explanation and source context.

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 --> G

Request-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

ComponentFileResponsibility
PineClientsrc/pine.tsSocket management, request queuing, protocol encoding/decoding
Tool definitionssrc/tools.tsMCP tool schemas, parameter validation, response formatting
Opcode enumsrc/pine.tsPINE 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:

ToolWidthOpcodeAlignment
pine_read88-bit / 1 byteOp.Read8None required
pine_read1616-bit / 2 bytesOp.Read162-byte
pine_read3232-bit / 4 bytesOp.Read324-byte
pine_read6464-bit / 8 bytesOp.Read648-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:

RegionAddress RangeDescription
EE Main RAM0x00100000 - 0x01FFFFFFWhere 99% of game state lives
IOP RAM0x1C000000+Input/Output Processor memory
Scratchpad0x70000000 - 0x70003FFFFast 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.
OperationRequired AlignmentMisaligned Behavior
read8NoneCorrect
read162-byteReturns wrong value silently
read324-byteReturns wrong value silently
read648-byteReturns 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:

ToolWidthOpcodeValue Encoding
pine_write88-bitOp.Write8Number 0-255
pine_write1616-bitOp.Write16Number 0-65535
pine_write3232-bitOp.Write32Number 0-4294967295
pine_write6464-bitOp.Write64Decimal 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:

ConfigurationMeasured 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:

ToolOpcodeDescription
pine_save_stateOp.SaveStateSave current emulator state to slot
pine_load_stateOp.LoadStateLoad 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

ErrorCauseFix
Cannot reach PINE serverEmulator not running, PINE disabled, wrong slotCheck PINE_SLOT setting
PINE FAIL response (0xFF)Emulator rejected requestEnsure game loaded, check address validity
Timeout (10s)Reply droppedRestart emulator if PCSX2 PINE is wedged

Data Corruption

SymptomCauseFix
Reads return zerosAddress in unallocated regionTry 0x00100000 first
Values look corruptedWrong endianness interpretationPINE returns little-endian
Silent wrong valuesUnaligned accessAlign addresses or use read_range

Sources: README.md:80-100

Configuration

Environment Variables

VariableDefaultPurpose
PINE_TARGETpcsx2Emulator name prefix for Unix socket path
PINE_SLOT28011PINE slot / port number
PINE_PIPELINE_BATCH1Parallel 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

ToolInput SchemaReturn Format
pine_pingNoneOK — emulator: <version>
pine_get_infoNoneMulti-line status with title, serial, CRC, version
pine_get_statusNone`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

Section Related Pages

Continue reading this section for the full explanation and source context.

Section Component Overview

Continue reading this section for the full explanation and source context.

Section Savestate Workflow

Continue reading this section for the full explanation and source context.

Section pinesavestate

Continue reading this section for the full explanation and source context.

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| B

Savestate 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.

PropertyValue
Tool Namepine_save_state
Sourcesrc/tools.ts:1-20

Parameters:

ParameterTypeConstraintsDescription
slotinteger0-255Save 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.

PropertyValue
Tool Namepine_load_state
Sourcesrc/tools.ts:1-20

Parameters:

ParameterTypeConstraintsDescription
slotinteger0-255Save 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 RangeConventional UseGUI Mapping
0Current run / temporary checkpointManual
1-9Quick save slotsF1-F10
10-99Scene-specific savesNamed folders
100-255Long-term snapshotsArchive

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:

PlatformPath
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 --> A

Memory 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"| B

Example 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

ErrorCauseFix
PINE FAIL responseNo game loaded, or slot is invalidLoad a game before saving
Slot contains garbageCorrupted savestate fileDelete file and re-save
Load restores wrong gameSerial/CRC mismatchUse correct game or different slot

Connection Requirements

The emulator must be running with PINE enabled:

  1. Launch PCSX2 (1.7.x Qt or newer)
  2. Navigate to Settings → Advanced → Enable PINE Server
  3. Default slot is 28011 (configurable via PINE_SLOT)

Sources: README.md:1-20

Configuration

Savestate operations are affected by the following environment variables:

VariableDefaultEffect
PINE_SLOT28011Sets the PINE slot used for communication
PINE_TARGETpcsx2Emulator name prefix for Unix socket path
PINE_TIMEOUT10000 (ms)Per-call timeout; affects savestate operations
ToolPurpose
pine_get_infoGet game metadata before saving (title, serial, CRC)
pine_get_statusCheck if emulator is running/paused before operations
pine_read_*Read memory from restored state
pine_write_*Modify memory after loading a state

See Also

Source: https://github.com/dmang-dev/mcp-pine / Human Manual

System Query Tools

Related topics: Memory Operations, Savestate Management

Section Related Pages

Continue reading this section for the full explanation and source context.

Section Architecture

Continue reading this section for the full explanation and source context.

Section Tool Categories

Continue reading this section for the full explanation and source context.

Section pineping

Continue reading this section for the full explanation and source context.

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

Tool Categories

CategoryToolsPurpose
Connectivitypine_pingVerify emulator connection and retrieve version
Introspectionpine_get_info, pine_get_statusQuery game metadata and emulator state
Memory Readpine_read8, pine_read16, pine_read32, pine_read64, pine_read_rangeRead from emulator's EE address space
Memory Writepine_write8, pine_write16, pine_write32, pine_write64Write to emulator's memory
State Managementpine_save_state, pine_load_stateManipulate 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 ResponseEmulator Status
0running
1paused
2shutdown
Otherunknown

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

ToolWidthAlignmentCorruption Risk
pine_read81 byteNoneNone
pine_read162 bytes2-byteSilently returns wrong value
pine_read324 bytes4-byteSilently returns wrong value
pine_read648 bytes8-byteSilently 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

ParameterTypeDescription
addressintegerAbsolute byte address in EE address space
valueintegerValue 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:

FieldPurpose
nameUnique identifier for the tool
descriptionPURPOSE / USAGE / BEHAVIOR / RETURNS template
inputSchemaJSON Schema for parameters

Error Handling

All tools implement consistent error handling:

Error ConditionResponse
Connection failureTool call rejects with error
PINE FAIL responseError message returned
Timeout (10s default)Tool call rejects
Invalid addressPINE FAIL response
const ok = (text: string) => {
  return { content: [{ type: "text" as const, text }] };
};

Sources: src/tools.ts:144-147

Configuration

Environment VariableDefaultPurpose
PINE_TARGETpcsx2Emulator name for socket path
PINE_SLOT28011PINE slot / port number
PINE_PIPELINE_BATCH1Read range batch size

Sources: README.md

Sources: src/tools.ts:47-140

Development Guide

Related topics: Troubleshooting Guide, Project Overview

Section Related Pages

Continue reading this section for the full explanation and source context.

Section Key Modules

Continue reading this section for the full explanation and source context.

Section System Components

Continue reading this section for the full explanation and source context.

Section PINE Protocol Flow

Continue reading this section for the full explanation and source context.

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

ModuleResponsibility
src/index.tsMCP server bootstrap, connection lifecycle, signal handling
src/pine.tsSocket communication, PINE opcodes, read/write operations
src/tools.tsTool 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

RequirementVersionNotes
Node.js≥ 22.0.0LTS recommended
npmBundled with NodeFor package management
PCSX2≥ 1.7.xPINE-enabled build
TypeScript^5.5.0Development 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:

  1. Parse environment variables for connection options
  2. Create PineClient with resolved socket/TCP descriptor
  3. Connect to emulator's PINE server
  4. Register tools with the MCP server
  5. Start server on stdio

PineClient (src/pine.ts)

The PineClient class handles low-level protocol communication.

Connection Modes:

PlatformMethodPath/Address
Linux/macOSUnix Socket$XDG_RUNTIME_DIR/<target>.sock.<slot>
Linux fallbackUnix Socket/tmp/<target>.sock.<slot>
WindowsTCP127.0.0.1:<slot>

Supported Operations:

OpcodeOperationDescription
0x01VersionGet emulator PINE version
0x02TitleGet loaded game title
0x03IDGet game serial ID
0x04UUIDGet disc CRC
0x05GameVersionGet game version string
0x10StatusGet emulator status
0x20Read8Read 8-bit value
0x21Read16Read 16-bit value
0x22Read32Read 32-bit value
0x23Read64Read 64-bit value
0x30Write8Write 8-bit value
0x31Write16Write 16-bit value
0x32Write32Write 32-bit value
0x33Write64Write 64-bit value
0x40SaveStateSave to slot
0x41LoadStateLoad 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:

CategoryTools
Connectivitypine_ping, pine_get_info
Readpine_read8, pine_read16, pine_read32, pine_read64, pine_read_range
Writepine_write8, pine_write16, pine_write32, pine_write64
Statepine_save_state, pine_load_state

Configuration

Environment Variables

VariableDefaultDescription
PINE_TARGETpcsx2Emulator name prefix for Unix socket path
PINE_SLOT28011PINE slot number (also TCP port on Windows)
PINE_PIPELINE_BATCH1Number 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 WidthAlignment 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.

SettingSafetyLatency
PINE_PIPELINE_BATCH=1Safe (default)~52ms for 4KB read
PINE_PIPELINE_BATCH=2+RiskyLower latency

Sources: src/pine.ts:150-160

Error Modes

SymptomCauseFix
Cannot reach PINE serverEmulator not running, PINE disabled, wrong slotCheck PINE_SLOT
PINE FAIL responseNo game loaded, unmapped addressLoad game, check address
Reads return zerosUnallocated memory regionTry 0x00100000 (EE RAM)
Tool calls work but values corruptedEndianness mismatchPINE returns little-endian
PINE call timed out (10s) after heavy usePCSX2 PINE queue desyncFull emulator restart

Emulator Support

PCSX2 (Primary)

  1. Launch PCSX2 1.7.x Qt or newer
  2. Settings → Advanced → Enable PINE Server
  3. Default slot is 28011
  4. 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

  1. Tool descriptions: Follow TDQS rubric in src/tools.ts
  2. Error handling: All async operations must handle rejection
  3. Alignment: Document alignment requirements in parameter descriptions
  4. Testing: Verify changes against live PCSX2 before submitting
  5. Documentation: Update CHANGELOG.md with changes

Version History

VersionDateKey Changes
0.2.12026-05-15Tool description quality pass
0.2.02026-05-10Bulk read, 10s timeout, robustness
0.1.0EarlierInitial release

Sources: CHANGELOG.md:1-30

Sources: package.json:8-10

Troubleshooting Guide

Related topics: Emulator Setup, Configuration Reference

Section Related Pages

Continue reading this section for the full explanation and source context.

Section Cannot Reach PINE Server

Continue reading this section for the full explanation and source context.

Section Transport Layer Differences

Continue reading this section for the full explanation and source context.

Section PINE FAIL Response (0xFF)

Continue reading this section for the full explanation and source context.

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:

CauseDescriptionFix
Emulator not runningPCSX2/RPCS3/Duckstation is not launchedStart the emulator with a game loaded
PINE not enabledThe PINE server toggle is off in emulator settingsEnable it in emulator settings
Slot/Port mismatchPINE_SLOT doesn't match the emulator's configured portVerify PINE_SLOT matches emulator configuration
Game not loadedSome PINE operations require an active game sessionLoad 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

PlatformTransportPath/Address
WindowsTCP127.0.0.1:<PINE_SLOT>
Linux/macOSUnix 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:

  1. No game loaded — Most PINE operations require an active game session
  2. Unmapped address — The memory address doesn't map to any allocated region
  3. 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:

  1. Fully quit PCSX2 (not just close the game)
  2. Restart PCSX2
  3. Load the game
  4. 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).

OperationLatencyNotes
Full 4096-byte read~52 msMeasured on PCSX2 v2.6.3 over loopback
Frame budget at 60fps~16.6 msBulk 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.

OperationAlignmentUnaligned Behavior
pine_read8 / pine_write8NoneWorks correctly
pine_read16 / pine_write162-byteSilently returns corrupted data
pine_read32 / pine_write324-byteSilently returns corrupted data
pine_read64 / pine_write648-byteSilently 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:

  1. Check if PCSX2's PINE server has wedged
  2. Verify no other tool is aggressive pipelining
  3. Consider restarting the emulator

Sources: CHANGELOG.md:26-29

Emulator-Specific Setup

  1. Launch PCSX2 1.7.x Qt or newer
  2. Navigate to Settings → Advanced → Enable PINE Server
  3. Default slot is 28011 — only change PINE_SLOT if you configure a different port
  4. 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 ``

  1. Enable IPC server in Configuration → Advanced
  2. Note the configured port
  3. Run with:

Sources: README.md:58-65

Duckstation

Duckstation includes a PINE server only in specific builds. Check whether your build includes it:

  1. Set PINE_TARGET=duckstation
  2. Configure PINE_SLOT to match the port shown in Duckstation's settings
  3. If connection fails, your build likely doesn't include PINE support

Sources: README.md:5-9

Environment Variables Reference

VariableDefaultPurpose
PINE_TARGETpcsx2Emulator name — used as prefix in Unix socket path on Linux/macOS
PINE_SLOT28011PINE slot / port number
PINE_HOST127.0.0.1TCP host override (Windows always uses this)
PINE_SOCKET_PATHautoFull Unix socket path override
PINE_PIPELINE_BATCH1Number 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_TARGET and PINE_SLOT values
  • [ ] 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 ``

  1. Search existing GitHub Issues
  2. File a new issue with diagnostic information
  3. 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.

medium Submit listing PR to punkpeye/awesome-mcp-servers

First-time setup may fail or require extra isolation and rollback planning.

medium Configuration risk needs validation

Users may get misleading failures or incomplete behavior unless configuration is checked carefully.

medium README/documentation is current enough for a first validation pass.

The project should not be treated as fully validated until this signal is reviewed.

medium Maintainer activity is unknown

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.

Sources 3

Count of project-level external discussion links exposed on this manual page.

Use Review before install

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.

Source: Project Pack community evidence and pitfall evidence