{
  "markdown": "# dfhack-mcp\n\nAn [MCP](https://modelcontextprotocol.io) server that gives an AI agent a live\nwindow into your **Dwarf Fortress** fort — a co-pilot and early-warning advisor,\nnot an autopilot. Point Claude (or any MCP client) at it and ask _\"how's my fort\ndoing?\"_ — it reads happiness, threats, stocks, jobs, health, and defenses\nstraight from the running game and answers in plain language.\n\n**Read-only by default.** The 37 sensor and reference tools only observe the\ngame. A handful of **actuators** that _change_ the fort — queue manager orders,\napply quickfort blueprints, assign labor, sound the civilian alert, pull a lever —\nship behind an explicit opt-in and stay hidden until you enable them (see\n[Taking action](#taking-action-actuators)).\n\nTwo kinds of tools:\n\n- **Sensors** answer _what is my fort doing right now?_ — `fort_status`,\n  `threats`, `stocks`, `jobs_and_labor`, `military`, `injuries_and_health`,\n  `defenses`, and more.\n- **Reference** answers _how does Dwarf Fortress work?_ — `game_data` is **your\n  world's** ground truth (its loaded raws), the `wiki_*` tools are the general\n  explanation, and `identify` fuses the two for _\"what is this creature and how\n  do I handle it.\"_\n\nIt returns **facts, not advice** — already-summarized JSON that reads like a\nglance at the screen, leaving the judgment to the agent.\n\nEvery tool has a reference page — parameters, return shape, real example\noutput, caveats — in [docs/tools](docs/tools/README.md).\n\n## Quick start\n\nThe package is published to npm and ships a **prebuilt bundle** — there is\nnothing to build. Point your MCP client at it with `npx`:\n\n```json\n{\n  \"mcpServers\": {\n    \"dfhack\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"dfhack-mcp\"]\n    }\n  }\n}\n```\n\nThat's it for Claude Desktop / Claude Code / any stdio MCP client. `npx -y\ndfhack-mcp` fetches and runs the latest release; its runtime dependencies — the\nMCP SDK, `zod`, and the\n[`dfhack-remote-node`](https://www.npmjs.com/package/dfhack-remote-node) RPC\ntransport — are pulled from npm automatically.\n\nClients that browse the [official MCP Registry](https://registry.modelcontextprotocol.io)\ncan find this server there as **`io.github.alexanderolvera/dfhack-mcp`**; the\nlisting is generated from [`server.json`](server.json) and points at the same npm\npackage, so either route installs the identical build.\n\nThen just have your fort running (next section) and ask your agent something like\n_\"check on my fort and flag anything urgent.\"_\n\nPrefer a pinned global install?\n\n```sh\nnpm install -g dfhack-mcp     # then set \"command\": \"dfhack-mcp\", \"args\": []\n```\n\n## Requirements\n\n- **Dwarf Fortress running with DFHack**, with a fort loaded. The tools read the\n  live game; if no fort is loaded they say so.\n- **DFHack Remote RPC** on `localhost:5000`. This is **on by default** whenever DF\n  runs with DFHack — no config file to edit. (The `allow_remote` setting only\n  governs connections from _other_ machines and can stay `false`; a local MCP\n  server reaches it either way.) Point elsewhere with `DFHACK_HOST` / `DFHACK_PORT`.\n- **Node 20+** to run the published package. (Node 24+ only if you develop from\n  source — see [Development](#development).)\n\n## What you can ask it\n\nYour agent picks the tools; you just describe what you want. The tools below are\nwhat it has to work with.\n\n### Sensors — the state of your fort\n\nNo arguments; each reports on the loaded fort.\n\n- **`fort_status()`** — name, date/season, population, wealth, happiness breakdown, pre-triaged alerts.\n- **`stocks()`** — food/drink as days-of-supply, plus critical material counts and notable low/high lists.\n- **`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).\n- **`unmet_needs()`** — the needs system aggregated: the top unmet needs ranked by how many dwarves are distracted, and how starved each is.\n- **`jobs_and_labor()`** — workforce utilization: busy vs. idle adults (children excluded), idle %, and a ranked breakdown of active jobs.\n- **`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.\n- **`injuries_and_health()`** — wounded / patients / bedridden / unconscious counts, plus the care needed (diagnosis, surgery, suture, …).\n- **`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).\n- **`burrows()`** — every burrow's size and membership, plus the civilian-alert safety-burrow set (configured/active/linked burrows) — the read half of `civilian_alert`.\n- **`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.\n- **`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.\n- **`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).\n- **`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()`.\n- **`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.\n- **`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.\n- **`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.\n- **`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.\n- **`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.\n- **`artifacts_and_engravings()`** — the fort's masterworks and notable engravings.\n- **`chronicle()`** — a scannable recent-events feed for the fort.\n\n**Spatial** (fog-of-war honest — undiscovered tiles never leak):\n\n- **`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.\n- **`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.\n\n  | glyph       | meaning                     | glyph   | meaning                                 |\n  | ----------- | --------------------------- | ------- | --------------------------------------- |\n  | `?`         | undiscovered (fog of war)   | `+`     | constructed floor                       |\n  | `#`         | undug stone / wall          | `~`     | water / brook                           |\n  | `,`         | undug soil (sand/clay/loam) | `%`     | magma                                   |\n  | `.`         | dug floor / walkable ground | `W`     | workshop / furnace                      |\n  | `F`         | fortification               | `S`     | stockpile                               |\n  | `r`         | ramp                        | `M`     | machine (gear/axle/pump/wheel/windmill) |\n  | `v`         | ramp top                    | `n`     | furniture (bed/chair/table/door/etc)    |\n  | `<` `>` `x` | up / down / up-down stair   | (space) | open space                              |\n  | `T`         | tree                        |         |                                         |\n\n- **`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).\n\n### Reference — how DF works\n\n`wiki_*` are pure HTTP and work without the game; `game_data` / `identify` read a\nloaded world.\n\n- **`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\":[]}`.\n- **`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.\n- **`wiki_search(query)`** — search the DF wiki for candidate titles + cleaned snippets (biased to the `DF2014` namespace).\n- **`wiki_lookup(title, section?, refresh?)`** — fetch a wiki article as clean text, pinned to `DF2014`; follows redirects, honors section fragments, cached ~30 days.\n\n### Taking action (actuators)\n\nBy default every tool above only reads the game. The **actuators** _change_ the\nfort, so they ship behind an explicit switch — set `DFHACK_MCP_ACTUATORS` and they\nappear in the tool list; leave it unset and the server is strictly read-only.\n\n```json\n{\n  \"mcpServers\": {\n    \"dfhack\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"dfhack-mcp\"],\n      \"env\": { \"DFHACK_MCP_ACTUATORS\": \"1\" }\n    }\n  }\n}\n```\n\nEvery actuator uses the same **preview → confirm → apply → undo** safety loop, so\na change is never a surprise:\n\n1. **Preview (dry-run).** The agent calls the tool with the operation fully\n   specified but **no** confirmation token. It gets back a `preview` of exactly\n   what would change — facts, never advice — plus a single-use `confirm_token`.\n   **Nothing is written.** If the operation can't be applied as asked (e.g. a\n   malformed blueprint), the preview reports why and **no token is issued**.\n2. **Apply.** The agent calls again with the same arguments plus that token. The\n   server re-checks that _the thing being acted on_ hasn't changed since the\n   preview; if it has, the token is void and the agent re-previews. On success it\n   gets an `undo` handle and a `readback` from the matching sensor confirming the\n   change.\n\nTokens are single-use and target-scoped: an unrelated change elsewhere in the\nfort does not void them, but a change to the target does. Each actuator names its\nown reversal path.\n\n**Manager work orders**\n\n- **`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.\n- **`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`.\n- **`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).\n\n**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.\n\n- **`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`.\n- **`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.\n\n**Labor via work details**\n\n- **`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`).\n- **`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.\n\n**Emergency response**\n\n- **`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.\n- **`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.\n- **`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.\n- **`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.\n\n**Saving the game**\n\n- **`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.\n\n## Configuration\n\nAll optional, set in your MCP client's `env` for the server:\n\n| Variable               | Default     | Effect                                                                                                                  |\n| ---------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------- |\n| `DFHACK_HOST`          | `127.0.0.1` | Host where DFHack's Remote RPC is listening.                                                                            |\n| `DFHACK_PORT`          | `5000`      | Port for DFHack's Remote RPC.                                                                                           |\n| `DFHACK_MCP_ACTUATORS` | _(unset)_   | Set to `1` to expose the write actuators (off = strictly read-only).                                                    |\n| `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). |\n\n## Troubleshooting\n\n- **`{\"error\":\"no fort loaded\"}`** — DFHack is reachable but you're at the title\n  screen or in the menus. Load a fort in Fortress mode.\n- **An error about not reaching DFHack** — Dwarf Fortress isn't running with\n  DFHack, or the RPC port differs. Confirm DF is up with DFHack and that\n  `DFHACK_HOST` / `DFHACK_PORT` match (defaults `127.0.0.1:5000`).\n- **The tools don't show up in your client** — restart the MCP client so it\n  relaunches the server; a running server won't pick up a config change.\n- **The write tools are missing** — that's the default. Set\n  `DFHACK_MCP_ACTUATORS=1` (see [Taking action](#taking-action-actuators)).\n- **First call after loading a fort errors once, then works** — a freshly-started\n  DFHack can reject the very first tool call while it finishes registering the\n  query scripts; retry once.\n\n## Development\n\nTo hack on the server itself, clone and run the TypeScript entry directly (Node\n24+ — it runs the sources via type-stripping, no build step):\n\n```sh\ngit clone https://github.com/alexanderolvera/dfhack-mcp.git\ncd dfhack-mcp\nnpm install        # or: npm run bootstrap  (installs + runs the T0 contract check)\nnode src/index.ts\n```\n\nPoint an MCP client at the checkout with `\"command\": \"node\", \"args\":\n[\"/absolute/path/to/dfhack-mcp/src/index.ts\"]`.\n\nAll version-fragile DFHack field access lives in **real `.lua` scripts** under\n`src/dfhack-queries/` (one per tool), invoked by name with native argv — so a\nDF/DFHack version bump is a localized fix and query parameters are injection-safe\nby construction. Each tool is a thin TypeScript wrapper in `src/tools/`.\n\n**Facts, not advice.** Tools _sense_ — they return what is true about the fort and\nthe world, the way a player reads a screen. They do not say what to build or how\nto fight; that judgment is the agent's. A field that says _what to do_ is advice —\nleave it out.\n\nEvery tool is verified against a **live fort** with the tiered harness (`npm run\nverify:t0` … `t2`) — never mocks. See [`CONTRIBUTING.md`](CONTRIBUTING.md) for the\ntool-authoring split and the verification workflow, and\n[`docs/VERIFY.md`](docs/VERIFY.md) for the harness tiers.\n\n## License\n\nISC — see [LICENSE.md](./LICENSE.md).\n",
  "bytes": 18941,
  "sha": "c4ad042e6107435f9549116c9c09283e07a6aebdf9a0288a72f64e609b6f5d5c",
  "repo_slug": "alexanderolvera/dfhack-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_alexanderolvera_dfhack_mcp_cf49ba16/readme"
}