MCP server exposing a DOS emulator (js-dos) to AI agents — drive, observe, and inspect DOS programs.
This is a well-designed, domain-specific MCP server for DOS emulation with 21 tools covering emulator control, input/interaction, observation, filesystem operations, and memory inspection. Strengths: nearly all tools have substantive, action-oriented descriptions (most 80-200 chars); comprehensive parameter schemas with proper types, enums, and descriptions; thoughtful domain-specific details (e.g., move_mouse_relative explanation of INT 33h relative vs absolute deltas); good error handling guidance (e.g., read_memory throws on truncation with explanation). Weaknesses: a few parameter descriptions are terse (e.g., 'path to file'); no documented output schemas for tools (what fields do they return?); no error recovery guidance for agent-facing failures; no idempotency hints; missing confirmation steps on destructive operations (fs_delete). Tool naming is excellent (verb_noun throughout); composition is single-purpose and chainable. The server handles secrets safely (no API keys exposed). Overall, a solid B+ implementation that would benefit from output schema documentation and recovery guidance to reach A-grade.
Press and release a mouse button at the cursor's current position, without moving first. send_click moves to its coordinates before clicking, which discards a position established with move_mouse_relative. The button is held down for hold_ms (default 120) because the guest polls the mouse and can miss a press and release delivered in the same instant; raise it for a game that polls slowly.
Delete a file or empty dir from the virtual DOS FS.
List a directory in the virtual DOS FS.
Recursively copy a virtual DOS FS subtree to a host directory.
Recursively copy a host directory into the virtual DOS FS.
Read a file from the virtual DOS FS.
Output schemas not documented. Tools return results but LLMs cannot see the response structure in advance. For example, screenshot returns { bytes: Buffer; mime: string }, but this is not visible in the tool definition. This forces LLMs to guess which fields are available and risks failed downstream chaining.
Destructive operations (fs_delete) lack confirmation or dry-run support. An LLM could irreversibly delete files without a safety net. Consider adding a dry_run parameter or requiring explicit confirmation.
Limited error recovery guidance in descriptions. Descriptions do not tell agents what to do on failure (e.g., 'If load_bundle fails with invalid format, try unzipping it first' or 'If fs_read fails, check permissions with fs_stat'). Agents have no signal for retryable vs permanent failures.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-22 | C | 61 | 2026-07-28+ | v2 |
Stat a single path in the virtual DOS FS. Cheaper than fs_list when you only need to check existence, size, or whether a path is a directory. Returns null if the path does not exist.
Flush any pending mirrored writes to their host directories.
Write a file to the virtual DOS FS.
Get the current status of the DOS emulator.
Load a DOS bundle (directory, .zip, or .jsdos file) into the emulator and initialize a session.
Move the mouse without clicking.
Move the mouse by a relative delta instead of to an absolute position. Prefer move_mouse unless the guest needs this. It is for games that track the cursor themselves from INT 33h relative deltas rather than reading the absolute position (Ultima Underworld does); for those, an absolute move is only ever seen as the delta it implies and so cannot place the cursor. A workable strategy is a large negative delta to clamp into a corner, then one delta to the target, then click_at_cursor so the position is not disturbed. Deltas are NOT guaranteed 1:1 with screen pixels, since the guest and DOSBox each apply their own sensitivity, and the corner strategy relies on the guest clamping, so verify against a screenshot rather than assuming. Avoid interleaving with move_mouse or send_click without deliberately re-establishing position: the absolute path moves the host pointer and this one does not, so the two notions of cursor position diverge.
Read the guest's emulated RAM by DOS physical address. Real-mode physical addressing: a `seg:off` pair from a disassembly is `seg * 16 + off`. Throws rather than returning a short buffer when the range runs past the end of memory, because a silent truncation reads as valid data downstream.
Take a screenshot of the DOS emulator canvas.
Scan the guest's emulated RAM for a byte pattern, returning physical addresses. The scan runs next to the memory rather than shipping it to the caller, which is the only practical way to locate a segment whose base is not known in advance.
Click at canvas-relative coordinates.
Inject a named key sequence (e.g. ['Ctrl+F5', 'Escape', 'ArrowUp']).
Type literal text as keystrokes, one character at a time (e.g. 'DIR\n'). There is no token or escape syntax: every character is sent as itself, so a key name wrapped in braces is typed as those characters instead of pressing that key. '\n' is Enter and '\t' is Tab; use send_key_sequence for any other named key, such as a function key or an arrow, and for a modifier combination.
Shut down the emulator and close the current session.
Wait for a specified duration in milliseconds.
Filesystem operation descriptions are generic. 'Read a file from the virtual DOS FS' provides minimal context on format, encoding, or when to use fs_read vs fs_pull_dir. add_context like 'Returns file contents as raw bytes; for large binary files, consider using fs_pull_dir instead.'
get_status description is too vague ('Get the current status of the DOS emulator'). Does it return CPU state? Memory usage? Running process? Paused state? LLMs cannot determine what this tool returns without implementation inspection.
No idempotency hints. send_keys, send_click, and other input tools may not be idempotent if called twice (second call repeats the keystrokes). Descriptions should state 'Not idempotent: calling twice will input twice' or document the retry safety.
fs_sync lacks clarity. Description is 'Flush any pending mirrored writes to their host directories.' but LLMs do not know what 'mirrored writes' means without reading the load_bundle mirror parameter documentation. Add context: 'If you set up directory mirrors in load_bundle, call this to flush pending changes from the DOS FS back to the host.'