🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

mcp-pine

Package Overview
Dependencies
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

mcp-pine - npm Package Compare versions

Comparing version
0.2.1
to
0.3.0
+31
dist/targets.d.ts
export interface TargetInfo {
/** Lowercase short name used in PINE socket file naming and PINE_TARGET env. */
name: string;
/** Human-readable emulator name for help text. */
displayName: string;
/** Console/system the emulator emulates. */
system: string;
/** Default PINE slot (also the TCP port on Windows). */
defaultSlot: number;
/** Short address-space name used in tool PURPOSE lines, e.g. "EE address space" (PCSX2), "main RAM" (DuckStation). */
addressSpaceName: string;
/** Useful-range hint for memory operations (single hex range string). */
usefulRangeHint: string;
/** Full multi-line memory map shown in memory-tool descriptions. */
memoryMap: string;
/** Startup help shown when PINE isn't reachable — exact menu path + port. */
setupHelp: string;
/** Where savestate files live + filename convention. Used by save/load slot tool descriptions. */
savestateInfo: string;
/** Per-tool alignment-failure note describing what THIS emulator does on unaligned access. */
alignmentNote: string;
}
export declare const TARGETS: Record<string, TargetInfo>;
/**
* Look up a target by name. Unknown targets get a synthetic "generic" record
* so the server still starts — useful for forward compatibility with PINE
* emulators we haven't catalogued yet. The synthetic record uses neutral
* help text and defers memory-map specifics to the user.
*/
export declare function lookupTarget(name: string): TargetInfo;
//# sourceMappingURL=targets.d.ts.map
{"version":3,"file":"targets.d.ts","sourceRoot":"","sources":["../src/targets.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,UAAU;IACzB,gFAAgF;IAChF,IAAI,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,WAAW,EAAE,MAAM,CAAC;IACpB,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,WAAW,EAAE,MAAM,CAAC;IACpB,sHAAsH;IACtH,gBAAgB,EAAE,MAAM,CAAC;IACzB,yEAAyE;IACzE,eAAe,EAAE,MAAM,CAAC;IACxB,oEAAoE;IACpE,SAAS,EAAE,MAAM,CAAC;IAClB,6EAA6E;IAC7E,SAAS,EAAE,MAAM,CAAC;IAClB,kGAAkG;IAClG,aAAa,EAAE,MAAM,CAAC;IACtB,8FAA8F;IAC9F,aAAa,EAAE,MAAM,CAAC;CACvB;AAmED,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAG9C,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAqBrD"}
// PINE target configuration.
// ───────────────────────────
// mcp-pine speaks the generic PINE protocol (same opcodes, same wire format
// for all targets). What differs across targets is *context*: the memory
// map an agent should target, the address-space name, the savestate file
// layout, and the startup steps the user has to follow in each emulator.
//
// This file collects that per-target context so tools.ts and index.ts can
// render help text that's accurate for the actual emulator the user is
// pointing at, rather than blanket-PCSX2 text that's wrong on PS1.
//
// PINE_TARGET env var selects the active target at startup. Unknown values
// pass through (warn + treat as generic) so a niche/future PINE emulator
// still works on the protocol level.
const PCSX2 = {
name: "pcsx2",
displayName: "PCSX2",
system: "PlayStation 2",
defaultSlot: 28011,
addressSpaceName: "EE main address space",
usefulRangeHint: "0x00100000-0x01FFFFFF for EE main RAM (where 99% of game state lives)",
memoryMap: `
PlayStation 2 main address space landmarks (PCSX2):
0x00100000-0x01FFFFFF EE main RAM (32 MiB) — game code & data; the most common target
0x10000000 Hardware registers (DMA, GIF, VIF, etc.)
0x11000000 VU0 / VU1 memory
0x12000000 GS privileged registers
0x1C000000-0x1C1FFFFF IOP RAM (2 MiB)
0x1F800000 IOP scratchpad
0x70000000 EE scratchpad (16 KiB)
PINE memory operations target the EE address space.`.trim(),
setupHelp: "For PCSX2: Settings > Advanced > Enable PINE Server (default port 28011).",
savestateInfo: "PCSX2 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'.",
alignmentNote: "PINE on PCSX2 does NOT enforce alignment — unaligned 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.",
};
// NOTE on DuckStation:
// stenzek implemented PINE in DuckStation in May 2024 (commit 4311e087)
// then dropped it in September 2024 (commit 19698559, "System: Drop IPC
// server"). DuckStation builds from 2024-09-21 onward have no PINE server.
// We don't ship a DuckStation TargetInfo because there's no PINE to talk
// to. If upstream brings it back, this is the place to add it.
const RPCS3 = {
name: "rpcs3",
displayName: "RPCS3",
system: "PlayStation 3",
defaultSlot: 28012,
addressSpaceName: "PPU effective address space",
usefulRangeHint: "0x00010000-0x0FFFFFFF for PPU main memory (game code & data are loaded dynamically)",
memoryMap: `
PlayStation 3 PPU effective address space landmarks (RPCS3):
0x00010000-0x0FFFFFFF Main memory (256 MiB) — game code, data, heap; layout is DYNAMIC
The PS3 dynamically loads code segments; addresses vary per game and per run.
Use the disassembler / RAM watcher in RPCS3 to locate specific values
before scripting against them.
0xC0000000+ RSX (GPU) IO-mapped memory — varies; not typically what you want for game state
0xD0000000+ Stack regions (per-thread)
PS3 is segmented and very dynamic compared to PS1/PS2. Plan on a discovery
phase (RAM watcher in RPCS3 UI) before pine_read* targets stabilize.`.trim(),
setupHelp: "For RPCS3: enable PINE via the IPC menu (see RPCS3 wiki Help:IPC_Protocol). " +
"Default slot 28012. PINE on RPCS3 is less mature than on PCSX2/DuckStation; some opcodes may not work.",
savestateInfo: "RPCS3 does not use the same savestate-slot model as PCSX2/DuckStation. " +
"PINE SaveState/LoadState opcodes may be no-ops or return FAIL — use RPCS3's UI for save management.",
alignmentNote: "RPCS3 emulates PowerPC 64-bit; PPU memory is naturally big-endian on hardware but PINE returns " +
"little-endian-decoded values (matching the protocol). Verify endianness when comparing against " +
"RPCS3-side dumps.",
};
export const TARGETS = {
pcsx2: PCSX2,
rpcs3: RPCS3,
};
/**
* Look up a target by name. Unknown targets get a synthetic "generic" record
* so the server still starts — useful for forward compatibility with PINE
* emulators we haven't catalogued yet. The synthetic record uses neutral
* help text and defers memory-map specifics to the user.
*/
export function lookupTarget(name) {
const known = TARGETS[name.toLowerCase()];
if (known)
return known;
return {
name: name,
displayName: name,
system: "(unknown emulator)",
defaultSlot: 28011,
addressSpaceName: "main address space",
usefulRangeHint: "(consult the emulator's documentation for its memory map)",
memoryMap: `Unknown PINE target "${name}".\n` +
`The PINE protocol layer works for any emulator that implements it, but mcp-pine has no\n` +
`built-in memory map for "${name}". Consult the emulator's documentation for valid addresses.`,
setupHelp: `Make sure the emulator at PINE_TARGET=${name} is running with its PINE/IPC server enabled.`,
savestateInfo: "Savestate behavior depends on the emulator. Consult its documentation.",
alignmentNote: "Alignment behavior depends on the emulator. Prefer aligned addresses to avoid silent corruption.",
};
}
//# sourceMappingURL=targets.js.map
{"version":3,"file":"targets.js","sourceRoot":"","sources":["../src/targets.ts"],"names":[],"mappings":"AAAA,6BAA6B;AAC7B,8BAA8B;AAC9B,4EAA4E;AAC5E,yEAAyE;AACzE,yEAAyE;AACzE,yEAAyE;AACzE,EAAE;AACF,0EAA0E;AAC1E,uEAAuE;AACvE,mEAAmE;AACnE,EAAE;AACF,2EAA2E;AAC3E,yEAAyE;AACzE,qCAAqC;AAyBrC,MAAM,KAAK,GAAe;IACxB,IAAI,EAAE,OAAO;IACb,WAAW,EAAE,OAAO;IACpB,MAAM,EAAE,eAAe;IACvB,WAAW,EAAE,KAAK;IAClB,gBAAgB,EAAE,uBAAuB;IACzC,eAAe,EAAE,uEAAuE;IACxF,SAAS,EAAE;;;;;;;;;oDASuC,CAAC,IAAI,EAAE;IACzD,SAAS,EACP,2EAA2E;IAC7E,aAAa,EACX,wEAAwE;QACxE,sFAAsF;QACtF,oDAAoD;IACtD,aAAa,EACX,+FAA+F;QAC/F,4FAA4F;QAC5F,uEAAuE;CAC1E,CAAC;AAEF,uBAAuB;AACvB,wEAAwE;AACxE,wEAAwE;AACxE,2EAA2E;AAC3E,yEAAyE;AACzE,+DAA+D;AAE/D,MAAM,KAAK,GAAe;IACxB,IAAI,EAAE,OAAO;IACb,WAAW,EAAE,OAAO;IACpB,MAAM,EAAE,eAAe;IACvB,WAAW,EAAE,KAAK;IAClB,gBAAgB,EAAE,6BAA6B;IAC/C,eAAe,EAAE,qFAAqF;IACtG,SAAS,EAAE;;;;;;;;;qEASwD,CAAC,IAAI,EAAE;IAC1E,SAAS,EACP,8EAA8E;QAC9E,wGAAwG;IAC1G,aAAa,EACX,yEAAyE;QACzE,qGAAqG;IACvG,aAAa,EACX,iGAAiG;QACjG,iGAAiG;QACjG,mBAAmB;CACtB,CAAC;AAEF,MAAM,CAAC,MAAM,OAAO,GAA+B;IACjD,KAAK,EAAE,KAAK;IACZ,KAAK,EAAE,KAAK;CACb,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;IAC1C,IAAI,KAAK;QAAE,OAAO,KAAK,CAAC;IACxB,OAAO;QACL,IAAI,EAAE,IAAI;QACV,WAAW,EAAE,IAAI;QACjB,MAAM,EAAE,oBAAoB;QAC5B,WAAW,EAAE,KAAK;QAClB,gBAAgB,EAAE,oBAAoB;QACtC,eAAe,EAAE,2DAA2D;QAC5E,SAAS,EACP,wBAAwB,IAAI,MAAM;YAClC,0FAA0F;YAC1F,4BAA4B,IAAI,8DAA8D;QAChG,SAAS,EACP,yCAAyC,IAAI,+CAA+C;QAC9F,aAAa,EACX,wEAAwE;QAC1E,aAAa,EACX,kGAAkG;KACrG,CAAC;AACJ,CAAC"}
+19
-7

@@ -6,4 +6,8 @@ #!/usr/bin/env node

import { registerTools } from "./tools.js";
const TARGET = process.env.PINE_TARGET ?? "pcsx2";
const SLOT = parseInt(process.env.PINE_SLOT ?? "28011", 10);
import { lookupTarget, TARGETS } from "./targets.js";
const TARGET_NAME = (process.env.PINE_TARGET ?? "pcsx2").toLowerCase();
const target = lookupTarget(TARGET_NAME);
// If PINE_SLOT is unset, fall back to the target's default slot rather than
// always defaulting to 28011 — DuckStation and RPCS3 may use different slots.
const SLOT = parseInt(process.env.PINE_SLOT ?? String(target.defaultSlot), 10);
const HOST = process.env.PINE_HOST; // optional TCP override

@@ -13,3 +17,3 @@ const SOCK = process.env.PINE_SOCKET_PATH; // optional Unix socket path override

const pine = new PineClient({
target: TARGET,
target: target.name,
slot: SLOT,

@@ -19,6 +23,14 @@ host: HOST,

});
// Surface the resolved target so the user can confirm we're pointing at
// the right emulator. If PINE_TARGET was unknown, lookupTarget returned
// a synthetic record — flag that loudly so the user knows tool help text
// will be generic.
const known = !!TARGETS[TARGET_NAME];
process.stderr.write(`[mcp-pine] target=${target.name} (${target.displayName} — ${target.system})` +
(known ? "" : " [WARNING: unknown target — using generic memory map context]") +
"\n");
// Try to connect eagerly so we can give a clear startup message either way.
try {
await pine.connect();
process.stderr.write(`[mcp-pine] connected to ${pine.describeTarget()} (target=${TARGET})\n`);
process.stderr.write(`[mcp-pine] connected to ${pine.describeTarget()}\n`);
}

@@ -28,6 +40,6 @@ catch (err) {

` Make sure the emulator is running with PINE enabled.\n` +
` For PCSX2: Settings > Advanced > Enable PINE Server (default port 28011).\n`);
` ${target.setupHelp}\n`);
}
const server = new Server({ name: "mcp-pine", version: "0.2.0" }, { capabilities: { tools: {} } });
registerTools(server, pine);
const server = new Server({ name: "mcp-pine", version: "0.3.0" }, { capabilities: { tools: {} } });
registerTools(server, pine, target);
const transport = new StdioServerTransport();

@@ -34,0 +46,0 @@ await server.connect(transport);

@@ -1,1 +0,1 @@

{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,OAAO,CAAC;AAClD,MAAM,IAAI,GAAK,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,OAAO,EAAE,EAAE,CAAC,CAAC;AAC9D,MAAM,IAAI,GAAK,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAU,wBAAwB;AACvE,MAAM,IAAI,GAAK,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAG,qCAAqC;AAEpF,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC;QAC1B,MAAM,EAAM,MAAM;QAClB,IAAI,EAAQ,IAAI;QAChB,IAAI,EAAQ,IAAI;QAChB,UAAU,EAAE,IAAI;KACjB,CAAC,CAAC;IAEH,4EAA4E;IAC5E,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACrB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,2BAA2B,IAAI,CAAC,cAAc,EAAE,YAAY,MAAM,KAAK,CAAC,CAAC;IAChG,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,yDAAyD,IAAI,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI;YAC3F,mEAAmE;YACnE,wFAAwF,CACzF,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,EACtC,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,CAChC,CAAC;IAEF,aAAa,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAE5B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,uCAAuC,CAAC,CAAC;AAChE,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAAC;IACnD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAErD,MAAM,WAAW,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;AACvE,MAAM,MAAM,GAAG,YAAY,CAAC,WAAW,CAAC,CAAC;AACzC,4EAA4E;AAC5E,8EAA8E;AAC9E,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,EAAE,CAAC,CAAC;AAC/E,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAU,wBAAwB;AACrE,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAG,qCAAqC;AAElF,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC;QAC1B,MAAM,EAAM,MAAM,CAAC,IAAI;QACvB,IAAI,EAAQ,IAAI;QAChB,IAAI,EAAQ,IAAI;QAChB,UAAU,EAAE,IAAI;KACjB,CAAC,CAAC;IAEH,wEAAwE;IACxE,wEAAwE;IACxE,yEAAyE;IACzE,mBAAmB;IACnB,MAAM,KAAK,GAAG,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IACrC,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,qBAAqB,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,WAAW,MAAM,MAAM,CAAC,MAAM,GAAG;QAC7E,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,gEAAgE,CAAC;QAC/E,IAAI,CACL,CAAC;IAEF,4EAA4E;IAC5E,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACrB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,2BAA2B,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC;IAC7E,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,yDAAyD,IAAI,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI;YAC3F,mEAAmE;YACnE,cAAc,MAAM,CAAC,SAAS,IAAI,CACnC,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,EACtC,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,CAChC,CAAC;IAEF,aAAa,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IAEpC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,uCAAuC,CAAC,CAAC;AAChE,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAAC;IACnD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { PineClient } from "./pine.js";
export declare function registerTools(server: Server, pine: PineClient): void;
import type { TargetInfo } from "./targets.js";
export declare function registerTools(server: Server, pine: PineClient, target: TargetInfo): void;
//# sourceMappingURL=tools.d.ts.map

@@ -1,1 +0,1 @@

{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACxE,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAkVvC,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,IAAI,CA+EpE"}
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACxE,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAiV/C,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI,CAgFxF"}
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
const PS2_REGIONS = `
PlayStation 2 main address space landmarks (PCSX2):
0x00100000-0x01FFFFFF EE main RAM (32 MiB) — game code & data; the most common target
0x10000000 Hardware registers (DMA, GIF, VIF, etc.)
0x11000000 VU0 / VU1 memory
0x12000000 GS privileged registers
0x1C000000-0x1C1FFFFF IOP RAM (2 MiB)
0x1F800000 IOP scratchpad
0x70000000 EE scratchpad (16 KiB)
PINE memory operations target the EE address space. Other PINE-protocol
emulators (RPCS3, Duckstation) use entirely different maps — consult their docs.`.trim();
// ──────────────────────────────────────────────────────────────────────────────

@@ -28,266 +17,275 @@ // Tool descriptions are written to the TDQS rubric (Glama's Tool Definition

// (units, alignment requirements, value-encoding rules, examples).
//
// As of v0.3.0 the tool list is built dynamically from the active PINE target
// (PCSX2 / DuckStation / RPCS3 / unknown), so memory maps, address-space names,
// alignment notes, and savestate help match the emulator the user actually
// pointed mcp-pine at.
// ──────────────────────────────────────────────────────────────────────────────
const ADDRESS_PARAM_DESC = (widthBytes) => {
function addressParamDesc(target, widthBytes) {
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). Pass as a number; ` +
: `MUST be ${widthBytes}-byte aligned (address % ${widthBytes} === 0). ` + target.alignmentNote;
return (`Absolute byte address in the ${target.addressSpaceName} (NOT a per-domain offset). Pass as a number; ` +
`hex literals like 0x00200000 are fine. Reads ${widthBytes} consecutive byte` +
`${widthBytes === 1 ? "" : "s"} starting here. ${alignNote} ` +
`Useful range: 0x00100000-0x01FFFFFF for EE main RAM (where 99% of game state lives). ` +
`Useful range: ${target.usefulRangeHint}. ` +
`An unmapped or invalid address returns a PINE FAIL response.`);
};
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.";
const TOOLS = [
// ── Connectivity & introspection ────────────────────────────────────────
{
name: "pine_ping",
description: "PURPOSE: Verify that the PINE server (PCSX2 or another PINE-compatible emulator) is reachable and responding to RPC over its socket. " +
"USAGE: Call this once at start-of-session before issuing other tool calls; if it succeeds, every other tool will work for the live emulator instance. Internally issues the PINE Version opcode (0x08) — a cheap round-trip that doubles as a liveness probe and an emulator-version sniff. " +
"BEHAVIOR: No side effects — pure liveness probe. The bridge connects on demand: on Linux/macOS it opens a Unix domain socket at $XDG_RUNTIME_DIR/<target>.sock.<slot> (or $TMPDIR / /tmp fallback), on Windows it opens TCP to 127.0.0.1:<slot> (default port 28011 for PCSX2). Times out after ~10 seconds with a clear error if the emulator isn't running, PINE isn't enabled (PCSX2: Settings > Advanced > Enable PINE Server), or the slot/port doesn't match. " +
"RETURNS: Single line 'OK — emulator: VERSION_STRING' (e.g. 'OK — emulator: PCSX2 2.6.3').",
inputSchema: { type: "object", properties: {} },
},
{
name: "pine_get_info",
description: "PURPOSE: Get the loaded game's metadata — title, serial code, disc CRC, in-game version string — plus the current emulator run state in one call. " +
"USAGE: Call after pine_ping to confirm what game is loaded (don't poke memory blindly — the same address means different things across games). For just the run state without the metadata round-trips use pine_get_status (1 PINE call vs 5 here). The serial (e.g. 'SLUS-21274') uniquely identifies the disc release region; combine with disc CRC to identify a specific revision. " +
"BEHAVIOR: No side effects — pure read of emulator metadata. Issues five PINE opcodes in parallel (Title, ID, UUID, GameVersion, Status). Any individual field that the emulator doesn't expose or that fails is replaced with the literal string '(unavailable)' and the rest still come back. If the entire connection fails the call propagates an error. " +
"RETURNS: Multi-line text with Title, Serial, Disc CRC, Game version, and Status — one field per line.",
inputSchema: { type: "object", properties: {} },
},
{
name: "pine_get_status",
description: "PURPOSE: Get just the emulator run state — 'running', 'paused', 'shutdown', or 'unknown'. " +
"USAGE: Use as a cheap (single PINE round-trip) check before timing-sensitive sequences — e.g. before a sequence of memory writes you may want to confirm the emulator isn't paused (writes still work but won't take visible effect until unpaused). For game metadata (title, serial, CRC) use pine_get_info instead — that batches Status with Title/ID/UUID/GameVersion. PINE itself has no pause/resume opcode, so this tool only reports state; pause from the emulator UI or via a separate hotkey. " +
"BEHAVIOR: No side effects — pure read. Issues PINE Status opcode (0x0F) and decodes the 32-bit response (0=running, 1=paused, 2=shutdown, anything else='unknown'). Returns an error if the connection fails or PINE returns FAIL. " +
"RETURNS: Single line 'Status: STATE' where STATE is one of 'running', 'paused', 'shutdown', 'unknown'.",
inputSchema: { type: "object", properties: {} },
},
// ── Memory reads ────────────────────────────────────────────────────────
{
name: "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. For 16/32/64-bit values use pine_read16/read32/read64 (one call instead of multi-byte assembly); for spans of more than ~4 bytes use pine_read_range (one batched call instead of N round-trips). For PS2 game-state pointers and 64-bit IDs prefer pine_read64 (PS2's EE is a 128-bit MIPS — 64-bit fields are common). " +
"BEHAVIOR: No side effects — pure read. Reads work whether the emulator is running or paused. No alignment requirement (byte access is naturally aligned). Returns an error if the address is unmapped, the connection drops, or PINE returns its FAIL response (0xFF). The 10-second per-call timeout fires if the emulator drops the reply (PCSX2 has been observed to do this under heavy pipeline load — see pine_read_range for the wider context).\n\n" +
PS2_REGIONS + "\n\n" +
"RETURNS: Single line 'ADDR_HEX: VAL_DEC (0xVAL_HEX)', e.g. '0x00200000: 99 (0x63)'.",
inputSchema: {
type: "object",
required: ["address"],
properties: {
address: { type: "integer", minimum: 0, description: ADDRESS_PARAM_DESC(1) },
}
function slotParamDesc(target) {
return (`Save state slot number (0-255). The PINE protocol accepts the full 0-255 range. ` +
target.savestateInfo +
` Slot numbers are independent of any path.`);
}
function buildTools(target) {
const MEM = target.memoryMap;
const ADDR_SPACE = target.addressSpaceName;
const EMU_NAME = target.displayName;
const SYSTEM = target.system;
return [
// ── Connectivity & introspection ────────────────────────────────────────
{
name: "pine_ping",
description: `PURPOSE: Verify the PINE server (${EMU_NAME}) is reachable and responding. ` +
"USAGE: Call once at session start before other tool calls. Issues PINE Version opcode (0x08) — doubles as liveness probe and emulator-version sniff. " +
`BEHAVIOR: No side effects. Bridge connects on demand — Unix socket at $XDG_RUNTIME_DIR/${target.name}.sock.<slot> (Linux/macOS, with $TMPDIR/$/tmp fallback) or TCP to 127.0.0.1:<slot> (Windows, default slot ${target.defaultSlot}). 10-second timeout if the emulator isn't running, PINE isn't enabled (${target.setupHelp}), or slot/port mismatches. ` +
`RETURNS: 'OK — emulator: VERSION_STRING', e.g. 'OK — emulator: ${EMU_NAME} <version>'.`,
inputSchema: { type: "object", properties: {} },
},
{
name: "pine_get_info",
description: "PURPOSE: Get the loaded game's metadata — title, serial code, disc CRC, in-game version string — plus the current emulator run state in one call. " +
"USAGE: Call after pine_ping to confirm what game is loaded (don't poke memory blindly — the same address means different things across games). For just the run state without the metadata round-trips use pine_get_status (1 PINE call vs 5 here). The serial (e.g. 'SLUS-21274' for PS2, 'SLUS-00067' for PS1) uniquely identifies the disc release region; combine with disc CRC to identify a specific revision. " +
"BEHAVIOR: No side effects — pure read of emulator metadata. Issues five PINE opcodes in parallel (Title, ID, UUID, GameVersion, Status). Any individual field that the emulator doesn't expose or that fails is replaced with the literal string '(unavailable)' and the rest still come back. If the entire connection fails the call propagates an error. " +
"RETURNS: Multi-line text with Title, Serial, Disc CRC, Game version, and Status — one field per line.",
inputSchema: { type: "object", properties: {} },
},
{
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 (batches Status with Title/ID/UUID/GameVersion). 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). Errors on connection failure or PINE FAIL. " +
"RETURNS: 'Status: STATE' where STATE ∈ {running, paused, shutdown, unknown}.",
inputSchema: { type: "object", properties: {} },
},
// ── Memory reads ────────────────────────────────────────────────────────
{
name: "pine_read8",
description: `PURPOSE: Read an unsigned 8-bit byte from the emulator's ${ADDR_SPACE} at the given absolute address. ` +
"USAGE: Use for single-byte fields — status flags, counters, 8-bit enums, character bytes. For 16/32/64-bit values use pine_read16/read32/read64 (one call instead of multi-byte assembly); for spans of more than ~4 bytes use pine_read_range (one batched call instead of N round-trips). " +
"BEHAVIOR: No side effects — pure read. Reads work whether the emulator is running or paused. No alignment requirement (byte access is naturally aligned). Returns an error if the address is unmapped, the connection drops, or PINE returns its FAIL response (0xFF). The 10-second per-call timeout fires if the emulator drops the reply (PCSX2 has been observed to do this under heavy pipeline load — see pine_read_range for the wider context).\n\n" +
MEM + "\n\n" +
"RETURNS: Single line 'ADDR_HEX: VAL_DEC (0xVAL_HEX)', e.g. '0x00200000: 99 (0x63)'.",
inputSchema: {
type: "object",
required: ["address"],
properties: {
address: { type: "integer", minimum: 0, description: addressParamDesc(target, 1) },
},
additionalProperties: false,
},
additionalProperties: false,
},
},
{
name: "pine_read16",
description: "PURPOSE: Read an unsigned 16-bit little-endian value from the emulator's EE address space at the given absolute address. " +
"USAGE: Use for 16-bit fields (HP, score, coordinates on many PS2 titles). For single bytes use pine_read8; for 32/64-bit use pine_read32/read64; for unaligned reads or big-endian fields, use pine_read_range and decode the bytes yourself (this tool always interprets bytes as little-endian, which matches PS2's native EE byte order). " +
"BEHAVIOR: No side effects — pure read. Reads two consecutive bytes (low byte at `address`, high byte at `address+1`) and combines them as little-endian. Address MUST be 2-byte aligned — PINE silently aligns down on PCSX2, returning corrupted data instead of an error if you pass an odd address. Returns a PINE FAIL response on unmapped addresses; times out after ~10s if the reply is dropped.\n\n" +
PS2_REGIONS + "\n\n" +
"RETURNS: Single line 'ADDR_HEX: VAL_DEC (0xVAL_HEX)'.",
inputSchema: {
type: "object",
required: ["address"],
properties: {
address: { type: "integer", minimum: 0, description: ADDRESS_PARAM_DESC(2) },
{
name: "pine_read16",
description: `PURPOSE: Read an unsigned 16-bit little-endian value from the emulator's ${ADDR_SPACE} at the given absolute address. ` +
`USAGE: Use for 16-bit fields (HP, score, coordinates on many ${SYSTEM} titles). For single bytes use pine_read8; for 32/64-bit use pine_read32/read64; for unaligned reads or big-endian fields, use pine_read_range and decode the bytes yourself (this tool always interprets bytes as little-endian, which matches MIPS byte order on PS1/PS2). ` +
"BEHAVIOR: No side effects — pure read. Reads two consecutive bytes (low byte at `address`, high byte at `address+1`) and combines them as little-endian. Address MUST be 2-byte aligned. " + target.alignmentNote + " Returns a PINE FAIL response on unmapped addresses; times out after ~10s if the reply is dropped.\n\n" +
MEM + "\n\n" +
"RETURNS: Single line 'ADDR_HEX: VAL_DEC (0xVAL_HEX)'.",
inputSchema: {
type: "object",
required: ["address"],
properties: {
address: { type: "integer", minimum: 0, description: addressParamDesc(target, 2) },
},
additionalProperties: false,
},
additionalProperties: false,
},
},
{
name: "pine_read32",
description: "PURPOSE: Read an unsigned 32-bit little-endian value from the emulator's EE address space at the given absolute address. " +
"USAGE: Use for 32-bit fields — timestamps, large counters, RGBA colors, and the lower half of 64-bit pointers. For single byte / 16-bit / 64-bit values use pine_read8/read16/read64; for big-endian or unaligned multi-word reads use pine_read_range and decode yourself. PS2 EE pointers are technically 64-bit but the upper bits are usually zero — many tools treat them as 32-bit, in which case this is the right call. " +
"BEHAVIOR: No side effects — pure read. Reads four consecutive bytes starting at `address` and combines them as little-endian (LSB at `address`, MSB at `address+3`). Address MUST be 4-byte aligned — PINE on PCSX2 does not enforce this; an unaligned address silently returns corrupted data. Returns a PINE FAIL response on unmapped addresses; times out after ~10s if the reply is dropped.\n\n" +
PS2_REGIONS + "\n\n" +
"RETURNS: Single line 'ADDR_HEX: VAL_DEC (0xVAL_HEX)'.",
inputSchema: {
type: "object",
required: ["address"],
properties: {
address: { type: "integer", minimum: 0, description: ADDRESS_PARAM_DESC(4) },
{
name: "pine_read32",
description: `PURPOSE: Read an unsigned 32-bit little-endian value from the emulator's ${ADDR_SPACE} at the given absolute address. ` +
"USAGE: Use for 32-bit fields — timestamps, large counters, RGBA colors, and the lower half of 64-bit pointers. For single byte / 16-bit / 64-bit values use pine_read8/read16/read64; for big-endian or unaligned multi-word reads use pine_read_range and decode yourself. " +
"BEHAVIOR: No side effects — pure read. Reads four consecutive bytes starting at `address` and combines them as little-endian (LSB at `address`, MSB at `address+3`). Address MUST be 4-byte aligned. " + target.alignmentNote + " Returns a PINE FAIL response on unmapped addresses; times out after ~10s if the reply is dropped.\n\n" +
MEM + "\n\n" +
"RETURNS: Single line 'ADDR_HEX: VAL_DEC (0xVAL_HEX)'.",
inputSchema: {
type: "object",
required: ["address"],
properties: {
address: { type: "integer", minimum: 0, description: addressParamDesc(target, 4) },
},
additionalProperties: false,
},
additionalProperties: false,
},
},
{
name: "pine_read64",
description: "PURPOSE: Read an unsigned 64-bit little-endian value from the emulator's EE address space at the given absolute address. " +
"USAGE: Use for true 64-bit fields — full PS2 EE pointers, large IDs, packed double-word state. The PS2 EE is a 128-bit MIPS (Emotion Engine), and a lot of game state genuinely lives in 64-bit slots; reach for this rather than chaining two pine_read32 calls. For 8/16/32-bit values use the corresponding sibling; for byte spans use pine_read_range. " +
"BEHAVIOR: No side effects — pure read. Reads eight consecutive bytes starting at `address` and combines them as little-endian. Address MUST be 8-byte aligned — PINE on PCSX2 does not enforce this; unaligned addresses silently return corrupted data. The result is returned as a decimal STRING (not a JSON number) to preserve precision past 2^53 (JavaScript number limit) — parse with BigInt if you need to do arithmetic. Returns a PINE FAIL response on unmapped addresses; times out after ~10s if the reply is dropped.\n\n" +
PS2_REGIONS + "\n\n" +
"RETURNS: Single line 'ADDR_HEX: VAL_DEC (0xVAL_HEX)' — VAL_DEC is a decimal string that may exceed 2^53.",
inputSchema: {
type: "object",
required: ["address"],
properties: {
address: { type: "integer", minimum: 0, description: ADDRESS_PARAM_DESC(8) },
{
name: "pine_read64",
description: `PURPOSE: Read an unsigned 64-bit little-endian value from the emulator's ${ADDR_SPACE} at the given absolute address. ` +
"USAGE: Use for true 64-bit fields — full pointers, large IDs, packed double-word state. The PS2 EE is a 128-bit MIPS where 64-bit slots are common; PS1 and PS3 use 64-bit less heavily but the opcode still works. Reach for this rather than chaining two pine_read32 calls when you want atomicity. For 8/16/32-bit values use the corresponding sibling; for byte spans use pine_read_range. " +
"BEHAVIOR: No side effects — pure read. Reads eight consecutive bytes starting at `address` and combines them as little-endian. Address MUST be 8-byte aligned. " + target.alignmentNote + " The result is returned as a decimal STRING (not a JSON number) to preserve precision past 2^53 (JavaScript number limit) — parse with BigInt if you need to do arithmetic. Returns a PINE FAIL response on unmapped addresses; times out after ~10s if the reply is dropped.\n\n" +
MEM + "\n\n" +
"RETURNS: Single line 'ADDR_HEX: VAL_DEC (0xVAL_HEX)' — VAL_DEC is a decimal string that may exceed 2^53.",
inputSchema: {
type: "object",
required: ["address"],
properties: {
address: { type: "integer", minimum: 0, description: addressParamDesc(target, 8) },
},
additionalProperties: false,
},
additionalProperties: false,
},
},
{
name: "pine_read_range",
description: "PURPOSE: Read a contiguous range of bytes from the emulator's EE address space and return them as a hex-formatted dump. " +
"USAGE: Use whenever you need more than ~4 bytes — far cheaper than looping pine_read8 since this tool batches the work into the largest aligned loads available (read64 on 8-byte boundaries, falling back to 32/16/8 at the unaligned edges). Hard cap of 4096 bytes per call; for larger reads, batch in 4 KiB chunks yourself. Classic uses: snapshot-diff RAM hunts (snapshot before a known game change, snapshot after, diff for matching deltas), inspecting unknown structs, capturing a region for later restore via cheats. " +
"BEHAVIOR: No side effects — pure read. PINE has NO native bulk-read opcode; this tool synthesizes the range by issuing a sequence of read64/32/16/8 calls and assembling the bytes client-side. By default it issues these calls FULLY SERIALLY (one in flight at a time) because PCSX2's PINE server has a fragile request queue — at as few as ~7 mixed in-flight requests it silently drops replies, which leaves the bridge's reply pipeline desynced and times out ALL subsequent calls until the emulator is restarted. Loopback is fast enough that serial is OK (~52 ms for 4096 bytes against PCSX2 v2.6.3). The PINE_PIPELINE_BATCH env var can raise the in-flight count if you trust your specific emulator's PINE implementation. Returns an error if length < 1 or > 4096, if any underlying read FAILs, or if a reply times out.\n\n" +
PS2_REGIONS + "\n\n" +
"RETURNS: Header line 'ADDR_HEX [N bytes]:' followed by space-separated 2-digit uppercase hex bytes.",
inputSchema: {
type: "object",
required: ["address", "length"],
properties: {
address: {
type: "integer",
minimum: 0,
description: "Starting absolute byte address in the EE address space. Bytes [address, address+length) are read. " +
"No alignment requirement — the tool picks the largest aligned load it can at each step (e.g. an unaligned start, " +
"an aligned middle, and an unaligned tail are handled in three different load widths)."
{
name: "pine_read_range",
description: `PURPOSE: Read a contiguous range of bytes from ${ADDR_SPACE} memory as a hex dump. ` +
"USAGE: For >4 bytes — far cheaper than looping pine_read8. Max 4096 bytes/call; chunk larger reads in 4 KiB. Powers snapshot-diff RAM hunts (snapshot before/after a known change, diff for matching deltas), unknown-struct inspection, and region capture/restore. " +
"BEHAVIOR: No side effects. PINE has no native bulk-read opcode; the tool synthesizes the range from read64/32/16/8 calls (largest aligned load at each step) and assembles client-side. Issued FULLY SERIALLY by default because PCSX2's PINE queue silently drops replies past ~7 in-flight requests, desyncing the bridge until emulator restart. Loopback serial is fast enough (~52 ms for 4096 bytes on PCSX2 v2.6.3); other targets are typically similar or faster. Override via PINE_PIPELINE_BATCH env var at your own risk. Errors on length out of 1-4096, any underlying FAIL, or reply timeout.\n\n" +
MEM + "\n\n" +
"RETURNS: 'ADDR_HEX [N bytes]:' header + space-separated 2-digit uppercase hex bytes.",
inputSchema: {
type: "object",
required: ["address", "length"],
properties: {
address: {
type: "integer",
minimum: 0,
description: `Starting absolute byte address in the ${ADDR_SPACE}. Bytes [address, address+length) are read. ` +
"No alignment requirement — the tool picks the largest aligned load it can at each step (e.g. an unaligned start, " +
"an aligned middle, and an unaligned tail are handled in three different load widths)."
},
length: {
type: "integer",
minimum: 1,
maximum: 4096,
description: "Number of consecutive bytes to read (1-4096). Hard cap is the tool's max; chunk larger reads yourself. " +
"Latency is roughly proportional to length / 8 in serial mode (the default) — a 4096-byte read is ~512 PINE round-trips on a typical 8-byte-aligned region, around 50 ms over loopback."
},
},
length: {
type: "integer",
minimum: 1,
maximum: 4096,
description: "Number of consecutive bytes to read (1-4096). Hard cap is the tool's max; chunk larger reads yourself. " +
"Latency is roughly proportional to length / 8 in serial mode (the default) — a 4096-byte read is ~512 PINE round-trips on a typical 8-byte-aligned region, around 50 ms over loopback."
},
additionalProperties: false,
},
additionalProperties: false,
},
},
// ── Memory writes ───────────────────────────────────────────────────────
{
name: "pine_write8",
description: "PURPOSE: Write a single unsigned byte (0-255) to the emulator's EE address space at the given absolute address. " +
"USAGE: Use for single-byte cheats, debug pokes, and game-state mutations (give a player N lives, unlock a flag, set a counter). For 16/32/64-bit values prefer pine_write16/write32/write64 (single call instead of byte-at-a-time, and atomic from the emulator's perspective). For seeding many bytes there is no native bulk write — loop pine_write8 yourself or batch via pine_write64 on aligned regions. To roll back later use pine_save_state BEFORE the write and pine_load_state to restore. " +
"BEHAVIOR: DESTRUCTIVE: overwrites whatever was at `address` with no undo. The write is direct memory access — bypasses TLB protection and any DMA semantics — so writes to read-only regions (BIOS, IOP ROM, etc.) are silently dropped by PCSX2 with no error. The write takes effect immediately, but visible game-state effects only appear when the emulator next ticks (so writing while paused shows changes only after unpause or frame-step). No alignment requirement for byte access. Returns an error if the connection drops or PINE returns FAIL on a wholly invalid address.\n\n" +
PS2_REGIONS + "\n\n" +
"RETURNS: Single line 'Wrote VAL_DEC (0xVAL_HEX) → ADDR_HEX'.",
inputSchema: {
type: "object",
required: ["address", "value"],
properties: {
address: { type: "integer", minimum: 0, description: ADDRESS_PARAM_DESC(1) },
value: {
type: "integer",
minimum: 0,
maximum: 255,
description: "Byte value to write. Must be 0-255 (0x00-0xFF). Values outside this range are rejected by the schema."
// ── Memory writes ───────────────────────────────────────────────────────
{
name: "pine_write8",
description: `PURPOSE: Write a single unsigned byte (0-255) to the emulator's ${ADDR_SPACE} at the given absolute address. ` +
"USAGE: Use for single-byte cheats, debug pokes, and game-state mutations (give a player N lives, unlock a flag, set a counter). For 16/32/64-bit values prefer pine_write16/write32/write64 (single call instead of byte-at-a-time, and atomic from the emulator's perspective). For seeding many bytes there is no native bulk write — loop pine_write8 yourself or batch via pine_write64 on aligned regions. To roll back later use pine_save_state BEFORE the write and pine_load_state to restore. " +
"BEHAVIOR: DESTRUCTIVE: overwrites whatever was at `address` with no undo. The write is direct memory access — bypasses TLB protection and any DMA semantics — so writes to read-only regions (BIOS, etc.) are silently dropped by the emulator with no error. The write takes effect immediately, but visible game-state effects only appear when the emulator next ticks (so writing while paused shows changes only after unpause or frame-step). No alignment requirement for byte access. Returns an error if the connection drops or PINE returns FAIL on a wholly invalid address.\n\n" +
MEM + "\n\n" +
"RETURNS: Single line 'Wrote VAL_DEC (0xVAL_HEX) → ADDR_HEX'.",
inputSchema: {
type: "object",
required: ["address", "value"],
properties: {
address: { type: "integer", minimum: 0, description: addressParamDesc(target, 1) },
value: {
type: "integer",
minimum: 0,
maximum: 255,
description: "Byte value to write. Must be 0-255 (0x00-0xFF). Values outside this range are rejected by the schema."
},
},
additionalProperties: false,
},
additionalProperties: false,
},
},
{
name: "pine_write16",
description: "PURPOSE: Write an unsigned 16-bit little-endian value to the emulator's EE address space at the given absolute address. " +
"USAGE: Use for 16-bit cheats and pokes (HP, score, coordinates). For single bytes use pine_write8; for 32/64-bit use pine_write32/write64; for big-endian fields, byteswap into a value with the bytes reversed yourself before calling — this tool always writes little-endian. To roll back, snapshot first via pine_save_state. " +
"BEHAVIOR: DESTRUCTIVE: overwrites two bytes (low byte at `address`, high byte at `address+1`) with no undo. Direct memory write — bypasses TLB protection; writes to read-only regions are silently dropped. Address MUST be 2-byte aligned — PINE on PCSX2 does NOT enforce this; an unaligned address writes the bytes at the aligned address below, silently corrupting unrelated state. Returns an error if the connection drops or PINE returns FAIL on a wholly invalid address.\n\n" +
PS2_REGIONS + "\n\n" +
"RETURNS: Single line 'Wrote VAL_DEC (0xVAL_HEX) → ADDR_HEX'.",
inputSchema: {
type: "object",
required: ["address", "value"],
properties: {
address: { type: "integer", minimum: 0, description: ADDRESS_PARAM_DESC(2) },
value: {
type: "integer",
minimum: 0,
maximum: 65535,
description: "16-bit value to write. Must be 0-65535 (0x0000-0xFFFF). LSB lands at `address`, MSB at `address+1`. " +
"For signed 16-bit values, encode as two's complement (e.g. -1 → 0xFFFF). Values outside the range are rejected by the schema."
{
name: "pine_write16",
description: `PURPOSE: Write an unsigned 16-bit little-endian value to ${ADDR_SPACE}. ` +
"USAGE: For 16-bit cheats/pokes (HP, score, coordinates). For single bytes use pine_write8; for 32/64-bit use pine_write32/write64; for big-endian fields byteswap first (this tool always writes little-endian). Snapshot via pine_save_state for rollback. " +
"BEHAVIOR: DESTRUCTIVE: overwrites two bytes (low at `address`, high at `address+1`) with no undo. Direct write — bypasses TLB; writes to read-only regions (BIOS) are silently dropped. Address MUST be 2-byte aligned. " + target.alignmentNote + " Errors on connection drop or PINE FAIL.\n\n" +
MEM + "\n\n" +
"RETURNS: 'Wrote VAL_DEC (0xVAL_HEX) → ADDR_HEX'.",
inputSchema: {
type: "object",
required: ["address", "value"],
properties: {
address: { type: "integer", minimum: 0, description: addressParamDesc(target, 2) },
value: {
type: "integer",
minimum: 0,
maximum: 65535,
description: "16-bit value to write. Must be 0-65535 (0x0000-0xFFFF). LSB lands at `address`, MSB at `address+1`. " +
"For signed 16-bit values, encode as two's complement (e.g. -1 → 0xFFFF). Values outside the range are rejected by the schema."
},
},
additionalProperties: false,
},
additionalProperties: false,
},
},
{
name: "pine_write32",
description: "PURPOSE: Write an unsigned 32-bit little-endian value to the emulator's EE address space at the given absolute address. " +
"USAGE: Use for 32-bit cheats and pokes — timestamps, large counters, RGBA colors, the lower half of pointers. For single byte / 16-bit values use pine_write8/write16; for true 64-bit fields (full EE pointers, large IDs) use pine_write64 — chaining two pine_write32 calls is non-atomic and can be observed mid-update by the running game. For big-endian layouts, byteswap into a little-endian value yourself first. " +
"BEHAVIOR: DESTRUCTIVE: overwrites four bytes starting at `address` with no undo. Direct memory write — bypasses TLB protection and DMA mediation; writes to read-only regions (BIOS, IOP ROM) are silently dropped with no error. Address MUST be 4-byte aligned — PINE on PCSX2 does NOT enforce this; an unaligned address silently corrupts the bytes at the aligned address below, NOT what you asked for, and supplies no error. Values are NOT truncated by this tool: the schema rejects anything outside 0-4294967295 (0x00000000-0xFFFFFFFF) before the call ever reaches PINE. Returns an error if the connection drops or PINE returns FAIL on a wholly invalid address.\n\n" +
PS2_REGIONS + "\n\n" +
"RETURNS: Single line 'Wrote VAL_DEC (0xVAL_HEX) → ADDR_HEX'.",
inputSchema: {
type: "object",
required: ["address", "value"],
properties: {
address: { type: "integer", minimum: 0, description: ADDRESS_PARAM_DESC(4) },
value: {
type: "integer",
minimum: 0,
maximum: 4294967295,
description: "32-bit value to write. Must be 0-4294967295 (0x00000000-0xFFFFFFFF). LSB lands at `address`, MSB at `address+3`. " +
"For signed 32-bit values, encode as two's complement (e.g. -1 → 0xFFFFFFFF). For floats, reinterpret the IEEE-754 bits as an integer first. " +
"Values outside the range are rejected by the schema, NOT silently truncated — pass the value you actually want stored."
{
name: "pine_write32",
description: `PURPOSE: Write an unsigned 32-bit little-endian value to the emulator's ${ADDR_SPACE} at the given absolute address. ` +
"USAGE: Use for 32-bit cheats and pokes — timestamps, large counters, RGBA colors, the lower half of pointers. For single byte / 16-bit values use pine_write8/write16; for true 64-bit fields use pine_write64 — chaining two pine_write32 calls is non-atomic and can be observed mid-update by the running game. For big-endian layouts, byteswap into a little-endian value yourself first. " +
"BEHAVIOR: DESTRUCTIVE: overwrites four bytes starting at `address` with no undo. Direct memory write — bypasses TLB protection and DMA mediation; writes to read-only regions (BIOS) are silently dropped with no error. Address MUST be 4-byte aligned. " + target.alignmentNote + " Values are NOT truncated by this tool: the schema rejects anything outside 0-4294967295 (0x00000000-0xFFFFFFFF) before the call ever reaches PINE. Returns an error if the connection drops or PINE returns FAIL on a wholly invalid address.\n\n" +
MEM + "\n\n" +
"RETURNS: Single line 'Wrote VAL_DEC (0xVAL_HEX) → ADDR_HEX'.",
inputSchema: {
type: "object",
required: ["address", "value"],
properties: {
address: { type: "integer", minimum: 0, description: addressParamDesc(target, 4) },
value: {
type: "integer",
minimum: 0,
maximum: 4294967295,
description: "32-bit value to write. Must be 0-4294967295 (0x00000000-0xFFFFFFFF). LSB lands at `address`, MSB at `address+3`. " +
"For signed 32-bit values, encode as two's complement (e.g. -1 → 0xFFFFFFFF). For floats, reinterpret the IEEE-754 bits as an integer first. " +
"Values outside the range are rejected by the schema, NOT silently truncated — pass the value you actually want stored."
},
},
additionalProperties: false,
},
additionalProperties: false,
},
},
{
name: "pine_write64",
description: "PURPOSE: Write an unsigned 64-bit little-endian value to the emulator's EE address space at the given absolute address. " +
"USAGE: Use for true 64-bit writes — full PS2 EE pointers, large IDs, packed doubleword state. Single atomic write from the emulator's perspective; preferred over chaining two pine_write32 calls when atomicity matters (a running game can observe the in-between state with the chained approach). For 8/16/32-bit values use the corresponding sibling. " +
"BEHAVIOR: DESTRUCTIVE: overwrites eight bytes starting at `address` with no undo. Direct memory write — bypasses TLB protection; writes to read-only regions are silently dropped. Address MUST be 8-byte aligned — PINE on PCSX2 does NOT enforce this; an unaligned address silently corrupts unrelated bytes. The `value` is passed as a DECIMAL STRING (not a JSON number) to preserve precision past 2^53 (JavaScript number limit) — pass '0' through '18446744073709551615' as a string, e.g. \"4294967296\". Strings that don't match ^[0-9]+$ are rejected by the schema. Returns an error if the connection drops or PINE returns FAIL on a wholly invalid address.\n\n" +
PS2_REGIONS + "\n\n" +
"RETURNS: Single line 'Wrote VAL_DEC (0xVAL_HEX) → ADDR_HEX' — VAL_DEC may exceed 2^53.",
inputSchema: {
type: "object",
required: ["address", "value"],
properties: {
address: { type: "integer", minimum: 0, description: ADDRESS_PARAM_DESC(8) },
value: {
type: "string",
pattern: "^[0-9]+$",
description: "64-bit value to write, as a non-negative DECIMAL STRING (digits only, no '0x' prefix, no sign, no separators). " +
"Range 0 through 18446744073709551615 (2^64 - 1). Example: \"18446744073709551615\" writes 0xFFFFFFFFFFFFFFFF. " +
"Encoded as a string so values past 2^53 are preserved exactly (JSON numbers lose precision at that point). " +
"For signed 64-bit values, encode as two's complement (e.g. -1 → \"18446744073709551615\")."
{
name: "pine_write64",
description: `PURPOSE: Write an unsigned 64-bit little-endian value to ${ADDR_SPACE}. ` +
"USAGE: For true 64-bit writes — full pointers, large IDs, packed doubleword state. Atomic from the emulator's perspective; preferred over chaining two pine_write32 calls (a running game can observe the in-between state). For 8/16/32-bit values use the corresponding sibling. " +
"BEHAVIOR: DESTRUCTIVE: overwrites eight bytes from `address` with no undo. Direct write — bypasses TLB; writes to read-only regions silently dropped. Address MUST be 8-byte aligned. " + target.alignmentNote + " `value` is a DECIMAL STRING (0 through 18446744073709551615) to preserve precision past JS's 2^53 number limit. Errors on connection drop or PINE FAIL.\n\n" +
MEM + "\n\n" +
"RETURNS: 'Wrote VAL_DEC (0xVAL_HEX) → ADDR_HEX' — VAL_DEC may exceed 2^53.",
inputSchema: {
type: "object",
required: ["address", "value"],
properties: {
address: { type: "integer", minimum: 0, description: addressParamDesc(target, 8) },
value: {
type: "string",
pattern: "^[0-9]+$",
description: "64-bit value to write, as a non-negative DECIMAL STRING (digits only, no '0x' prefix, no sign, no separators). " +
"Range 0 through 18446744073709551615 (2^64 - 1). Example: \"18446744073709551615\" writes 0xFFFFFFFFFFFFFFFF. " +
"Encoded as a string so values past 2^53 are preserved exactly (JSON numbers lose precision at that point). " +
"For signed 64-bit values, encode as two's complement (e.g. -1 → \"18446744073709551615\")."
},
},
additionalProperties: false,
},
additionalProperties: false,
},
},
// ── Save state ─────────────────────────────────────────────────────────
{
name: "pine_save_state",
description: "PURPOSE: Trigger the emulator to save its complete state (RAM, EE/IOP/VU registers, GS state, sound, timing) to the given numbered savestate slot. " +
"USAGE: Use as a rollback point before risky writes, to bookmark an interesting game state, or to share a repro state with a teammate. The companion pine_load_state restores from the same slot. PINE savestates are SLOT-BASED (0-255 integer), unlike file-path-based emulator APIs (e.g. BizHawk's) — the emulator decides where on disk to put the file. PCSX2's GUI uses slots 0-9 as F1-F10; programmatic use can extend up to 255 if you don't mind being outside the GUI's range. " +
"BEHAVIOR: DESTRUCTIVE TO TARGET SLOT: silently overwrites whatever was previously saved in that slot — no prompt, no backup, no way to recover the old state. The save is bound to the EXACT game disc and PCSX2 version that produced it; loading a slot saved against a different game or major PCSX2 version usually crashes the core. The PINE call returns immediately after PCSX2 schedules the save, NOT after the file is fully on disk — for very large states there can be a brief window where the file is half-written. Returns an error if PCSX2 has no game loaded, the savestate folder isn't writable, or PINE returns FAIL.\n\n" +
"RETURNS: Single line 'Save state triggered for slot N'.",
inputSchema: {
type: "object",
required: ["slot"],
properties: {
slot: { type: "integer", minimum: 0, maximum: 255, description: SLOT_PARAM_DESC },
// ── Save state ─────────────────────────────────────────────────────────
{
name: "pine_save_state",
description: "PURPOSE: Trigger the emulator to save complete state (RAM, registers, GPU, audio, timing) to a numbered slot. " +
`USAGE: Rollback point before risky writes, bookmarks, repro sharing. Companion pine_load_state restores from the same slot. PINE savestates are SLOT-BASED (0-255), not file-path-based — ${EMU_NAME} picks the disk location. ` +
`BEHAVIOR: DESTRUCTIVE TO TARGET SLOT: silently overwrites prior contents — no prompt, no backup, no recovery. Bound to the exact game disc and ${EMU_NAME} version; loading mismatched usually crashes the core. The call returns when ${EMU_NAME} schedules the save, NOT when the file is on disk — brief half-written window possible. Errors on no game loaded, unwritable folder, or PINE FAIL.\n\n` +
"RETURNS: 'Save state triggered for slot N'.",
inputSchema: {
type: "object",
required: ["slot"],
properties: {
slot: { type: "integer", minimum: 0, maximum: 255, description: slotParamDesc(target) },
},
additionalProperties: false,
},
additionalProperties: false,
},
},
{
name: "pine_load_state",
description: "PURPOSE: Trigger the emulator to load a previously-saved state from the given numbered savestate slot, replacing all live state. " +
"USAGE: Counterpart to pine_save_state. Use to undo a sequence of writes/inputs (the snapshot/experiment/restore workflow), to jump to a bookmarked game state, or to start each tool-call sequence from a known baseline. There is no PINE 'reset' opcode — to start fresh from boot you must use the emulator's GUI or pre-prepare a slot containing a freshly booted state. " +
"BEHAVIOR: DESTRUCTIVE TO LIVE STATE: replaces ALL current emulator state (RAM, registers, GS, audio, etc.) with the contents of the slot's file. Anything not previously snapshotted is lost permanently. The state file MUST come from the same game disc and same PCSX2 version that produced it; loading an incompatible state typically crashes the core (no recovery without restarting PCSX2). The PINE call returns immediately after PCSX2 schedules the load, NOT after the load is fully visible — there can be a brief window where state is partially loaded. Returns an error if the slot file doesn't exist, the file is corrupt or for the wrong game, or PINE returns FAIL.\n\n" +
"RETURNS: Single line 'Load state triggered for slot N'.",
inputSchema: {
type: "object",
required: ["slot"],
properties: {
slot: { type: "integer", minimum: 0, maximum: 255, description: SLOT_PARAM_DESC },
{
name: "pine_load_state",
description: "PURPOSE: Trigger the emulator to load a previously-saved state from the given numbered savestate slot, replacing all live state. " +
"USAGE: Counterpart to pine_save_state. Use to undo a sequence of writes/inputs (the snapshot/experiment/restore workflow), to jump to a bookmarked game state, or to start each tool-call sequence from a known baseline. There is no PINE 'reset' opcode — to start fresh from boot you must use the emulator's GUI or pre-prepare a slot containing a freshly booted state. " +
`BEHAVIOR: DESTRUCTIVE TO LIVE STATE: replaces ALL current emulator state (RAM, registers, GPU, audio, etc.) with the contents of the slot's file. Anything not previously snapshotted is lost permanently. The state file MUST come from the same game disc and same ${EMU_NAME} version that produced it; loading an incompatible state typically crashes the core (no recovery without restarting ${EMU_NAME}). The PINE call returns immediately after ${EMU_NAME} schedules the load, NOT after the load is fully visible — there can be a brief window where state is partially loaded. Returns an error if the slot file doesn't exist, the file is corrupt or for the wrong game, or PINE returns FAIL.\n\n` +
"RETURNS: Single line 'Load state triggered for slot N'.",
inputSchema: {
type: "object",
required: ["slot"],
properties: {
slot: { type: "integer", minimum: 0, maximum: 255, description: slotParamDesc(target) },
},
additionalProperties: false,
},
additionalProperties: false,
},
},
];
];
}
function ok(text) {

@@ -302,3 +300,4 @@ return { content: [{ type: "text", text }] };

}
export function registerTools(server, pine) {
export function registerTools(server, pine, target) {
const TOOLS = buildTools(target);
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));

@@ -305,0 +304,0 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {

@@ -1,1 +0,1 @@

{"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
{
"name": "mcp-pine",
"version": "0.2.1",
"description": "MCP server for emulators that speak PINE (PCSX2, and other PINE-compatible emulators) — exposes memory read/write and savestate control",
"version": "0.3.0",
"description": "MCP server for emulators that speak PINE — first-class PCSX2 (PS2) and RPCS3 (PS3), with target-aware tool descriptions, memory r/w, and savestate control",
"license": "MIT",

@@ -49,4 +49,6 @@ "type": "module",

"playstation",
"ps2",
"ps3",
"claude"
]
}
+18
-15

@@ -8,7 +8,7 @@ # mcp-pine

An [MCP](https://modelcontextprotocol.io) server for emulators that speak [PINE](https://github.com/GovanifY/pine) (Protocol for Instrumentation of Network Emulators) — exposes **memory read/write** and **savestate control** to MCP-compatible clients (Claude Desktop, Claude Code, etc.).
An [MCP](https://modelcontextprotocol.io) server for emulators that speak [PINE](https://github.com/GovanifY/pine) (Protocol for Instrumentation of Network Emulators) — first-class support for **PCSX2** (PS2) and **RPCS3** (PS3), with target-aware tool descriptions so the agent sees the right memory map for whichever emulator it's pointed at. Exposes memory read/write and savestate control. Driven from MCP-compatible clients (Claude Desktop, Claude Code, etc.).
## What you can do with it
- **Read & write emulated memory** — 8/16/32/64-bit, anywhere in the EE address space
- **Read & write emulated memory** — 8/16/32/64-bit, anywhere in the emulator's address space (PS2 EE for PCSX2, PPU main memory for RPCS3)
- **Trigger save / load state** to numbered slots

@@ -18,2 +18,4 @@ - **Query game metadata** — title, serial, disc CRC, version

Tool descriptions, memory-map context, and setup help are rendered **per-target** at startup — set `PINE_TARGET=rpcs3` and every memory-tool description shows PS3 PPU addresses instead of PS2 EE addresses.
What you **can't** do (because PINE itself doesn't expose these):

@@ -39,10 +41,13 @@ - Send controller input

| Emulator | Platform | PINE built in? | Default slot |
|------------------|---------------|-------------------------------------------|--------------|
| **PCSX2 ≥ 1.7** ([setup](#pcsx2)) | PlayStation 2 | ✅ Yes (toggle in settings) | 28011 |
| **RPCS3** ([setup](#rpcs3)) | PlayStation 3 | ⚠️ Has IPC with PINE-compatible opcodes — verify before relying on it | varies |
| **Duckstation** | PlayStation 1 | ⚠️ PINE has been discussed in upstream issues; check current build | varies |
| Emulator | Platform | PINE built in? | Default slot | `PINE_TARGET` |
|------------------|---------------|-------------------------------------------|--------------|---------------|
| **PCSX2 ≥ 1.7** ([setup](#pcsx2)) | PlayStation 2 | ✅ Yes (toggle in settings) | 28011 | `pcsx2` (default) |
| **RPCS3** ([setup](#rpcs3)) | PlayStation 3 | ⚠️ Has IPC with PINE-compatible opcodes — verify before relying on it | 28012 | `rpcs3` |
Other emulators implementing the PINE spec should work out of the box once you point `mcp-pine` at the right slot — open an issue if you've tested one and it works.
**Note on DuckStation (PS1)**: DuckStation had PINE support from May–September 2024 but [dropped it in commit 19698559](https://github.com/stenzek/duckstation/commit/19698559). Current builds have no PINE server. If upstream brings it back, `PINE_TARGET=duckstation` is reserved.
Setting `PINE_TARGET` does two things: (1) selects the right Unix socket filename on Linux/macOS, and (2) renders all tool descriptions, memory maps, and setup help for that emulator's address space. Default is `pcsx2` for back-compat.
## Requirements

@@ -98,6 +103,3 @@

### Duckstation
Check whether your build of Duckstation includes a PINE server (this varies by version). If yes, set `PINE_TARGET=duckstation PINE_SLOT=<port>`.
## Register with your MCP client

@@ -147,4 +149,4 @@

|---------------------|---------------|---------|
| `PINE_TARGET` | `pcsx2` | Emulator name — used as the prefix in the Unix socket file path on Linux/macOS (`<target>.sock.<slot>`). Ignored on Windows (TCP only). |
| `PINE_SLOT` | `28011` | PINE slot — also the TCP port on Windows |
| `PINE_TARGET` | `pcsx2` | Emulator name. Known values: `pcsx2`, `rpcs3`. Selects (1) the Unix socket file prefix on Linux/macOS (`<target>.sock.<slot>`), and (2) the memory map and setup help shown in tool descriptions. Unknown values pass through with a generic memory map. |
| `PINE_SLOT` | target default | PINE slot — also the TCP port on Windows. Defaults: `pcsx2`=28011, `rpcs3`=28012. Set explicitly to override. |
| `PINE_HOST` | `127.0.0.1` | Override the host (TCP only) |

@@ -168,14 +170,15 @@ | `PINE_SOCKET_PATH` | (auto) | Override the full Unix socket path on Linux/macOS, bypassing automatic resolution |

### PlayStation 2 address space (cheat sheet)
### PlayStation 2 address space (PCSX2, default target)
| Range | Region |
|-------|--------|
| `0x00000000` | EE main RAM (32 MiB) — **start here for game data** |
| `0x00100000-0x01FFFFFF` | EE main RAM (32 MiB) — **start here for game data** |
| `0x10000000` | Hardware registers (DMA, GIF, VIF) |
| `0x11000000` | VU0 / VU1 memory |
| `0x12000000` | GS privileged registers |
| `0x1C000000` | IOP RAM (2 MiB) |
| `0x1C000000-0x1C1FFFFF` | IOP RAM (2 MiB) |
| `0x1F800000` | IOP scratchpad |
| `0x70000000` | EE scratchpad (16 KiB) |
## Troubleshooting

@@ -182,0 +185,0 @@