Skip to main content
Glama

bizhawk_read16

Read a 16-bit little-endian value from emulator memory at a specified address. Use for game-state values like HP, score, or coordinates that occupy two bytes.

Instructions

PURPOSE: Read an unsigned 16-bit little-endian value from emulator memory at the given address. USAGE: Use for 16-bit fields (most game-state values: HP, score, coordinates). For single bytes use bizhawk_read8; for 32-bit values use bizhawk_read32; for non-aligned spans or big-endian fields use bizhawk_read_range and decode the bytes yourself (this tool always interprets bytes as little-endian regardless of the target system's native endianness). 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. Returns an error if the named domain doesn't exist, address+2 exceeds domain size, or the core doesn't expose memory.read_u16_le. RETURNS: Single line 'ADDR_HEX: VAL_DEC (0xVAL_HEX)'.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
addressYesByte offset within the chosen memory domain. Per-domain offsets are 0-based and INDEPENDENT of system bus addresses (e.g. SNES WRAM uses 0x09C6, NOT 0x7E09C6). Reads 2 consecutive bytes starting here. Returns an error if address < 0 or address + 2 exceeds the domain's size.
domainNoOptional case-sensitive memory domain name. Omit to use BizHawk's currently selected domain (see bizhawk_get_info → current_memory_domain). Discover available names with bizhawk_list_memory_domains; they vary per system (WRAM on SNES, RAM on NES, RDRAM on N64, 68K RAM on Genesis, MainRAM on PSX, EWRAM/IWRAM on GBA). Returns an error if the name doesn't match any domain on the loaded core.

Implementation Reference

  • Tool definition and input schema for bizhawk_read16. Defines the tool name, description, and input schema with required 'address' (integer, min 0) and optional 'domain' (string) parameters.
    {
      name: "bizhawk_read16",
      description:
        "PURPOSE: Read an unsigned 16-bit little-endian value from emulator memory at the given address. " +
        "USAGE: Use for 16-bit fields (most game-state values: HP, score, coordinates). For single bytes use bizhawk_read8; for 32-bit values use bizhawk_read32; for non-aligned spans or big-endian fields use bizhawk_read_range and decode the bytes yourself (this tool always interprets bytes as little-endian regardless of the target system's native endianness). " +
        "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. Returns an error if the named domain doesn't exist, address+2 exceeds domain size, or the core doesn't expose memory.read_u16_le. " +
        "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) },
          domain:  { type: "string", description: DOMAIN_PARAM_DESC },
        },
        additionalProperties: false,
      },
  • Handler case for bizhawk_read16 in the CallToolRequestSchema switch statement. Delegates to bh.call('read16', { address, domain }) and formats the result as 'ADDR_HEX: VAL_DEC (0xVAL_HEX)'.
    case "bizhawk_read8":  return ok(`${addrHex(a())}: ${fmtHex(await bh.call<number>("read8", { address: a(), ...dom() }))}`);
    case "bizhawk_read16": return ok(`${addrHex(a())}: ${fmtHex(await bh.call<number>("read16", { address: a(), ...dom() }))}`);
    case "bizhawk_read32": return ok(`${addrHex(a())}: ${fmtHex(await bh.call<number>("read32", { address: a(), ...dom() }))}`);
  • src/tools.ts:486-660 (registration)
    The registerTools function wires up ListToolsRequestSchema and CallToolRequestSchema handlers on the MCP server, making all tools (including bizhawk_read16) available via their definitions in TOOLS array and handled via the switch statement.
    export function registerTools(server: Server, bh: BizhawkServer): 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;
        const dom = () => p.domain ? { domain: p.domain } : {};
    
        switch (name) {
          case "bizhawk_ping": {
            const r = await bh.call<string>("ping");
            return ok(r);
          }
    
          case "bizhawk_get_info": {
            const r = await bh.call<{
              rom_name?: string;
              rom_hash?: string;
              framecount?: number;
              memory_domains?: string[];
              current_memory_domain?: string;
              capabilities?: Record<string, boolean>;
            }>("get_info");
            const lines = [
              `ROM:        ${r.rom_name ?? "(unavailable)"}`,
              `ROM hash:   ${r.rom_hash ?? "(unavailable)"}`,
              `Framecount: ${r.framecount ?? "(unavailable)"}`,
            ];
            if (r.memory_domains?.length) {
              lines.push("");
              lines.push(`Memory domains: ${r.memory_domains.join(", ")}`);
              if (r.current_memory_domain) {
                lines.push(`Active domain (used when 'domain' is omitted): ${r.current_memory_domain}`);
              }
            }
            if (r.capabilities) {
              const missing = Object.entries(r.capabilities).filter(([, v]) => !v).map(([k]) => k);
              if (missing.length) {
                lines.push("");
                lines.push(`Missing capabilities on this BizHawk build: ${missing.join(", ")}`);
              }
            }
            return ok(lines.join("\n"));
          }
    
          case "bizhawk_list_memory_domains": {
            const r = await bh.call<string[]>("list_memory_domains");
            return ok("Memory domains:\n  " + r.join("\n  "));
          }
    
          case "bizhawk_read8":  return ok(`${addrHex(a())}: ${fmtHex(await bh.call<number>("read8", { address: a(), ...dom() }))}`);
          case "bizhawk_read16": return ok(`${addrHex(a())}: ${fmtHex(await bh.call<number>("read16", { address: a(), ...dom() }))}`);
          case "bizhawk_read32": return ok(`${addrHex(a())}: ${fmtHex(await bh.call<number>("read32", { address: a(), ...dom() }))}`);
    
          case "bizhawk_read_range": {
            const bytes = await bh.call<number[]>("read_range", { address: a(), length: p.length, ...dom() });
            const hex = bytes.map((b) => b.toString(16).padStart(2, "0").toUpperCase()).join(" ");
            return ok(`${addrHex(a())} [${bytes.length} bytes${p.domain ? `, ${p.domain}` : ""}]:\n${hex}`);
          }
    
          case "bizhawk_write8": {
            await bh.call("write8", { address: a(), value: p.value, ...dom() });
            return ok(`Wrote ${fmtHex(p.value)} → ${addrHex(a())}${p.domain ? ` (${p.domain})` : ""}`);
          }
          case "bizhawk_write16": {
            await bh.call("write16", { address: a(), value: p.value, ...dom() });
            return ok(`Wrote ${fmtHex(p.value)} → ${addrHex(a())}${p.domain ? ` (${p.domain})` : ""}`);
          }
          case "bizhawk_write32": {
            await bh.call("write32", { address: a(), value: p.value, ...dom() });
            return ok(`Wrote ${fmtHex(p.value)} → ${addrHex(a())}${p.domain ? ` (${p.domain})` : ""}`);
          }
          case "bizhawk_write_range": {
            const r = await bh.call<{ written: number }>("write_range", { address: a(), bytes: p.bytes, ...dom() });
            return ok(`Wrote ${r.written} bytes → ${addrHex(a())}${p.domain ? ` (${p.domain})` : ""}`);
          }
    
          case "bizhawk_press_buttons": {
            await bh.call("press_buttons", { buttons: p.buttons, player: p.player ?? 1 });
            const pressed = Object.entries(p.buttons as Record<string, boolean>)
              .filter(([, v]) => v).map(([k]) => k);
            return ok(`Set joypad ${p.player ?? 1}: ${pressed.length ? pressed.join("+") : "(all released)"}`);
          }
    
          case "bizhawk_play_input_sequence": {
            const params: Record<string, unknown> = { frames: p.frames };
            if (p.screenshot_every       !== undefined) params.screenshot_every       = p.screenshot_every;
            if (p.screenshot_dir         !== undefined) params.screenshot_dir         = p.screenshot_dir;
            if (p.screenshot_prefix      !== undefined) params.screenshot_prefix      = p.screenshot_prefix;
            if (p.observe_memory         !== undefined) params.observe_memory         = p.observe_memory;
            if (p.stop_on_memory_change  !== undefined) params.stop_on_memory_change  = p.stop_on_memory_change;
            const r = await bh.call<{
              played: number;
              final_framecount?: number;
              stopped_early?: boolean;
              stop_reason?: string;
              observations?: {
                frame_offset: number;
                path?: string;
                memory?: Record<string, number>;
              }[];
            }>("play_input_sequence", params);
    
            const obs = r.observations ?? [];
            const lines = [
              `Played ${r.played} frames. Final framecount: ${r.final_framecount ?? "(unavailable)"}.`,
            ];
            if (r.stopped_early) {
              lines.push(`Stopped early — reason: ${r.stop_reason ?? "(unspecified)"}.`);
            }
            lines.push(`Captured ${obs.length} observation${obs.length === 1 ? "" : "s"}.`);
            // Per-observation lines so the agent can correlate inline images with state
            for (let i = 0; i < obs.length; i++) {
              const o = obs[i];
              const memStr = o.memory
                ? ` memory={${Object.entries(o.memory).map(([k, v]) => `${k}=${v}`).join(", ")}}`
                : "";
              const imgStr = o.path ? ` (image ${i + 1})` : "";
              lines.push(`  obs[${i}] frame_offset=${o.frame_offset}${memStr}${imgStr}`);
            }
    
            // Build the multi-content response: text summary + per-observation
            // inline image blocks. We read each PNG from disk (Lua wrote it),
            // base64-encode for MCP transport.
            const content: ({ type: "text"; text: string } | { type: "image"; data: string; mimeType: string })[] = [
              { type: "text", text: lines.join("\n") },
            ];
            const fs = await import("node:fs");
            for (const o of obs) {
              if (!o.path) continue;
              try {
                const bytes = fs.readFileSync(o.path);
                content.push({
                  type: "image",
                  data: bytes.toString("base64"),
                  mimeType: "image/png",
                });
              } catch (err) {
                content.push({
                  type: "text",
                  text: `(failed to read observation at frame ${o.frame_offset} from ${o.path}: ${(err as Error).message})`,
                });
              }
            }
            return { content };
          }
    
          case "bizhawk_pause":         await bh.call("pause");          return ok("Emulation paused");
          case "bizhawk_unpause":       await bh.call("unpause");        return ok("Emulation resumed");
          case "bizhawk_reset":         await bh.call("reset");          return ok("Core reset");
          case "bizhawk_frame_advance": {
            const f = await bh.call<number>("frame_advance", { count: p.count ?? 1 });
            return ok(`Advanced ${p.count ?? 1} frame(s). Framecount: ${f}`);
          }
    
          case "bizhawk_screenshot": {
            const path = await bh.call<string>("screenshot", { path: p.path });
            return ok(`Screenshot saved: ${path}`);
          }
    
          case "bizhawk_save_state": {
            const r = await bh.call<{ path: string }>("save_state", { path: p.path });
            return ok(`Saved state to ${r.path}`);
          }
          case "bizhawk_load_state": {
            const r = await bh.call<{ path: string }>("load_state", { path: p.path });
            return ok(`Loaded state from ${r.path}`);
          }
    
          default:
            throw new Error(`Unknown tool: ${name}`);
        }
      });
    }
  • Helper functions used by the bizhawk_read16 handler: ok() formats a success response, fmtHex() formats the numeric value as 'DEC (0xHEX)', and addrHex() formats the address as '0xXXXX'.
    function ok(text: string) {
      return { content: [{ type: "text" as const, text }] };
    }
    function fmtHex(n: unknown): string {
      if (typeof n !== "number") return String(n);
      return `${n} (0x${n.toString(16).toUpperCase()})`;
    }
    function addrHex(n: number): string {
      return `0x${n.toString(16).toUpperCase().padStart(4, "0")}`;
    }
  • The BizhawkServer.call() method used by bizhawk_read16 handler to send the 'read16' RPC command to the BizHawk Lua bridge over TCP. Enqueues a command and returns a promise that resolves with the BizHawk response.
    async call<T = unknown>(method: string, params: Record<string, unknown> = {}): Promise<T> {
      return new Promise<T>((resolve, reject) => {
        const id = this.nextId++;
        const pending: PendingCmd = {
          id,
          method,
          params,
          resolve: (r) => resolve(r as T),
          reject,
        };
    
        const timer = setTimeout(() => {
          // Drop from queue if still waiting; from inflight if already sent.
          this.queue   = this.queue.filter((p) => p.id !== id);
          this.inflight.delete(id);
          if (this.inflight.size === 0) this.awaitingResult = false;
          reject(new Error(
            `BizHawk call "${method}" timed out (${this.timeoutMs}ms) — ` +
            `is the bridge.lua script still polling?`,
          ));
        }, this.timeoutMs);
    
        // Wrap so the timer always clears
        const origResolve = pending.resolve, origReject = pending.reject;
        pending.resolve = (r) => { clearTimeout(timer); origResolve(r); };
        pending.reject  = (e) => { clearTimeout(timer); origReject(e); };
    
        this.queue.push(pending);
      });
    }
Behavior5/5

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

Despite no annotations, the description fully discloses behavioral traits: no side effects (pure read), byte ordering details (reads low byte at address, high byte at address+1, little-endian combination), and error conditions for missing domain, out-of-bounds, or missing core capability. This provides complete transparency for an agent.

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?

Structured with bold headings for clear skimming. Every sentence is relevant, though slightly wordy. Could be shortened by removing redundant examples, but remains efficient for the information conveyed.

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?

No output schema, but description specifies exact return format. Covers all essential aspects: purpose, usage, behavior, errors, and integration with sibling tools. Complete for a simple read tool with two parameters.

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

Parameters4/5

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

Input schema has 100% coverage with detailed descriptions. The description adds value by explicitly stating the little-endian interpretation (not in schema) and the independence of bus addresses (already in schema but reinforced). A small but meaningful addition beyond schema.

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?

Description clearly states verb ('Read'), resource ('unsigned 16-bit little-endian value'), and context ('from emulator memory at the given address'). Distinguishes from siblings by explicitly naming alternatives (bizhawk_read8, bizhawk_read32, bizhawk_read_range) with specific use-cases, making the tool's purpose unmistakable.

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 states when to use ('for 16-bit fields') and when not to use, listing exact sibling tools for other bit widths and endianness. Includes error conditions and domain selection guidance, leaving no ambiguity about appropriate invocation.

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

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