Can It Run DOOM? Playing DOOM in Claude Code with doom-mcp

DOOM runs on everything. Calculators, ATMs, pregnancy tests, an oscilloscope you bought at a garage sale. It was only a matter of time before AI coding assistants joined the list. Now they have.

doom-mcp is an MCP server that embeds the real DOOM engine and exposes it to Claude Code, Kiro, Cursor, or any MCP client through three tools. The AI sees the game, fights demons, navigates levels, and reports what’s happening. You can direct it or let it play on its own.

> Let's play DOOM!

  Starting E1M1 on medium difficulty...

  HP:100 Armor:0 | Pistol (key:2) | Ammo: 50b 0s 0r 0c | Kills:0 | (-416,256) 0deg | E1M1
  No enemies in sight. Exploring the corridor ahead.

> Go forward and find some enemies

  Sprinting down the hallway... I can see an opening ahead.

  HP:100 Armor:0 | Pistol (key:2) | Ammo: 50b 0s 0r 0c | Kills:0 | (0,256) 0deg | E1M1
  ENEMIES IN SIGHT (aim for angle~0 then fire): Imp (HP:60) 30deg LEFT(turn_left ~9 ticks) dist:400

  There's an Imp to the left! Turning to face it...

> Shoot it!

  Aimed and firing!

  HP:100 Armor:0 | Pistol (key:2) | Ammo: 47b 0s 0r 0c | Kills:0 | (0,256) 30deg | E1M1
  ENEMIES IN SIGHT (aim for angle~0 then fire): Imp (HP:15) AHEAD dist:350

  Hit! The Imp is wounded (HP:15). One more burst should finish it.

  HP:95 Armor:0 | Pistol (key:2) | Ammo: 44b 0s 0r 0c | Kills:1 | (0,256) 30deg | E1M1
  ITEMS: Stimpack (+10 HP) 12deg RIGHT(turn_right ~4 ticks) dist:48 (~3 ticks fwd+run to reach)
  1 kill! Want to see a screenshot?

Getting Started

Register with Claude Code

claude mcp add doom -- npx -y doom-mcp

For Kiro, Cursor, Windsurf, or any other MCP client, add to .mcp.json:

{
  "mcpServers": {
    "doom": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "doom-mcp"]
    }
  }
}

Play

Open a new session and say: “Let’s play DOOM”

The AI will ask which mode you want, start on E1M1, and begin.

Play Modes

Two ways to play:

User-directed: You give commands (“go forward”, “open that door”, “shoot the imp”). The AI executes one action at a time and describes what happens. Good for a text-adventure feel where you call the shots and the AI handles the execution.

Autonomous: The AI makes all decisions: movement, combat, exploration. You watch and intervene if you want. It’s genuinely entertaining to watch it work through a level, spot an Imp, and decide whether to charge or take cover.

WAD Files

doom-mcp ships with Freedoom out of the box. Freedoom is a free and open-source replacement IWAD (DOOM’s game data format) with its own levels and enemy designs. If you want the original id Software levels, enemies, and atmosphere, the shareware DOOM1.WAD is free to download legally. Set the path in your MCP config:

{
  "mcpServers": {
    "doom": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "doom-mcp"],
      "env": {
        "DOOM_WAD_PATH": "/path/to/DOOM1.WAD"
      }
    }
  }
}

If you own DOOM or DOOM 2, those WADs work the same way.

A Few Things I Learned Building This

FFI over subprocess

The obvious approach is to run DOOM as a child process and communicate via pipes. The problem is timing and synchronization: you’re fighting the engine’s internal clock, process startup overhead, and serialization on every frame.

Instead, doom-mcp embeds doomgeneric (a portable C implementation of the DOOM engine) directly via Rust FFI (Foreign Function Interface). Rust was the right choice here: it has excellent FFI support for C code, compiles to a single native binary, and gives memory safety without a garbage collector that could interrupt the game loop. No subprocess spawning, no pipes. Each tool call advances the engine by calling doomgeneric_Tick() directly and reading the frame buffer in-memory.

Virtual time

DOOM normally ties its game clock to wall time. That’s fine for a real-time player, but it’s wrong for an AI that might take 500ms to decide its next move. Without intervention, the engine would skip ticks during the AI’s thinking time and produce non-deterministic behavior.

The solution is to decouple the engine’s clock from wall time entirely. Each doomgeneric_Tick() call advances exactly one game tic (1/35th of a second) regardless of how much real time has passed. Gameplay is fully deterministic: the same sequence of actions always produces the same result.

Line-of-sight, not wallhack

Enemy detection could just iterate the object list and report everything on the map. That would be cheating in a way that makes the game too easy and less interesting.

Instead, the server performs a proper line-of-sight check for each enemy, the same check the DOOM engine uses internally (P_CheckSight()). The AI only sees enemies it could see if it were a human looking at the screen. When an enemy moves behind a wall or around a corner, it drops from the AI’s view immediately. It still needs to explore to find things.

What the AI gets per action

Each doom_action call returns structured game state alongside a small PNG:

  • HP, armor, ammo by type, current weapon, kill count
  • Visible enemies with direction (degrees), distance, and HP
  • Nearby items within pickup range
  • A 160×100 thumbnail PNG for inference

The structured data gives the AI something it can reason about without having to interpret pixel-level vision. The image fills in the spatial context. Together they let the AI make reasonable decisions: “Imp at 30 degrees left, distance 400, HP 60. Turn left and fire.”

Tools Reference

doom_start

Starts or restarts a game. Safe to call at any time.

Parameter Type Default Description
skill int 1-5 3 1=baby, 2=easy, 3=medium, 4=hard, 5=nightmare
episode int 1-4 1 Episode number
map int 1-9 1 Map number

doom_action

Advances the game by executing actions for a number of ticks.

Parameter Type Required Description
actions string yes Comma-separated: forward, backward, turn_left, turn_right, strafe_left, strafe_right, fire, use, run, 17
ticks int 1-105 no Ticks to advance. Default 7. 7 ticks ≈ 0.2s, 35 ticks ≈ 1s

Weapon keys: 1=fists, 2=pistol, 3=shotgun, 4=chaingun, 5=rocket launcher, 6=plasma, 7=BFG.

doom_screenshot

Saves a full-resolution 320×200 screenshot to the system temp directory and opens it in the default image viewer. Does not advance the game. Note: the viewer launch will fail silently on headless systems or SSH sessions.

How Well Does It Actually Play?

Realistically: well enough to be fun. On E1M1 at medium difficulty, it gets 5-10 kills in a typical 50-action session. It can navigate corridors, spot enemies, aim, and fire. It struggles with enemies behind partial cover and complex door sequences.

It improves significantly when you direct it. “There’s an Imp to your left” turns a wandering AI into a focused combatant. The user-directed mode is where most of the entertainment is. Two AI agents in deathmatch is the obvious next experiment, and the architecture could extend to other doomgeneric-compatible titles: Heretic, Hexen, DOOM II.

The token cost is real: each action call is roughly 600-1000 total tokens (input and output combined: game state text plus the PNG). A 50-action session is 30,000-50,000 tokens, which works out to roughly $0.10-0.50 depending on your model. Worth it.

Additional Resources

The real question was never whether it could run DOOM. It’s what you do with it now that it can. Let me know in the comments how far you get.

Leave a Reply