Skip to main content
Glama
dmang-dev

mcp-dolphin

dolphin_load_state

Restores a saved emulator state from a specific slot, overwriting all live state. Use it after saving state to revert experiments or undo changes.

Instructions

PURPOSE: Load a previously-saved state from the given slot, replacing all live state. USAGE: Counterpart to dolphin_save_state. The classic snapshot/experiment/restore loop: save_state(N) → run experiment → load_state(N) to undo. BEHAVIOR: DESTRUCTIVE TO LIVE STATE: replaces ALL current emulator state. The state file MUST come from the same game disc and same Dolphin build that produced it; loading an incompatible state typically crashes the core (no recovery without restarting Dolphin). RETURNS: 'Load state triggered for slot N'.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
slotYesSlot (0-255). 1-10 are mapped to F1-F10 in Dolphin's GUI.

Implementation Reference

  • The MCP tool handler that executes 'dolphin_load_state' — calls the Python bridge via DolphinClient with 'savestate.load_from_slot' and returns a success message.
    case "dolphin_load_state": {
      await dol.call("savestate.load_from_slot", [p.slot as number]);
      return ok(`Load state triggered for slot ${p.slot}`);
    }
  • The tool registration/schema definition for 'dolphin_load_state' — defines name, description, and inputSchema requiring a 'slot' integer (0-255).
    {
      name: "dolphin_load_state",
      description:
        "PURPOSE: Load a previously-saved state from the given slot, replacing all live state. " +
        "USAGE: Counterpart to dolphin_save_state. The classic snapshot/experiment/restore loop: save_state(N) → run experiment → load_state(N) to undo. " +
        "BEHAVIOR: DESTRUCTIVE TO LIVE STATE: replaces ALL current emulator state. The state file MUST come from the same game disc and same Dolphin build that produced it; loading an incompatible state typically crashes the core (no recovery without restarting Dolphin). " +
        "RETURNS: 'Load state triggered for slot N'.",
      inputSchema: {
        type: "object",
        required: ["slot"],
        properties: {
          slot: { type: "integer", minimum: 0, maximum: 255, description: "Slot (0-255). 1-10 are mapped to F1-F10 in Dolphin's GUI." },
        },
        additionalProperties: false,
      },
    },
  • src/tools.ts:488-618 (registration)
    The registration function that wires up all tools (including 'dolphin_load_state') into the MCP server via ListToolsRequestSchema and CallToolRequestSchema handlers.
    export function registerTools(server: Server, dol: DolphinClient): 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 a = () => p.address as number;
    
        switch (name) {
          case "dolphin_ping": {
            const r = await dol.call<{ bridge_version: string; dolphin: string }>("bridge.ping");
            return ok(`OK — bridge v${r.bridge_version} (${r.dolphin})`);
          }
          case "dolphin_get_info": {
            const r = await dol.call<{ bridge_version: string; dolphin: string }>("bridge.ping");
            return ok(
              `Bridge version: ${r.bridge_version}\n` +
              `Dolphin label:  ${r.dolphin}`,
            );
          }
    
          case "dolphin_read8":  return ok(`${addrHex(a())}: ${fmtHex(await dol.call<number>("memory.read_u8",  [a()]))}`);
          case "dolphin_read16": return ok(`${addrHex(a())}: ${fmtHex(await dol.call<number>("memory.read_u16", [a()]))}`);
          case "dolphin_read32": return ok(`${addrHex(a())}: ${fmtHex(await dol.call<number>("memory.read_u32", [a()]))}`);
          case "dolphin_read64": {
            const v = BigInt(await dol.call<number | string>("memory.read_u64", [a()]) as never);
            return ok(`${addrHex(a())}: ${fmtHex(v)}`);
          }
    
          case "dolphin_read_range": {
            const len = p.length as number;
            const hex = await dol.call<string>("memory.read_bytes", [a(), len]);
            const bytes = hex.match(/.{2}/g) ?? [];
            const spaced = bytes.map((b) => b.toUpperCase()).join(" ");
            return ok(`${addrHex(a())} [${bytes.length} bytes]:\n${spaced}`);
          }
    
          case "dolphin_write8": {
            await dol.call("memory.write_u8", [a(), p.value as number]);
            return ok(`Wrote ${fmtHex(p.value as number)} → ${addrHex(a())}`);
          }
          case "dolphin_write16": {
            await dol.call("memory.write_u16", [a(), p.value as number]);
            return ok(`Wrote ${fmtHex(p.value as number)} → ${addrHex(a())}`);
          }
          case "dolphin_write32": {
            await dol.call("memory.write_u32", [a(), p.value as number]);
            return ok(`Wrote ${fmtHex(p.value as number)} → ${addrHex(a())}`);
          }
          case "dolphin_write64": {
            const v = BigInt(p.value as string);
            // Felk's API takes a Python int; JS BigInt won't JSON-serialise so we
            // convert to a string and the bridge parses it back via int(str).
            // (For now the bridge actually accepts a JSON number — but 2^53 boundary
            // hits hard, so we use string transport and let the bridge widen.)
            await dol.call("memory.write_u64", [a(), v <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(v) : Number(v)]);
            return ok(`Wrote ${fmtHex(v)} → ${addrHex(a())}`);
          }
    
          case "dolphin_press_gc_buttons": {
            const port = (p.port as number | undefined) ?? 0;
            const state = p.state as Record<string, unknown>;
            await dol.call("controller.set_gc_buttons", [port, state]);
            const keys = Object.keys(state).join(",") || "(empty)";
            return ok(`Set GC port ${port}: ${keys}`);
          }
          case "dolphin_press_wiimote_buttons": {
            const port = (p.port as number | undefined) ?? 0;
            const state = p.state as Record<string, unknown>;
            await dol.call("controller.set_wiimote_buttons", [port, state]);
            const keys = Object.keys(state).join(",") || "(empty)";
            return ok(`Set Wii Remote port ${port}: ${keys}`);
          }
    
          case "dolphin_set_wiimote_pointer": {
            const port = (p.port as number | undefined) ?? 0;
            const x = p.x as number, y = p.y as number;
            await dol.call("controller.set_wiimote_pointer", [port, x, y]);
            return ok(`Set Wii Remote port ${port} pointer to (${x}, ${y})`);
          }
          case "dolphin_set_wiimote_acceleration": {
            const port = (p.port as number | undefined) ?? 0;
            const x = p.x as number, y = p.y as number, z = p.z as number;
            await dol.call("controller.set_wiimote_acceleration", [port, x, y, z]);
            return ok(`Set Wii Remote port ${port} accel to (${x}, ${y}, ${z})`);
          }
          case "dolphin_set_wiimote_angular_velocity": {
            const port = (p.port as number | undefined) ?? 0;
            const x = p.x as number, y = p.y as number, z = p.z as number;
            await dol.call("controller.set_wiimote_angular_velocity", [port, x, y, z]);
            return ok(`Set Wii Remote port ${port} angular_velocity to (${x}, ${y}, ${z})`);
          }
    
          case "dolphin_reset":  await dol.call("emulation.reset");  return ok("Reset triggered.");
    
          case "dolphin_frame_advance": {
            // Implemented client-side as polling on frame.get_count rather than
            // a bridge-side blocking wait — keeps the bridge coroutine free to
            // service other commands and avoids per-call long-hold tying up the
            // dispatcher.
            const frames = p.frames as number;
            const start = await dol.call<number>("frame.get_count");
            const target = start + frames;
            const pollEveryMs = 16;
            const overallTimeoutMs = 15000;
            const deadline = Date.now() + overallTimeoutMs;
            let now = start;
            while (now < target) {
              if (Date.now() > deadline) {
                throw new Error(`frame_advance(${frames}) timed out after ${overallTimeoutMs}ms (start=${start}, target=${target}, reached=${now}); emulator may be paused`);
              }
              await new Promise((r) => setTimeout(r, pollEveryMs));
              now = await dol.call<number>("frame.get_count");
            }
            return ok(`Advanced to frame ${now} (waited ${now - start} frames).`);
          }
    
          case "dolphin_save_state": {
            await dol.call("savestate.save_to_slot", [p.slot as number]);
            return ok(`Save state triggered for slot ${p.slot}`);
          }
          case "dolphin_load_state": {
            await dol.call("savestate.load_from_slot", [p.slot as number]);
            return ok(`Load state triggered for slot ${p.slot}`);
          }
    
          default:
            throw new Error(`Unknown tool: ${name}`);
        }
      });
    }
  • The Python bridge helper function that actually calls Dolphin's savestate.load_from_slot API — invoked remotely by the Node.js handler.
    def _save_to_slot(p):    savestate.save_to_slot(p[0]);   return None
    def _load_from_slot(p):  savestate.load_from_slot(p[0]); return None
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Explicitly states 'DESTRUCTIVE TO LIVE STATE: replaces ALL current emulator state' and warns of crash risk from incompatible states. No annotations provided, so description fully carries burden.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Four dense, front-loaded sentences with zero fluff. Every sentence serves a purpose: purpose, usage, behavior, and return value.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's simplicity (one parameter, no output schema), the description fully covers purpose, usage, behavior, constraints, and return. Completely adequate.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% for the single parameter 'slot', and description adds no additional meaning beyond the schema's own description. Baseline of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Clearly states 'Load a previously-saved state from the given slot, replacing all live state.' Specific verb and resource, distinguishes from sibling dolphin_save_state.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly describes counterpart relationship and provides classic usage pattern: save_state(N) → experiment → load_state(N). Also warns about compatibility requirements.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/dmang-dev/mcp-dolphin'

If you have feedback or need assistance with the MCP directory API, please join our Discord server