dfhack-mcp

MCP serverAI & models

This integration gives your AI a live view of a running Dwarf Fortress fort as clear, labeled facts. Once added, your AI can answer questions about your citizens, defenses, farms, artifacts, and the fort's overall condition using 37 curated tools. It turns the game's raw data into plain answers you can act on.

Unavailable. This server has no hosted endpoint yet, so Ahel can't serve it.

Add to setup to save this item as a reference. Ahel cannot run it, and signing in will not install it.

What your AI can do with it

  • Pull a one-call overview of the fort: its name, in-game date, season, and population
  • Find citizens by name or profession and open a deep dossier on any one of them
  • Follow the fort's chronicle of combat, deaths, moods, sieges, and migrant waves
  • See where discovered threats are and what the fort has to fight them with
  • Check farm plots and seed stock to keep food production moving
  • Track the fort's performance health so slowdowns are caught early

Getting started

  1. Save this item in Your setup as a reference.
  2. Read the source or reference documentation for its setup requirements. Saving it here does not connect it to your AI.
  3. Check this page for availability before trying to install it through Ahel.

From the project's README

As published by alexanderolvera/dfhack-mcp in README.md.

An MCP server that gives an AI agent a live window into your Dwarf Fortress fort — a co-pilot and early-warning advisor, not an autopilot. Point Claude (or any MCP client) at it and ask "how's my fort doing?" — it reads happiness, threats, stocks, jobs, health, and defenses straight from the running game and answers in plain language.

Read-only by default. The 37 sensor and reference tools only observe the game. A handful of actuators that change the fort — queue manager orders, apply quickfort blueprints, assign labor, sound the civilian alert, pull a lever — ship behind an explicit opt-in and stay hidden until you enable them (see Taking action).

Two kinds of tools:

  • Sensors answer what is my fort doing right now? — fort_status, threats, stocks, jobs_and_labor, military, injuries_and_health, defenses, and more.
  • Reference answers how does Dwarf Fortress work? — game_data is your world's ground truth (its loaded raws), the wiki_* tools are the general explanation, and identify fuses the two for "what is this creature and how do I handle it."

It returns facts, not advice — already-summarized JSON that reads like a glance at the screen, leaving the judgment to the agent.

Every tool has a reference page — parameters, return shape, real example output, caveats — in docs/tools.

Quick start

The package is published to npm and ships a prebuilt bundle — there is nothing to build. Point your MCP client at it with npx:

{
  "mcpServers": {
    "dfhack": {
      "command": "npx",
      "args": ["-y", "dfhack-mcp"]
    }
  }
}

That's it for Claude Desktop / Claude Code / any stdio MCP client. npx -y dfhack-mcp fetches and runs the latest release; its runtime dependencies — the MCP SDK, zod, and the dfhack-remote-node RPC transport — are pulled from npm automatically.

Clients that browse the official MCP Registry can find this server there as io.github.alexanderolvera/dfhack-mcp; the listing is generated from server.json and points at the same npm package, so either route installs the identical build.

Then just have your fort running (next section) and ask your agent something like "check on my fort and flag anything urgent."

Prefer a pinned global install?

npm install -g dfhack-mcp     # then set "command": "dfhack-mcp", "args": []

Requirements

  • Dwarf Fortress running with DFHack, with a fort loaded. The tools read the live game; if no fort is loaded they say so.
  • DFHack Remote RPC on localhost:5000. This is on by default whenever DF runs with DFHack — no config file to edit. (The allow_remote setting only governs connections from other machines and can stay false; a local MCP server reaches it either way.) Point elsewhere with DFHACK_HOST / DFHACK_PORT.
  • Node 20+ to run the published package. (Node 24+ only if you develop from source — see Development.)

What you can ask it

Your agent picks the tools; you just describe what you want. The tools below are what it has to work with.

Sensors — the state of your fort

No arguments; each reports on the loaded fort.

  • fort_status() — name, date/season, population, wealth, happiness breakdown, pre-triaged alerts.
  • stocks() — food/drink as days-of-supply, plus critical material counts and notable low/high lists.
  • threats() — dangerous units grouped by type; active vs. contained, great-danger/invader/undead flags, plus each group's decisive traits (trapavoid, flier, fire, webber, building-destroyer, ranged).
  • unmet_needs() — the needs system aggregated: the top unmet needs ranked by how many dwarves are distracted, and how starved each is.
  • jobs_and_labor() — workforce utilization: busy vs. idle adults (children excluded), idle %, and a ranked breakdown of active jobs.
  • military() — squads, enlisted soldiers, filled positions, readiness against hostiles on the map, and per-squad roster equipment gaps (missing uniform pieces), ammo, and active training order.
  • injuries_and_health() — wounded / patients / bedridden / unconscious counts, plus the care needed (diagnosis, surgery, suture, …).
  • defenses() — active hostiles with map positions and distance/direction/z-delta to the fort core and nearest drawbridge, plus a controllable-structure inventory (bridges, levers, floodgates, hatches, cage traps, doors).
  • burrows() — every burrow's size and membership, plus the civilian-alert safety-burrow set (configured/active/linked burrows) — the read half of civilian_alert.
  • mechanisms() — every lever's position, state, and linked target(s) (bridge/door/floodgate/hatch/support/weapon-trap); pressure-plate trigger conditions; unlinked levers and bridges.
  • moods() — any active strange mood (fey/secretive/possessed/macabre/fell): the dwarf, driving skill, workshop state, and each demanded material cross-referenced against fort stock — the "demands bones, fort has zero" early warning.
  • mandates_and_justice() — the nobility's overhead: active production mandates and export bans, unmet noble room demands, and justice state (open cases, convictions awaiting punishment, restraint capacity).
  • rooms_and_zones() — the facility inventory, each count paired with its demand-side number: bedrooms, dining halls, the hospital, wells, temples, taverns, libraries, guildhalls, and coffins free vs. dead awaiting burial. The supply-side companion to unmet_needs().
  • trade() — the caravan lifecycle and trade depot: depot existence/completeness and wagon-accessibility, caravans present and their state, broker assignment/presence, and the count and approximate value of goods staged in the depot.
  • environment() — ambient conditions right now: season and weather, surface temperature and whether exposed water is frozen, the embark's biome alignment (evil/good/reanimating), and — for each cavern the fort has already breached — whether it is open or sealed. Fog-of-war honest.
  • find_unit(query) — look up citizens by name fragment or profession; a compact dossier per match (profession, age, stress, job, squad, health flags). Chain into citizen for depth.
  • citizen(unit_id) — the full character sheet for one dwarf: social graph (spouse/parents/children/friends/grudges, each with a unit_id you can walk), worshipped deities, notable personality extremes, skills of note, likes/detests, and recent thoughts tied to current stress.
  • site_history() — the fort's entry in the world saga: founding, the fort name in Dwarven + English with etymology, prior sieges/battles at the site, and notable figures who died here.
  • artifacts_and_engravings() — the fort's masterworks and notable engravings.
  • chronicle() — a scannable recent-events feed for the fort.

Spatial (fog-of-war honest — undiscovered tiles never leak):

  • map_overview() — cheap orientation to run before any per-tile read: map extents, the fort-core coordinate, the surface z-level, the z-levels carrying player activity (digging/construction), and stairways as vertical columns. Fixed-size regardless of fort size.

  • tile_region(z?, x0?, y0?, x1?, y1?) — a bounded window of one z-level as an ASCII grid plus a self-describing legend. Undiscovered tiles stay ?. All params optional: none → a 60×40 window on the fort core; z alone → that level's centroid; explicit corners otherwise. Hard-capped at 100×100 (oversized requests are clamped, never errored). Renders the map; never designs it.

    glyphmeaningglyphmeaning
    ?undiscovered (fog of war)+constructed floor
    #undug stone / wall~water / brook
    ,undug soil (sand/clay/loam)%magma
    .dug floor / walkable groundWworkshop / furnace
    FfortificationSstockpile
    rrampMmachine (gear/axle/pump/wheel/windmill)
    vramp topnfurniture (bed/chair/table/door/etc)
    < > xup / down / up-down stair(space)open space
    Ttree
  • geology(reveal_hidden?) — a one-call geological survey (revealed-info only by default): surface z-level, the exposed layer stack with material names, the aquifer (light vs. heavy, z-range), discovered caverns, whether the magma sea is reached, and surface water. reveal_hidden: true bypasses fog of war (a debug/spoiler switch, default off).

Reference — how DF works

wiki_* are pure HTTP and work without the game; game_data / identify read a loaded world.

  • game_data(query, kind?) — your world's raws across six kinds (creature, material, plant, reaction, item, building; default creature). Ground truth for procedural creatures (demons, forgotten beasts, titans) that never reach the wiki. query is a token (DEMON_4, INORGANIC:IRON), a name ("plump helmet"), or — for creatures — a live unit_id. One strong hit → a full dossier; several → a disambiguation list; none → {"match_count":0,"matches":[]}.
  • identify(query) — "what is this creature and how do I handle it" in one call: fuses game_data (your world's raws) with wiki_lookup (strategy). Returns the dossier (its flags[]/interactions[] carry facts like TRAPAVOID → mechanical traps don't work) plus 1–2 trimmed wiki excerpts. Reach for it when a threat appears.
  • wiki_search(query) — search the DF wiki for candidate titles + cleaned snippets (biased to the DF2014 namespace).
  • wiki_lookup(title, section?, refresh?) — fetch a wiki article as clean text, pinned to DF2014; follows redirects, honors section fragments, cached ~30 days.

Taking action (actuators)

By default every tool above only reads the game. The actuators change the fort, so they ship behind an explicit switch — set DFHACK_MCP_ACTUATORS and they appear in the tool list; leave it unset and the server is strictly read-only.

{
  "mcpServers": {
    "dfhack": {
      "command": "npx",
      "args": ["-y", "dfhack-mcp"],
      "env": { "DFHACK_MCP_ACTUATORS": "1" }
    }
  }
}

Every actuator uses the same preview → confirm → apply → undo safety loop, so a change is never a surprise:

  1. Preview (dry-run). The agent calls the tool with the operation fully specified but no confirmation token. It gets back a preview of exactly what would change — facts, never advice — plus a single-use confirm_token. Nothing is written. If the operation can't be applied as asked (e.g. a malformed blueprint), the preview reports why and no token is issued.
  2. Apply. The agent calls again with the same arguments plus that token. The server re-checks that the thing being acted on hasn't changed since the preview; if it has, the token is void and the agent re-previews. On success it gets an undo handle and a readback from the matching sensor confirming the change.

Tokens are single-use and target-scoped: an unrelated change elsewhere in the fort does not void them, but a change to the target does. Each actuator names its own reversal path.

Manager work orders

  • work_order_list(after_id?) — read-only, always available. The fort's manager orders as facts: id, job type, output item/material, amount total/left, repeat frequency, bound workshop, and per-order validation state. Paged (cap 256) with a cursor.
  • work_order_create(job_type, amount, frequency?, material?, item_type?) — queue a new order; the preview flags would_duplicate and manager_present. Reversal: work_order_cancel.
  • work_order_cancel(order_id) — remove one order by id; the undo handle is a recreate spec (with a faithful flag when a workshop binding or conditions can't be fully restored).

Quickfort blueprints — designate dig/zone from an agent-drafted quickfort CSV. There's no separate read sensor: blueprint_apply without a token is the preview.

  • blueprint_apply(csv, anchor_x, anchor_y, anchor_z, mode) — designate from a #dig or #zone blueprint; the top-left cell maps to the anchor. The dry-run parses quickfort's own stats and previews tiles affected, footprint, and fog-of-war tiles under it (a fact, never blocked). A malformed blueprint blocks with no token (quickfort would partially apply). v1 scope: dig + zone only — build/place are rejected. Reversal: blueprint_undo.
  • blueprint_undo(csv, anchor_x, anchor_y, anchor_z, mode) — revert a dig/zone designation via quickfort's native undo (same csv/anchor/mode). The token signs a per-cell digest, so any per-cell drift voids it.

Labor via work details

  • work_details() — read-only, always available. Every work detail (the game's labor groups): name, mode, the labor tokens it enables, and its assigned citizens (id-sorted, capped at 200 with the full member_count).
  • assign_work_detail(unit_id, detail, enabled) — add or remove one citizen to/from one detail. The preview reports currently_member, resulting_members_count, and only_member; an already-satisfied request previews as a no-op. Reversal: the same call with enabled inverted.

Emergency response

  • burrows() — read-only, always available. Every burrow's size and membership, plus the civilian alert's own state: configured (has the fort ever set one up), active (is it sounding right now), and the linked burrow ids.
  • civilian_alert(burrow, enabled) — add or remove one burrow from the civilian-alert safety set. enabled=true also sounds the alarm if it wasn't already; enabled=false only silences it once the set becomes fully empty. Reversal: the same call with enabled inverted.
  • mechanisms() — read-only, always available. Every lever/pressure-plate's position and linked target(s) (bridge/door/floodgate/hatch/support/weapon-trap), plus unlinked levers and bridges.
  • pull_lever(lever_id, urgent?) — queue a job for a dwarf to pull a named lever (urgent defaults to do-now priority). This queues the job; the physical toggle happens once a dwarf completes it, not on apply. Reversal: pull the same lever again.

Saving the game

  • game_save() — checkpoint the fort with a quicksave before a large or risky change, so a bad batch can be rolled back by loading the save. Takes no arguments; the dry-run previews the fort and game date being frozen. Two facts to know: the write is asynchronous (DF commits it over the next few frames — the readback confirms the quicksave dispatched, not that the file landed) and it routes through DF's autosave, so it lands in a rotating "autosave" folder per your DF settings rather than overwriting the loaded save. Irreversible: to roll back, load the appropriate save/autosave in DF. Fortress mode only.

Configuration

All optional, set in your MCP client's env for the server:

VariableDefaultEffect
DFHACK_HOST127.0.0.1Host where DFHack's Remote RPC is listening.
DFHACK_PORT5000Port for DFHack's Remote RPC.
DFHACK_MCP_ACTUATORS(unset)Set to 1 to expose the write actuators (off = strictly read-only).
DFHACK_MCP_DEV(unset)Set to 1 to expose run_lua, a raw DFHack Lua escape hatch (reads and writes game state; for tool authors only).

Troubleshooting

  • {"error":"no fort loaded"} — DFHack is reachable but you're at the title screen or in the menus. Load a fort in Fortress mode.
  • An error about not reaching DFHack — Dwarf Fortress isn't running with DFHack, or the RPC port differs. Confirm DF is up with DFHack and that DFHACK_HOST / DFHACK_PORT match (defaults 127.0.0.1:5000).
  • The tools don't show up in your client — restart the MCP client so it relaunches the server; a running server won't pick up a config change.
  • The write tools are missing — that's the default. Set DFHACK_MCP_ACTUATORS=1 (see Taking action).
  • First call after loading a fort errors once, then works — a freshly-started DFHack can reject the very first tool call while it finishes registering the query scripts; retry once.

Development

To hack on the server itself, clone and run the TypeScript entry directly (Node 24+ — it runs the sources via type-stripping, no build step):

git clone https://github.com/alexanderolvera/dfhack-mcp.git
cd dfhack-mcp
npm install        # or: npm run bootstrap  (installs + runs the T0 contract check)
node src/index.ts

Point an MCP client at the checkout with "command": "node", "args": ["/absolute/path/to/dfhack-mcp/src/index.ts"].

All version-fragile DFHack field access lives in real .lua scripts under src/dfhack-queries/ (one per tool), invoked by name with native argv — so a DF/DFHack version bump is a localized fix and query parameters are injection-safe by construction. Each tool is a thin TypeScript wrapper in src/tools/.

Facts, not advice. Tools sense — they return what is true about the fort and the world, the way a player reads a screen. They do not say what to build or how to fight; that judgment is the agent's. A field that says what to do is advice — leave it out.

Every tool is verified against a live fort with the tiered harness (npm run verify:t0 … t2) — never mocks. See CONTRIBUTING.md for the tool-authoring split and the verification workflow, and docs/VERIFY.md for the harness tiers.

License

ISC — see LICENSE.md.

Tools it gives your AI (37)

ToolWhat it does
artifacts_and_engravingsThe fort's art, as labeled facts. Returns the named ARTIFACTS (paginated), SITE-SCOPED BY DEFAULT — only artifacts belonging to the loaded fort, which is almost always what is meant by "our...
burrowsThe fort's burrows as facts: each burrow's id, name, exact tile_count (dfhack.burrows.isAssignedBlockTile summed over every assigned block — the precise painted area, not a bounding box)...
chronicleThe fort's announcement/report stream (combat, deaths, moods, artifacts, sieges, migrants, ...) as triaged, cursor-addressable events. Reads the rolling, front-pruned report window.
citizenA deep dossier on ONE citizen, chained by unit_id from find_unit (or chronicle). Where find_unit stays compact, this is the depth: the walkable social graph (spouse, parents, children, friends...
defensesWhere the threats are versus what you have to fight them with. Returns active hostiles the fort has DISCOVERED (same fog-of-war gate and same count as threats()' active_hostiles and military()'s...
environmentThe fort's ambient conditions right now: current season and dominant weather (none/rain/snow), the surface temperature with whether exposed water is currently frozen (the freezing point is 10000 DF...
farmingThe fort's farm plots and seed stock as facts — the early-survival pipeline that stocks (food OUTPUTS) and game_data (what is plantable, in the abstract) don't cover.
find_unitLook up citizens by a name fragment or profession (case-insensitive, matches either).
fluidsWater and magma engineering facts the Earthworks tier (tile_region, geology) does not cover: aquifer layers, standing/flowing water, the magma sea's top, flood exposure at the fort interior, and...
fort_healthThe fort's computational health as facts — FPS death is the true endgame boss, and nothing else reports it.
fort_statusOne-call situational overview of the currently loaded Dwarf Fortress fort: name, in-game date and season, population, created wealth, a happiness breakdown (miserable/unhappy/content/happy)...
game_dataLook up the LOADED WORLD's raws (ground truth for THIS world) and return curated, labeled facts.
geologyA one-call geological survey of the embark, REVEALED-INFO ONLY by default. Returns surface_z_max — the highest open-to-sky ground z-level found ANYWHERE on the embark (NOT map_overview()'s...
hauling_routesThe fort's minecart hauling infrastructure: routes, each route's stops, and the vehicle (minecart) objects fort-wide. routes[] is {id, name?, stops[], vehicles[]}.
identifyOne-call "what is this creature and how do I handle it": fuses THIS WORLD's raws (ground truth) with the DF wiki (strategy). Pass a creature token (e.g.
injuries_and_healthThe fort's medical picture, counted on CURRENT condition rather than on history. wounded = dwarves carrying at least one wound with live damage (bleeding, pain, swelling, an unset fracture, a...
jobs_and_laborWorkforce utilization: how many working-age dwarves are busy vs. idle (children/babies excluded from the labor pool), the idle percentage, and a ranked breakdown of what jobs the fort is currently...
livestock_and_pasturesThe fort's tame animal economy as facts — every prior tool sees hostiles (threats) or nothing at all here.
map_overviewCheap spatial orientation to run BEFORE any per-tile terrain read: map extents (x/y/z tile counts), the fort-core coordinate (the same 3D citizen centroid defenses() reports), surface_z_at_core —...
mandates_and_justiceThe fort's nobility overhead as facts. Active production mandates (a noble's make-N-of-an-item quota with its remaining count and days to deadline) and export bans, listed by item.
mechanismsThe fort's lever/pressure-plate wiring as facts — players (and an AI co-pilot) routinely forget which lever raises which bridge; this makes it legible.
militaryThe fort's military: number of squads, how many living present dwarves are actually enlisted (soldiers), filled squad positions, and readiness read against hostiles currently on the map...
moodsAny active strange mood (fey/secretive/possessed/macabre/fell) and its material countdown.
nobles_and_administratorsThe fort's appointed positions (manager, bookkeeper, broker, chief medical dwarf, sheriff, expedition leader/mayor, militia commander/captain, hammerer, and any higher noble the site has grown into)...
petitionsThe fort's outstanding agreements as facts: location petitions (temple/guildhall requests from a deity's worshippers or a guild) and residency/citizenship petitions (a migrant or visitor asking to...
rooms_and_zonesThe fort's facility inventory, each count paired with its demand-side number where one exists: bedrooms (assigned/unassigned vs.
site_historyThis fort's entry in the PERMANENT world saga (the durable history event log, not the pruned live report stream).
stockpilesThe fort's hauling/logistics picture: every stockpile as a fact sheet, plus fort-wide backlog signals stockpiles alone don't surface.
stocksFood and drink as estimated days-of-supply for the current population, plus counts of critical materials (wood, fuel, cloth, tanned hides, stone) and lists of notably low or high stocks.
threatsDangerous units currently on the map, grouped by creature type. Separates ACTIVE hostiles from CONTAINED ones (caged/chained), flags great-danger creatures (megabeasts, titans, demons, forgotten...
tile_regionA bounded window of ONE z-level rendered as an ASCII character grid plus a self-describing legend (every response carries the legend for exactly the glyphs it uses).
tradeThe trade picture right now: whether a trade depot exists, is complete, and is wagon-accessible (DF's own pathability check, not merely built); which caravans are present and their lifecycle state...
unmet_needsWhy the fort is stressed: the dwarven needs system aggregated across all citizens. Returns the top unmet needs (e.g.
wiki_lookupFetch a Dwarf Fortress wiki article as clean, readable text, pinned to the DF2014 namespace. Follows redirects (multi-hop) and honors section fragments (e.g.
wiki_searchSearch the Dwarf Fortress wiki (MediaWiki) for candidate article titles and cleaned snippets. Discovery/disambiguation step before wiki_lookup; biased to the DF2014 (Steam/Premium) namespace.
work_detailsList the fort’s work details (the labor-management groups) as facts: each detail’s name, mode (OnlySelectedDoesThis / EverybodyDoesThis / NobodyDoesThis / Default), the labor tokens it enables, and...
work_order_listList the fort’s active manager (work) orders as facts: id, job type, output item/material tokens, amount total/left, repeat frequency, bound workshop, condition count, and per-order validation state...

Signals

GitHub stars
2
Forks
1
Last commit
Aug 2026
Weekly downloads
57
Tools captured
37
Advanced
Delivery
dfhack-mcp MCP server → your Ahel connector (mcp.ahel.ai) → your AI.
Item type
mcp-server
Key
io-github-alexanderolvera-dfhack-mcp
Source
github.com/alexanderolvera/dfhack-mcp