Skip to main content
Glama
AstralVoidZ
by AstralVoidZ

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault
PPSSPP_DFX_WS_HOSTNoPPSSPP WebSocket host127.0.0.1
PPSSPP_DFX_WS_PORTNoPPSSPP WebSocket port12345
PPSSPP_DFX_EXE_PATHNoPPSSPP executable path(from yaml)
PPSSPP_DFX_LOG_LEVELNoLog levelINFO
PPSSPP_DFX_LOG_FORMATNoLog format (text or json)text
PPSSPP_DFX_RATE_LIMITNoPer-tool rate limit (calls/min, 0 disables)60
PPSSPP_DFX_SESSIONS_PATHNoSession state path~/.ppsspp-dfx/sessions.json

Instructions

Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.

This server publishes no instructions, or was last inspected before Glama recorded them.

Capabilities

Features and capabilities supported by this server

Protocol revision2025-11-25

CapabilityDetails
tools
{
  "listChanged": false
}
prompts
{
  "listChanged": false
}
resources
{
  "subscribe": false,
  "listChanged": false
}
completions
{}
experimental
{}

Tools

Functions exposed to the LLM to take actions

NameDescription
ppsspp_analyze_logA

PURPOSE: Filter a PPSSPP log file for ERROR / WARNING / CRASH lines.

USAGE: log_path optional (defaults to the server-mirrored PPSSPP broadcast log at .ppsspp-dfx/output/ppsspp.log, written while a session runs); filter optional (keyword); session_id optional.

BEHAVIOR: READ-ONLY. Reads and filters a log file. Does not contact PPSSPP.

RETURNS: {log_path, matches: [{line_no, text}...], count, filter, filter_mode, total_matches, truncated}. truncated=true covers both an explicit limit cut and the internal 500-match cap; total_matches is a lower bound (>= the value) whenever truncated=true.

ppsspp_assembleA

PURPOSE: Assemble MIPS instruction(s) and write the resulting bytes to memory.

USAGE: session_id + address + code ('

' or ';' separated — PPSSPP assembles one line per call so the tool loops; armips-style ';' comments are NOT supported here).

BEHAVIOR: DESTRUCTIVE. Protected ranges (kernel, top.prx code) need force=true. A partial write is reported with an error directing you to disassemble and inspect.

RETURNS: {address, code, bytes_written, response, text}.
ppsspp_batch_stepA

PURPOSE: Execute an ordered automation sequence of press / wait / state_probe / screenshot steps in one call, optionally on a detached background task that outlives the client timeout.

USAGE: session_id + steps:[{type: press|wait|state_probe|screenshot, ...}]; on_failure='continue'|'abort' (default continue); background=false|true.

ROUTING: ordered multi-step automation -> here; single CPU-step -> ppsspp_step; background job survey -> ppsspp_batch_status(batch_id omitted). BEHAVIOR: STATE-CHANGE. Foreground (default) holds the session lock for the whole batch; frames are 60fps wall-clock equivalents; sequences estimated >25s are rejected up-front with BATCH_BUDGET_EXCEEDED (the MCP client aborts tool calls at ~30s, killing the remaining steps server-side). Per-step MCP progress is reported when the client requests it. background=true validates and submits instantly, returns {action:'submitted', batch_id,...}, keeps the session lock for the batch duration, and reports progress via ppsspp_batch_status. If any foreground step fails the whole call is isError BATCH_STEP_FAILED — inspect results[] per step. Screenshots are auto-skipped during replay recording.

RETURNS: foreground {total, executed, succeeded, failed, skipped, recording_mode, results[], aborted}; background {action:'submitted', batch_id, session_id, total, estimated_s, hint}.

ppsspp_batch_statusA

PURPOSE: Poll the state and progress of a background batch job without touching the session.

USAGE: batch_id from ppsspp_batch_step(background=true); omit batch_id for survey/list mode (e.g. to recover a lost batch_id or audit background activity before touching the session).

BEHAVIOR: Lock-free registry read — never opens the WS transport and never waits for the per-session lock, so it is safe to call while a background batch (or any other tool) owns the session. Executed-step count updates as steps complete; 'completed' carries the full foreground-shaped result. READ-ONLY.

RETURNS: with batch_id → {batch_id, session_id, status: queued|running|completed|failed|cancelled, executed, total, error, result, retention_jobs}; with batch_id omitted (survey) → {jobs: [{batch_id, session_id, status, executed, total, error, result_present}], retention_jobs} in submission order (finished jobs beyond retention are evicted and absent).

ppsspp_batch_cancelA

PURPOSE: Request cancellation of a queued or running background batch job.

USAGE: batch_id from ppsspp_batch_step(background=true); omit batch_id for survey/list mode (e.g. to recover a lost batch_id or audit background activity before touching the session).

BEHAVIOR: STATE-CHANGE. Cancels the detached task; the job's own finally block releases the session lock, so subsequent tool calls are free to use the session immediately. The abort happens at the current step boundary (a press finishes, a mid-wait cuts within ~1s). Cancelling an already-finished job is an error — check ppsspp_batch_status first if unsure.

RETURNS: {batch_id, status, note} — poll ppsspp_batch_status for the terminal state.

ppsspp_breakpointA

PURPOSE: Manage breakpoints AND consume their hits — set/remove/update/list CPU execution breakpoints and memory watchpoints, strict-wait for a hit, or one-call arm-hit-capture-resume tracing.

USAGE: session_id optional when exactly one session is active; management actions as below; action='wait' blocks until any breakpoint is hit (set/mem_set first; lock-free; breakpoint stays armed); action='trace' arms a temporary MEMORY breakpoint at address, waits, captures pc/registers/backtrace, always removes it and resumes (defaults to read access; narrow with read/write/size). For EXECUTION breakpoints use action='set' + 'wait'.

ROUTING: persistent breakpoint management -> here; one-shot strict-wait -> action='wait'; armed hit-capture -> action='trace'. BEHAVIOR: MUTATING. trace arms/removes and set/mem_* manage state; Reliable hits need CPUCore=2 (IR Interpreter). mem_remove resolves the watchpoint's real size via mem_list first (address+size matching); mem_update merges existing read/write/change unconditionally (PPSSPP zero-omits omitted bools). CPU set/remove return no data — the tool follows with a list for verification. wait/trace are lock-free during the wait itself (concurrent reads keep working); do NOT submit step/pause/resume during a wait. CONDITION SEMANTICS: PPSSPP's IR mode ignores register conditions, so any condition is enforced MCP-side — the breakpoint is armed unconditionally and each hit's expression is evaluated with cpu.evaluate; a falsy hit is auto-resumed (not surfaced) and counted in filtered_hits.

RETURNS: stats → {mode:"stats", window_s, total_hits, by_pc: [{pc, count, first_seen, last_seen}]} (fixed ~30s sampling window — no shorter-window option, probe_changes?: [{probe, old, new, ts}], note}; management actions → {action, address, enabled, breakpoints[]}; wait → {hit, already_paused, timeout_s, pc, reason, related_address, ticks, condition, condition_filtered, filtered_hits, storm_break}; trace → {hit, already_paused, address, access, timeout_s, hits: [{pc, related_address, reason, ticks, mem_hits?, registers?, backtrace?}], bp_removed, resumed, note}.

ppsspp_contextA

PURPOSE: One-call crash-triage pack — identity + disassembly window + optional backtrace for an address.

USAGE: pass a crash PC or call target; identity resolves via addresses.yaml known_functions (IDA offset applied); disasm covers window instructions before/after; include_backtrace=true adds the call stack (pauses the CPU briefly).

BEHAVIOR: READ-ONLY. Unknown addresses return identity=null and the raw window instead of failing; backtrace is skipped (not an error) when the CPU is running — the note field says why. SCOPE: identity/region come from a TOPX-specific address knowledge base (addresses.yaml). For any other ISO they come back empty and note says so explicitly -- an empty identity means 'not in the tables', NOT 'bad address'.

ROUTING: persistent breakpoints around this address -> ppsspp_breakpoint; one armed hit-capture -> ppsspp_breakpoint(action="trace"); recurring sampling -> ppsspp_state_observer.

RETURNS: {address, identity: {name, start, offset} | null, region, disasm: [{address, text}], backtrace: [...], backtrace_note}.

ppsspp_diff_memoryA

PURPOSE: Snapshot a memory range and diff it against current memory — the classic "what changed?" variable-locator.

USAGE: diff_memory(action="snapshot", start=..., end=...) → handle; act in game; diff_memory(action="compare", handle=...) → changed-byte list; action="drop"/"list" manage handles.

BEHAVIOR: READ-ONLY. Memory is never written — only the per-server snapshot registry mutates. Large ranges are read across multiple reads; per-snapshot cap 8 MiB; registry cap 4 with FIFO eviction. The registry is process-global: parallel sessions share one cap and FIFO order, so another session's snapshots can evict yours under load. compare requires the handle's exact range.

RETURNS: snapshot → {handle, start, size_bytes, checksum}; compare → {handle, start, size_bytes, changed_count, truncated, changes: [{address, old, new}]}; drop → {handle, dropped}; list → {handles: [...], count, max_snapshots}.

ppsspp_evaluateA

PURPOSE: Evaluate a debugger expression (register names, hex literals, simple arithmetic).

USAGE: session_id + expression. No '*addr' dereference syntax — read memory with read_u32 instead.

BEHAVIOR: READ-ONLY. Pauses/resumes the CPU internally.

RETURNS: {expression, value, response, text}.

ppsspp_gpu_recordA

PURPOSE: Capture the next rendered frame's GE command stream as a binary dump file.

USAGE: session_id. The CPU must be RUNNING — a paused GPU never flips a frame; the MCP pre-probe converts that into a clean CPU_STATE_ERROR.

BEHAVIOR: READ-ONLY. Captures to a binary file under output/gpu_dumps/ (not JSON).

RETURNS: {size_bytes, file_path, raw, text}.

ppsspp_gpu_statsA

PURPOSE: Query GPU counters — fps, vblanks per second, timing info.

USAGE: session_id. The CPU must be RUNNING; paused, the MCP pre-probe returns CPU_STATE_ERROR instead of hanging — which doubles as the cheapest paused-CPU probe.

BEHAVIOR: READ-ONLY.

ON TIMEOUT: the error names WHY it timed out -- expected_stall (the CPU was stepping, so no frame is coming), pairing_broken (a broadcast arrived meanwhile, so ticket pairing failed), or no_producer (nothing was broadcast at all, so the emulator is not producing frames -- check for a modal dialog blocking it). A CPU_FREEZE_SUSPECTED is re-checked against the frame heartbeat first: if no frames are arriving it is reported as WS_TIMEOUT with a no-producer attribution, NOT as a CPU freeze.

RETURNS: {fps, vblanks_per_second, info, timing, raw, text}.

ppsspp_press_buttonA

PURPOSE: Simulate a single PSP button press for a duration.

USAGE: session_id + button required; duration optional (default 1 frame). Valid button names: cross / circle / triangle / square / up / down / left / right / start / select / ltrigger / rtrigger.

BEHAVIOR: STATE-CHANGE. Sends input events to PPSSPP. Button state returns to released after the duration elapses.

RETURNS: {button, duration}.

ppsspp_hold_buttonsA

PURPOSE: Hold a combination of PSP buttons until a subsequent call changes the state.

USAGE: session_id + buttons required (pipe-separated combination, e.g. 'cross|circle'). Valid names: cross / circle / triangle / square / up / down / left / right / start / select / ltrigger / rtrigger.

BEHAVIOR: STATE-CHANGE. Sets the button-held state in PPSSPP; it persists until the next hold_buttons call. To release, call hold_buttons with buttons='' (every button is then sent as released). Note: send_analog does NOT release buttons -- it drives the analog axes on a separate PPSSPP event.

RETURNS: {buttons}.

ppsspp_send_analogA

PURPOSE: Send an analog stick position (x, y in [0, 255], 128 = center).

USAGE: session_id + x + y required. 0 = full left / up, 255 = full right / down.

BEHAVIOR: STATE-CHANGE. Sets analog stick position; persists until next send_analog call.

RETURNS: {x, y}.

ppsspp_wait_framesB

PURPOSE: Wait N frames (wall-clock sleep at 60 FPS by default) to let the emulator advance.

USAGE: session_id + frames required; interval optional (default 1/60 s).

BEHAVIOR: STATE-CHANGE. Sleeps the caller; emulator advances N frames. Session must be alive (validated before sleep).

RETURNS: {frames, elapsed_s}.

ppsspp_healthA

PURPOSE: Probe MCP server liveness and readiness — plus an optional four-point session health battery.

USAGE: no args for the server-level probe (does NOT contact PPSSPP); pass session_id to also run the session battery (iso_loaded / cpu_running / ws_connected / game_mode_valid — absorbed from the former ppsspp_smoke_test tool).

BEHAVIOR: READ-ONLY. Server counters are read in-memory; the session battery (when requested) contacts PPSSPP over the session transport but never mutates state.

READING session_checks: each entry carries value_status besides passed -- 'ok' (the probe really read), 'stale_address_suspected' (the read succeeded and returned zero on several consecutive readings, so the probe address may have drifted -- a suspicion, not a verdict), 'failed' (the read raised or the data was absent; no value is reported), 'not_configured' (no probe address, so nothing was read). A probe that READ ZERO and one that COULD NOT READ both show passed=false while meaning opposite things: the first is a fact about the game, the second about the tooling. Do not read passed=false alone as a finding about the emulated game.

RETURNS: Dict with status ('ok'/'degraded'), version, python_version, pydantic_version, uptime_s, tool_count, session_count — plus session_checks: [{name, passed, detail, value_status, value?}] and overall_session_status when session_id is provided.

ppsspp_list_addressesA

PURPOSE: List the project's known address constants from addresses.yaml — the single source of truth; never guess hex addresses.

USAGE: optional section filter; an unknown section returns an error listing the valid ones.

CONVERSION: IDA <-> PPSSPP address conversion is plain arithmetic — ppsspp_addr = ida_addr + (top_base.ppsspp - top_base.ida) (defaults 0x08804000 - 0x00000000). ppsspp_convert_address was un-tooled in v0.1.6. BEHAVIOR: READ-ONLY. Int values ≥0x1000 are returned as hex strings that can be pasted straight into address parameters.

RETURNS: {sections, count, section_filter}.

ppsspp_read_memoryA

PURPOSE: Read memory (read_bytes / read_u32 / read_string).

USAGE: action; session_id optional when exactly one session is active; address as '0x' hex string; read_bytes ≤65536 per call (split larger reads); Memory scanning has moved to ppsspp_scan.

BEHAVIOR: READ-ONLY. read_string is ASCII-only (use read_bytes + Shift-JIS decode for game text). Reading code segments: use ppsspp_disassemble — MCP provides no IR-encoding detection (a read_u32 over JIT-IR bytes just returns the raw value).

RETURNS: {action, address, value, size, text, file} — read_bytes has output=value (default; byte list + hex text) / hex (text only, value=null) / file (paths + 64-byte preview; payload saved under .ppsspp-dfx/output/memory_reads/).

ppsspp_write_memoryA

PURPOSE: Write u8/u16/u32 or raw bytes to memory.

USAGE: session_id + address ('0x' hex) + data + format ('u8'|'u16'|'u32'|'bytes'; bytes accepts hex or base64).

BEHAVIOR: DESTRUCTIVE. Protected ranges (kernel, top.prx code) need force=true (PROTECTED_ADDRESS).

RETURNS: {address, format, bytes_written, value, text}.

ppsspp_disassembleA

PURPOSE: Disassemble N MIPS instructions at a given address.

USAGE: address required; session_id optional when exactly one session is active; count optional (default 10).

BEHAVIOR: READ-ONLY. Calls memory.disasm via WebSocket. Does not modify memory or CPU state.

RETURNS: {address, count, instructions: [{address, text}...]}.

ppsspp_memory_mapB

PURPOSE: Get the PPSSPP memory region map (user / kernel / VRAM ranges).

USAGE: session_id.

BEHAVIOR: READ-ONLY.

RETURNS: {ranges[], mapping, text}.

ppsspp_queryA

PURPOSE: Aggregate game-state queries — game_state, registers (all or one), backtrace, threads, modules, and function-list management (funcs/func_scan/func_add/func_remove).

USAGE: action + session_id; 'register' needs name; func_scan/func_remove need address; top_n defaults to 100 (pass 0 for the full list — hle.func.list can reach 700+KB).

ROUTING: one-shot PC read -> query(action='register', name='pc') (safe=true pauses for consistency; safe=false for hot-path polling); pause+capture -> ppsspp_frame_snapshot; recurring named probes -> ppsspp_state_observer; game_state / backtrace / threads / modules / HLE func management also here. BEHAVIOR: READ-ONLY. Lookups only — func_add/func_remove mutate the debugger function list. Verified on a live game: threads / modules / funcs / func_scan respond while the CPU is RUNNING (no pause needed); running-state PC/isCurrent reads are LOW trust unless safe=true (which pauses briefly for a consistent, high-trust read).

RETURNS: {action, data, trust_level} — data shape depends on the action.

ppsspp_replayA

PURPOSE: Aggregate PPSSPP replay subsystem — record input sequences, execute them, and save/load .ppr recordings.

USAGE: session_id optional when exactly one session is active; actions: begin/abort/flush/execute/status/time_get/time_set/save/load/wait_complete; execute needs version + base64_input; time_set needs value; save/load take a bare file name (always under output/replays/).

BEHAVIOR: STATE-CHANGE. Recording requires the CPU RUNNING (real input timing); screenshots are rejected while recording. Replay timelines use ABSOLUTE game-clock timestamps anchored at the RECORDING session's boot — a replay only injects correctly when a fresh boot's clock is aligned to them: execute/load ONLY loads the event table and returns t0_s / estimated_end_s + the boot-aligned sequence (reset -> wait_ready -> wait boot+estimated_end_s -> abort); it does NOT play by itself. executing/saving NEVER clear on their own — only abort clears them — so wait_complete times out on any un-aborted replay; completion = the timeline estimate + explicit abort. execute/load auto-abort a live executing/saving state first. restore_rtc defaults to False: setting it rewinds the game-visible wall clock of the RUNNING session and pollutes every in-game timer; when needed, set it before the boot-aligned reset.

RETURNS: {action, executing, saving, version, size, base64, base_rtc, data} — execute/load data carries t0_s, estimated_end_s, event_count and boot_aligned_sequence; fields depend on the action.

ppsspp_scanA

PURPOSE: Three-mode memory scanner — byte-pattern search, Cheat-Engine-style value scan with narrowing sessions, and charset-aware string harvesting.

USAGE: mode='pattern' + pattern + start_addr/end_addr (migrated from read_memory scan); mode='value' + phase='initial'/value/width → handle, then phase='narrow'/op/value to converge, 'list'/'drop' to manage; mode='strings' + charset + start_addr/end_addr → [{address, text}]. background=true submits a detached job instead (recommended for full-band scans) and returns {action:'submitted', batch_id, ...} — poll ppsspp_batch_status(batch_id=...).

BEHAVIOR: READ-ONLY. Large ranges are read across multiple reads; unreadable regions are skipped per-chunk (one WS round-trip each), but CONSECUTIVE read timeouts (10 s each, >5 in a row) abort the scan — a wedged PPSSPP fails the scan cleanly instead of pinning the session lock. pattern/strings ranges over 2 MiB are AUTO-BACKGROUNDED (returns {action:'submitted', batch_id, ...} even with background=false) — measured: 24 MB @ 4 KiB chunks takes 53-96 s depending on PPSSPP build, always past the ~30s client timeout, while @ 64 KiB chunks it is 3.4-40 s (build-dependent). Value sessions live in a bounded per-server registry (cap 4, FIFO), are bound to the creating session, and the initial-scan cap is 8 MiB foreground / 32 MiB background. Background scans carry a 600 s wall-clock budget; exceeding it fails the job and releases the session. The registry is process-global: parallel sessions share one cap and FIFO order, so another session's scans can evict your handle under load.

ROUTING: what-changed-between-two-points -> ppsspp_diff_memory (snapshots); who-accesses-this-address -> ppsspp_breakpoint(action='trace'); value candidates with known addresses -> read_memory directly.

RETURNS: pattern → {action, address, value: [matches], size}; value initial → {scan_handle, width, candidates, passes}; value narrow → {scan_handle, candidates, passes}; value list → {scan_handle, addresses: [...]}; value drop → {scan_handle, dropped}; strings → {charset, count, strings: [{address, text}]}; background submission (explicit background=true OR pattern/strings range > 2 MiB) → {action: 'submitted', batch_id, session_id, estimated_s}.

ppsspp_screenshotA

PURPOSE: Capture the framebuffer as an image (ImageContent) plus metadata.

USAGE: session_id optional when exactly one session is active; source='render' (default; empty frames auto-fall back to VRAM — colors unreliable there) or 'output' (CRASH-RISK, do not use); mutually exclusive with the deprecated mode param.

BEHAVIOR: READ-ONLY. An empty capture returns empty=true instead of an error — advance to a rendered scene and retry.

RETURNS: structuredContent metadata (mode/source/size_bytes/width/height/file_path/format/empty); the image itself arrives as an ImageContent block. The auto-saved PNG/JPG path is in file_path.

ppsspp_dumpA

PURPOSE: Dump the currently-bound GPU texture OR CLUT palette as an image plus metadata.

USAGE: kind='texture' → the bound texture (level selects mipmap; PPSSPP captures the currently-bound texture — it does NOT support capture by VRAM address); kind='clut' → the bound palette (level must be 0).

BEHAVIOR: READ-ONLY. An empty capture raises CAPTURE_EMPTY — enter a scene that renders (texture) or uses the palette (clut) and retry.

RETURNS: structuredContent metadata (kind/level/file_path/size_bytes/format); the image itself arrives as an ImageContent block.

ppsspp_list_scriptsA

PURPOSE: List diagnostic scripts declared in .ppsspp-dfx/config/scripts.manifest.yaml.

USAGE: category optional filter (eboot / state / p0ab / ndx / memory / misc / recipe).

BEHAVIOR: READ-ONLY. Reads the in-memory manifest registry (loaded at startup). Does not execute any script.

RETURNS: {scripts: [ScriptEntryView...], count, category}.

ppsspp_run_scriptA

PURPOSE: Invoke a manifest-registered diagnostic script by name with validated input.

USAGE: name (see ppsspp_list_scripts; skeleton scripts return not_implemented); input dict validated against the script's Pydantic model; session_id required when the script declares requires_ppsspp (missing → SESSION_NOT_FOUND).

BEHAVIOR: STATE-CHANGE. Runs manifest-registered script code. Unknown names → SCRIPT_NOT_FOUND.

RETURNS: {name, output, output_model}.

ppsspp_reload_scriptsA

PURPOSE: Manually reload the script manifest YAML and clear the script module cache.

USAGE: No parameters.

BEHAVIOR: MUTATING. Re-reads the manifest file and invalidates cached script modules. Reversible: re-reading an unchanged file produces an equal registry. When the exposed tool set ACTUALLY changes (tools added or removed), the server notifies the client with a tool-list-changed notification so cached tools/list results are invalidated; a no-op reload sends nothing.

RETURNS: {reloaded_count, exposed_count, manifest_path, scripts: [ScriptEntryView...]}.

ppsspp_search_disasmA

PURPOSE: Loop-search disassembly for a substring, collecting matching instructions with context.

USAGE: session_id + match (a leading '$' is stripped); start address; end=0 wraps the search around the whole region; max_results default 100.

BEHAVIOR: READ-ONLY.

RETURNS: {address, match, end, results[{address, text, name, params}], text}.

ppsspp_search_memory_infoA

PURPOSE: Search PPSSPP's memory-tracking metadata for allocation/texture tags matching a string.

USAGE: session_id + match (case-insensitive substring, required); optional address/end/type filters.

BEHAVIOR: READ-ONLY. Returns a single extent per matching tag.

RETURNS: {regions[], count, raw, text}.

ppsspp_sessionA

PURPOSE: Start / stop / inspect PPSSPP debug sessions — action=list / start / stop / get / wait_ready; wait_ready blocks until the emulated CPU is up.

USAGE: action='list' takes no other params and returns {sessions, count} (idle sessions >30min are auto-GC'd as a side effect; NOT a per-session health probe — use ppsspp_health(session_id=...) for that); action='start' needs iso_path (pass wait_ready=true to block until the CPU is up in the same call); stop/get/wait_ready need session_id. Call wait_ready AFTER start and BEFORE any memory tool — PPSSPP answers WebSocket before the CPU boots. start(resilient=true) self-heals boot wedges (blacklist quarantine + relaunch with the same session_id, ≤2 retries).

BEHAVIOR: STATE-CHANGE. start spawns a PPSSPP subprocess + WS debugger; stop terminates it (never taskkill the process yourself); wait_ready polls the probe lock-free and fails [BOOT_TIMEOUT] on wedge suspicion; list/get are read-only.

RETURNS: action=start/get → SessionResponse {session_id, iso_path, pid, ws_url, created_at, last_active_at, exec_count, ws_connected, recovered, restored (1 = session record was restored from sessions.json after a server restart, 0 = created in this process), ppsspp_version}; action=wait_ready → {action, ready, elapsed_s, probe_addr, probe_value, note} — elapsed_s is THIS call's wait duration, not the start→ready total; action=list → {sessions: [SessionResponse...], count}.

ppsspp_state_observerA

PURPOSE: Named memory-probe registry plus running-state observation — register probes once, then sample them cheaply every loop.

USAGE: action + session_id for observe; register needs name + address (+size 1/2/4, description); observe takes comma-separated names and samples.

ROUTING: recurring sampled probes across loops -> here; one-shot paused snapshot -> ppsspp_frame_snapshot; single-address access watch -> ppsspp_breakpoint(action="trace"). BEHAVIOR: STATE-CHANGE. register/clear mutate the registry; observe is reliable while RUNNING. The registry is PER-SESSION (keyed by session_id), seeded from addresses.yaml state_probes; clear removes user-registered probes only, so the configured baseline survives. Delete semantics are IDEMPOTENT: clearing an unknown probe name succeeds (ok), unlike ppsspp_breakpoint mem_remove which rejects missing targets.

RETURNS: {registered|probes|observations, count, success_count, failure_count} — shape depends on the action.

ppsspp_stepA

PURPOSE: Aggregate CPU run-state control (pause / resume / reset / run_until / next_hle).

USAGE: action='pause' / 'resume' / 'reset' / 'next_hle' take only session_id (optional when exactly one session is active); 'run_until' requires address.

NOTE (v0.1.6): single-stepping (into/over/out) moved to ppsspp_batch_step's cpu_step step type — this tool no longer accepts those actions.

ROUTING: single run-state operations -> here (run_until for run-to-address); multi-step press/wait/probe sequences and cpu_step -> ppsspp_batch_step. BEHAVIOR: STATE-CHANGE. Advances or changes CPU run state. 'reset' reboots the game (lost in-memory state). 'run_until' sets a temp breakpoint and resumes.

RETURNS: {action, address, pc, ticks, reason, related_address}. pc is stepping-verified (HIGH trust) for 'pause'; for 'resume' pc/ticks are a best-effort LOW-trust cpu.status snapshot of the running CPU (0/0.0 if that read failed); 'reset' reports 0.

ppsspp_watch_valueA

PURPOSE: Value-change watch on an address — zero-pause alternative to a read watchpoint.

USAGE: session_id + address + mode + interval. Polls the value every interval_frames and records changes (old/new/frame/time). Pure reads: the CPU is never paused, so hot addresses are safe (storm-free).

BEHAVIOR: READ-ONLY. Polling loop; never mutates state; blocks ~duration_frames/60 seconds (cap 18000 frames).

RETURNS: {address, size, mode, samples, first_value, last_value, changes: [{frame, t_s, old, new}], change_count}.

ppsspp_frame_snapshotA

PURPOSE: One-call paused scene snapshot — pause (unless already paused), capture pc + registers + optional named probes, then resume.

USAGE: session_id; probes = optional comma-separated state_observer registry names; want_registers default true. Prefer this over a manual pause + query(registers) + resume sequence.

ROUTING: pause+capture+resume in one call -> here; cheap PC-only check -> ppsspp_query(action='register', name='pc', safe=true); recurring sampled probes -> ppsspp_state_observer. BEHAVIOR: STATE-CHANGE. The session lock is held for the whole call (pause→capture→resume is short). A CPU we paused is resumed before returning; an already-paused CPU stays paused. A failing capture never leaves the game frozen.

RETURNS: {was_stepping, resumed, pc, trust_level, registers, probes} — registers/probes keys are ALWAYS present; they carry null when opted out (want_registers=false / probes omitted) — nullable-key contract, 2026-09-08.

ppsspp_write_registerA

PURPOSE: Set a CPU register (GPR/FPU/VFPU names, plus pc/hi/lo).

USAGE: session_id + name (MIPS ABI names preferred; numeric aliases like 'r5' normalize to the GPR of that index, i.e. r5 -> a1) + value (hex).

BEHAVIOR: DESTRUCTIVE. Pauses and resumes the CPU automatically (REQUIRED_STEPPING handled internally) — no manual pause needed.

RETURNS: {name, value, response, text}.

Prompts

Interactive templates invoked by user choice

NameDescription
memory-breakpoint-wizardGuide the Agent through setting up a memory read/write breakpoint in PPSSPP: verify prerequisites, set the breakpoint via ppsspp_breakpoint(mem_set), resume execution, and verify the hit.
memory-trace-wizardGuide the Agent through tracing 'what code reads/writes this address' in PPSSPP: prefer the one-call ppsspp_breakpoint(action='trace'), fall back to the manual ppsspp_breakpoint(set) + ppsspp_breakpoint(action='wait') protocol, and interpret the hit scene (PC sits AFTER the access instruction).

Resources

Contextual data attached and managed by the client

NameDescription
game-stateREAD-ONLY snapshot of the single active PPSSPP session's game status (running/paused state and game title via the game.status event). Requires exactly one active session.
registersREAD-ONLY snapshot of all CPU registers (GPR + FPU + VFPU via cpu.getAllRegs) for the single active PPSSPP session. Requires exactly one active session.

TDQS

A3.9/5.0

Scored across 37 tools

Disambiguation4/5

The tool set has several clusters that a naive agent could confuse (read_memory / query / state_observer / frame_snapshot / watch_value for state reads; step / batch_step for stepping; watch_value / breakpoint(trace) / state_observer for observing), but nearly every description carries an explicit ROUTING block that names the intended tool for each case, which sharply reduces misselection. Boundaries remain slightly blurred by design given the mega-tools (breakpoint, query, scan, replay) that fold many actions into one.

Naming Consistency5/5

Every tool uses the identical ppsspp_ snake_case prefix and mostly a verb_noun shape (read_memory, write_register, press_button, hold_buttons, batch_step), with a few noun-named aggregators (context, health, session, replay). The convention is uniform and predictable throughout.

Tool Count3/5

37 tools is on the heavy side for a single MCP server and several obvious groupings (batch_step/batch_status/batch_cancel, list_scripts/run_script/reload_scripts) could plausibly be folded together. The domain genuinely spans sessions, memory, breakpoints, GPU, input, replay, and scripting, so the breadth is partly earned, but it sits at the upper edge of comfortable.

Completeness5/5

The surface covers the full debugging lifecycle end to end: session start/stop/wait, memory read/write/scan/diff, disassembly and search, breakpoints and value watches, register/PC evaluation, GPU stats/dump/record, screenshots, input, replay, batching, and script management. There are no obvious dead ends for the stated PPSSPP-debugging purpose.

Maintenance

ActivityMaintained
ResponsivenessNo issues