Skip to main content
Glama
dmang-dev

mcp-retroarch

retroarch_read_memory

Read up to 4096 bytes from the emulated memory of a libretro core via its system memory map, returning an array of byte values.

Instructions

Read up to 4096 bytes from emulated memory via the loaded core's system memory map. Returns an array of byte values.

RetroArch exposes two distinct memory APIs:

  • READ_CORE_MEMORY / WRITE_CORE_MEMORY (used by retroarch_read/write_memory): Goes through the libretro core's system memory map. Most reliable when the core advertises a memory map (most cores do). Errors with "no memory map defined" if the loaded core doesn't.

  • READ_CORE_RAM / WRITE_CORE_RAM (used by retroarch_read/write_ram): Uses the achievement (CHEEVOS) address space. Works even when no core memory map is defined, but addresses follow CHEEVOS conventions, not the system bus. Use when read_memory returns "no memory map defined".

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
addressYesMemory address (in the core's address space)
lengthYes

Implementation Reference

  • Tool handler for 'retroarch_read_memory': calls ra.readMemory() and formats the hex output.
    case "retroarch_read_memory": {
      const bytes = await ra.readMemory(p.address as number, p.length as number);
      const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0").toUpperCase()).join(" ");
      return ok(`${addrHex(p.address as number)} [${bytes.length} bytes]:\n${hex}`);
    }
  • Schema/registration definition for 'retroarch_read_memory' tool, including name, description, and inputSchema.
    {
      name: "retroarch_read_memory",
      description: `Read up to 4096 bytes from emulated memory via the loaded core's system memory map. Returns an array of byte values.\n\n${MEMORY_NOTE}`,
      inputSchema: {
        type: "object",
        required: ["address", "length"],
        properties: {
          address: { type: "integer", description: "Memory address (in the core's address space)" },
          length:  { type: "integer", minimum: 1, maximum: 4096 },
        },
      },
  • src/tools.ts:176-246 (registration)
    registerTools() registers all tools via ListToolsRequestSchema and CallToolRequestSchema - the switch case dispatches to the handler.
    export function registerTools(server: Server, ra: RetroArchClient): 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>;
    
        switch (name) {
          case "retroarch_ping": {
            const v = await ra.getVersion();
            return ok(`OK — RetroArch ${v}`);
          }
    
          case "retroarch_get_status": {
            const s = await ra.getStatus();
            if (s.state === "contentless") return ok("No content loaded");
            return ok(
              `State:  ${s.state}\n` +
              `System: ${s.system}\n` +
              `Game:   ${s.game}\n` +
              `CRC32:  ${s.crc32 ?? "(none reported)"}`,
            );
          }
    
          case "retroarch_get_config": {
            const v = await ra.getConfigParam(p.name as string);
            return ok(`${p.name} = ${v}`);
          }
    
          case "retroarch_read_memory": {
            const bytes = await ra.readMemory(p.address as number, p.length as number);
            const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0").toUpperCase()).join(" ");
            return ok(`${addrHex(p.address as number)} [${bytes.length} bytes]:\n${hex}`);
          }
    
          case "retroarch_write_memory": {
            const n = await ra.writeMemory(p.address as number, p.bytes as number[]);
            return ok(`Wrote ${n} bytes → ${addrHex(p.address as number)}`);
          }
    
          case "retroarch_read_ram": {
            const bytes = await ra.readRam(p.address as number, p.length as number);
            const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0").toUpperCase()).join(" ");
            return ok(`${addrHex(p.address as number)} [${bytes.length} bytes, CHEEVOS]:\n${hex}`);
          }
    
          case "retroarch_write_ram": {
            await ra.writeRam(p.address as number, p.bytes as number[]);
            return ok(`Wrote ${(p.bytes as number[]).length} bytes → ${addrHex(p.address as number)} (CHEEVOS, no ack)`);
          }
    
          case "retroarch_pause_toggle":  await ra.pauseToggle();   return ok("Pause toggled");
          case "retroarch_frame_advance": await ra.frameAdvance();  return ok("Advanced one frame");
          case "retroarch_reset":         await ra.reset();         return ok("Game reset");
          case "retroarch_screenshot":    await ra.screenshot();    return ok("Screenshot saved to RetroArch's configured screenshot directory");
          case "retroarch_show_message": {
            await ra.showMessage(p.message as string);
            return ok(`Showed: ${p.message}`);
          }
    
          case "retroarch_save_state_current":  await ra.saveStateCurrent();          return ok("Saved to current slot");
          case "retroarch_load_state_current":  await ra.loadStateCurrent();          return ok("Loaded from current slot");
          case "retroarch_load_state_slot":     await ra.loadStateSlot(p.slot as number); return ok(`Loaded from slot ${p.slot}`);
          case "retroarch_state_slot_plus":     await ra.stateSlotPlus();             return ok("Incremented current slot");
          case "retroarch_state_slot_minus":    await ra.stateSlotMinus();            return ok("Decremented current slot");
    
          default:
            throw new Error(`Unknown tool: ${name}`);
        }
      });
    }
  • RetroArchClient.readMemory() - sends READ_CORE_MEMORY command via UDP and parses the reply bytes via parseMemoryReply().
    /** Memory read via libretro's system memory map (preferred). */
    async readMemory(addr: number, length: number): Promise<Uint8Array> {
      if (length <= 0)   throw new Error("length must be positive");
      if (length > 4096) throw new Error("length exceeds 4096 byte limit");
      const cmd = `READ_CORE_MEMORY 0x${addr.toString(16)} ${length}`;
      const r = (await this.query(cmd)).toString().trim();
      return parseMemoryReply(r, "READ_CORE_MEMORY", length);
    }
  • parseMemoryReply() - parses the 'READ_CORE_MEMORY <addr> b1 b2 ...' response into a Uint8Array, handling failure shape.
    function parseMemoryReply(reply: string, expectedCmd: string, expectedLen: number): Uint8Array {
      const tokens = reply.split(/\s+/);
      if (tokens[0] !== expectedCmd) {
        throw new Error(`unexpected reply prefix (got "${tokens[0]}", expected "${expectedCmd}")`);
      }
      const tail = tokens.slice(2);
      if (tail.length === 0) {
        throw new Error(`${expectedCmd} returned no bytes`);
      }
      // Failure shape: "<cmd> <addr> -1 <error_msg>"
      if (tail[0] === "-1") {
        const err = tail.slice(1).join(" ") || "(no error message)";
        throw new Error(`${expectedCmd} failed: ${err}`);
      }
      const out = new Uint8Array(tail.length);
      for (let i = 0; i < tail.length; i++) {
        const v = parseInt(tail[i], 16);
        if (Number.isNaN(v) || v < 0 || v > 255) {
          throw new Error(`${expectedCmd}: malformed byte at index ${i}: "${tail[i]}"`);
        }
        out[i] = v;
      }
      if (out.length !== expectedLen) {
        // Not strictly an error — RetroArch may clamp at memory-region boundaries.
        // Caller can decide what to do with a short read.
      }
      return out;
    }
Behavior4/5

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

With no annotations, the description carries full disclosure burden. It discloses the 4096-byte limit, the return type (array of byte values), and the error case. It does not explicitly state that the tool is non-destructive (read-only), but that is strongly implied by the name and context.

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

Conciseness4/5

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

The description is well-structured with a brief overview followed by a bulleted explanation of APIs. It is slightly verbose but every sentence adds value. No wasted words.

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

Completeness4/5

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

Given the lack of annotations and output schema, the description covers the purpose, usage alternatives, error condition, and return type. It is sufficiently complete for an experienced agent, though it could mention if the operation is safe to call repeatedly.

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?

The schema has 50% coverage (only address has a description). The description adds that the address is in the core's address space and reiterates the length limit, but does not provide additional semantic context beyond the schema. Baseline score 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?

The description clearly states it reads up to 4096 bytes from emulated memory via the core's memory map, using a distinct API from its sibling retroarch_read_ram. The verb 'read' and resource 'emulated memory' are specific, and the differentiation between read_memory and read_ram is explicitly explained.

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?

The description provides explicit guidance on when to use this tool vs. retroarch_read_ram: use read_memory when the core advertises a memory map, otherwise use read_ram. It even mentions the error condition 'no memory map defined' that triggers the alternative.

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-retroarch'

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