dwarffortress-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,26 @@
1
+ # Dwarffortress
2
+
3
+ Use this MCP server to the Dwarf Fortress wiki, !!FUN!! knowledge about the most complex game ever made.
4
+
5
+ ## Quick start
6
+
7
+ ```bash
8
+ npm install
9
+ npm run build
10
+ node dist/index.js
11
+ ```
12
+
13
+ The server uses stdio, so it can be connected to Claude Desktop, Cursor, VS Code, MCP Inspector, or another compatible MCP client.
14
+
15
+ ## Tools at a glance
16
+
17
+ - `search_pages`: Search the Dwarf Fortress wiki.
18
+ - `get_page`: Read a DF wiki page as plain text.
19
+
20
+ ## Limits and privacy
21
+
22
+ This project is intentionally narrow. It should be treated as a practical helper, not a complete certification or security audit. Check the implementation and the returned data before using it with sensitive material. No credentials are required unless the project explicitly says otherwise.
23
+
24
+ ## Try it
25
+
26
+ After building, connect the server through your MCP client. The repository root also contains `smoke-test.mjs` for projects covered by the shared harness. A typical tool call starts with `search_pages`.
package/dist/api.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ export declare class DfError extends Error {
2
+ }
3
+ export interface WikiPage {
4
+ pageid: number;
5
+ title: string;
6
+ snippet?: string;
7
+ }
8
+ export declare function searchPages(query: string, limit?: number): Promise<WikiPage[]>;
9
+ export declare function getPage(title: string, maxChars?: number): Promise<{
10
+ title: string;
11
+ wikitext: string;
12
+ url: string;
13
+ } | null>;
14
+ export declare function wikiTextToPlain(wt: string, maxChars?: number): string;
package/dist/api.js ADDED
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Dwarf Fortress wiki client (dwarffortresswiki.org). MediaWiki API,
3
+ * keyless. Contains the lore, !!FUN!!, adamantine, and magma knowledge.
4
+ */
5
+ const BASE = "https://dwarffortresswiki.org/api.php";
6
+ export class DfError extends Error {
7
+ }
8
+ async function getJson(params) {
9
+ const qs = new URLSearchParams({ format: "json", ...params });
10
+ const res = await fetch(`${BASE}?${qs.toString()}`, {
11
+ headers: { "User-Agent": "dwarffortress-mcp/1.0 (research)" },
12
+ });
13
+ if (!res.ok)
14
+ throw new DfError(`DF wiki error ${res.status}`);
15
+ return (await res.json());
16
+ }
17
+ export async function searchPages(query, limit = 8) {
18
+ const d = await getJson({
19
+ action: "query",
20
+ list: "search",
21
+ srsearch: query,
22
+ srlimit: String(limit),
23
+ srprop: "snippet",
24
+ });
25
+ return (d.query?.search ?? []).map((s) => ({
26
+ pageid: s.pageid,
27
+ title: s.title,
28
+ snippet: (s.snippet ?? "").replace(/<[^>]+>/g, ""),
29
+ }));
30
+ }
31
+ export async function getPage(title, maxChars = 15000) {
32
+ const d = await getJson({
33
+ action: "parse",
34
+ page: title,
35
+ prop: "wikitext",
36
+ formatversion: "2",
37
+ });
38
+ const p = d.parse;
39
+ if (!p)
40
+ return null;
41
+ const wt = typeof p.wikitext === "string" ? p.wikitext : (p.wikitext?.["*"] ?? "");
42
+ return {
43
+ title: p.title ?? title,
44
+ wikitext: wt.slice(0, maxChars),
45
+ url: `https://dwarffortresswiki.org/index.php/${encodeURIComponent(p.title ?? title).replace(/%2F/g, "/")}`,
46
+ };
47
+ }
48
+ export function wikiTextToPlain(wt, maxChars = 12000) {
49
+ return wt
50
+ .replace(/\{\{[^{}]*\}\}/g, "")
51
+ .replace(/<ref[^>]*>[\s\S]*?<\/ref>/gi, "")
52
+ .replace(/<[^>]+>/g, "")
53
+ .replace(/\[\[(?:[^|\]]*\|)?([^\]]*)\]\]/g, "$1")
54
+ .replace(/'''/g, "")
55
+ .replace(/''/g, "")
56
+ .replace(/\{\|[\s\S]*?\|\}/g, "[table]")
57
+ .replace(/={2,}([^=]+)={2,}/g, "\n$1\n")
58
+ .replace(/\n{3,}/g, "\n\n")
59
+ .trim()
60
+ .slice(0, maxChars);
61
+ }
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2
+ import { createServer } from "./server.js";
3
+ async function main() {
4
+ const server = createServer();
5
+ const transport = new StdioServerTransport();
6
+ await server.connect(transport);
7
+ console.error("MCP server running on stdio");
8
+ }
9
+ main().catch((err) => {
10
+ console.error("Fatal error:", err);
11
+ process.exit(1);
12
+ });
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function createServer(): McpServer;
package/dist/server.js ADDED
@@ -0,0 +1,59 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { DfError, getPage, searchPages, wikiTextToPlain } from "./api.js";
4
+ const text = (t) => ({ content: [{ type: "text", text: t }] });
5
+ const textError = (t) => ({ content: [{ type: "text", text: t }], isError: true });
6
+ const READ_ONLY = { readOnlyHint: true, openWorldHint: true };
7
+ export function createServer() {
8
+ const server = new McpServer({
9
+ name: "dwarffortress-mcp",
10
+ version: "1.0.0",
11
+ });
12
+ server.registerTool("search_pages", {
13
+ title: "Search pages",
14
+ description: "Search the Dwarf Fortress wiki.",
15
+ inputSchema: z.object({ query: z.string().describe("Search terms, e.g. 'adamantine' or 'vampire'") }),
16
+ annotations: READ_ONLY,
17
+ }, async ({ query }) => {
18
+ try {
19
+ const pages = await searchPages(query);
20
+ if (pages.length === 0)
21
+ return text(`No DF wiki pages match "${query}".`);
22
+ return text(`DF wiki results for "${query}":\n\n` +
23
+ pages
24
+ .map((p, i) => `${i + 1}. ${p.title}\n ${p.snippet ?? ""}\n https://dwarffortresswiki.org/index.php/${encodeURIComponent(p.title).replace(/%2F/g, "/")}`)
25
+ .join("\n\n"));
26
+ }
27
+ catch (e) {
28
+ return textError(errorMessage(e));
29
+ }
30
+ });
31
+ server.registerTool("get_page", {
32
+ title: "Get page",
33
+ description: "Read a DF wiki page as plain text.",
34
+ inputSchema: z.object({
35
+ title: z.string().describe("Exact page title from search_pages"),
36
+ maxChars: z.number().int().min(500).max(30000).default(12000),
37
+ }),
38
+ annotations: READ_ONLY,
39
+ }, async ({ title, maxChars }) => {
40
+ try {
41
+ const page = await getPage(title);
42
+ if (!page)
43
+ return text(`No DF wiki page "${title}".`);
44
+ const plain = wikiTextToPlain(page.wikitext, maxChars);
45
+ return text(`${page.title}\n${page.url}\n\n${plain || "(no readable content)"}`);
46
+ }
47
+ catch (e) {
48
+ return textError(errorMessage(e));
49
+ }
50
+ });
51
+ return server;
52
+ }
53
+ function errorMessage(e) {
54
+ if (e instanceof DfError)
55
+ return `Error: ${e.message}`;
56
+ if (e instanceof Error)
57
+ return `Error: ${e.message}`;
58
+ return `Error: ${String(e)}`;
59
+ }
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "dwarffortress-mcp",
3
+ "version": "1.0.0",
4
+ "description": "Use this MCP server to the Dwarf Fortress wiki, !!FUN!! knowledge about the most complex game ever made. Tools include search pages, get page",
5
+ "type": "module",
6
+ "mcpName": "io.github.mrfentmen/dwarffortress-mcp",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/mrfentmen/dwarffortress-mcp.git"
10
+ },
11
+ "bin": {
12
+ "dwarffortress-mcp": "./dist/index.js"
13
+ },
14
+ "main": "./dist/index.js",
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsc -p tsconfig.json",
20
+ "start": "node dist/index.js",
21
+ "dev": "rm -rf dist && tsc -p tsconfig.json && node dist/index.js",
22
+ "inspect": "npx @modelcontextprotocol/inspector node dist/index.js"
23
+ },
24
+ "keywords": [
25
+ "mcp",
26
+ "dwarf",
27
+ "fortress",
28
+ "wiki",
29
+ "roguelike"
30
+ ],
31
+ "license": "MIT",
32
+ "dependencies": {
33
+ "@modelcontextprotocol/sdk": "^1.0.4",
34
+ "zod": "^3.23.8"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^22.0.0",
38
+ "typescript": "^5.6.0"
39
+ },
40
+ "engines": {
41
+ "node": ">=20"
42
+ }
43
+ }