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 160x100 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, 1-7
|
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 320x200 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
- doom-mcp on GitHub: Source, docs, and examples
- doom-mcp on npm: Package page
- doomgeneric by ozkl: The portable DOOM engine this is built on
- Freedoom: The open-source IWAD that ships with the package
- DOOM1.WAD shareware download: The original shareware episode, free and legal
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.
Top comments (0)