dnd-universe MCP Server
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@dnd-universe MCP ServerSearch for pages about the city of Waterdeep"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
dnd-universe
The shared world: every place, person, faction and item in your campaign, as linked files you can query, draw, and hand to any AI assistant.
This is the layer everything else plugs into. dnd-scribe feeds sessions in at
one end. The web app and the MCP server (both still to build) read from it at
the other. Foundry VTT gets maps and encounters pushed to it.
Status
Working now:
Entity model and library. Places, characters, factions, items, events, sessions, creatures, lore. Cross-links, backlinks, search, merge-safe writes.
World map import. Turns an Azgaar Fantasy Map Generator export into linked entities with generated visual descriptions.
Art pipeline. Local SDXL on your GPU, one consistent house style across the whole world, content-addressed so nothing is ever drawn twice.
CLI over all of it.
The live wiki. Per-person views, editing, art, uploads, a Discord inbox and a Structure editor, served over a permanent HTTPS address.
The MCP server, so anyone's assistant can read and write the world.
Not built yet: Foundry integration, and the session-to-wiki pipeline.
Related MCP server: llm-wiki-kiss
The one architectural decision worth arguing about
Files are the source of truth. A database, when it arrives, is a derived index you can delete and rebuild.
Entities are markdown with YAML frontmatter under content/<kind>/<slug>.md.
The reasons:
Several people edit this world at once, and markdown in git merges. A shared database needs a migration and a merge strategy for every schema change, and someone to own it.
Language models read and write files natively. That's the whole basis of the MCP server: your friends' assistants author lore directly, and a file is far less fragile to hand a model than a write API.
The web app loses nothing. It indexes the files into SQLite or Postgres at load and queries the index.
If you'd rather the database be authoritative, say so before the web app is built. It's a cheap change now and an expensive one after.
Setup
cd C:\Claude\dnd-universe
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements.txtFor the art command you also need PyTorch from the CUDA 12.8 index, which is
what an RTX 50-series card requires:
.\.venv\Scripts\python.exe -m pip install torch --index-url https://download.pytorch.org/whl/cu128Everything except art runs without torch, so you can build the world on any
machine and generate the pictures on the one with the GPU.
Check it works:
.\.venv\Scripts\python.exe tests\test_universe.pyBuilding the world map
Open https://azgaar.github.io/Fantasy-Map-Generator/ and generate a world, or load one you've already made.
Edit it until it's yours: rename states, place burgs, write legend notes. Anything you write in the map's Notes carries over as page text.
Menu > Save/Load > Export > Save as JSON. Not the
.mapfile, which is a custom format this doesn't read.Import it:
python cli.py import-map path\to\world.json --dry-runCheck the counts look like your map, then drop --dry-run.
What comes across:
Azgaar | becomes | linked to |
state |
| its capital |
province |
| its realm |
burg |
| realm and province |
culture |
| |
religion |
|
Each settlement gets an appearance written from what the map already knows:
size from population, plus harbor, citadel, walls, temple, market, and the
local biome. That's what the art pipeline draws, so imported towns are
immediately drawable without anyone typing a description.
Re-running is safe. Import merges rather than overwrites, and anything a human wrote wins. Automated passes fill empty fields and add to lists; they never replace prose you typed. Re-import after every map edit.
A caveat: Azgaar's JSON shape shifts between versions, and this parser is
tolerant rather than strict, so it skips what it doesn't recognise instead of
failing. Always --dry-run a new export first and check the counts.
Generating art
python cli.py art saltmere-keeppython cli.py art --all --kind place --dry-run--dry-run prints the prompts without touching the GPU, which is the fast way
to tune style before committing to a few hundred images.
Variants control framing. Places take wide, interior, aerial, map.
Characters take portrait, full, action:
python cli.py art the-drowned-lantern --variant interiorConsistency is the point of the whole design:
house_styleinconfig.yamlis prepended to every prompt. It's the single lever that makes four hundred images look like one world. Change it and deleteassets/to restyle everything.Seeds are derived from the entity slug and variant, so the same character regenerates with roughly the same face every time.
Images are content-addressed by a hash of prompt, seed, model, size and steps. Running
art --allafter adding one character generates one image, not four hundred. Use--forceto override.Every image gets a JSON sidecar recording exactly what produced it.
Appearance beats summary. Prompts use an entity's appearance field and
never its summary, because a summary says what something means and an image
model needs to know what it looks like. Entities with no appearance are drawn
from the bare name and the CLI warns you.
The MCP server
mcp_server.py exposes the world to Claude, so everyone at the table can read
and write The Buried Star from their own client instead of routing everything
through one person.
Reading: search_world, get_page, list_pages, world_overview,
open_questions, whats_new, list_files, get_structure.
Writing: create_page, update_page, link_pages, mark_filed,
remove_file, move_page, add_kind, change_kind, remove_kind,
set_site, set_home_sections.
Running it just for yourself
.\.venv\Scripts\python.exe mcp_server.pyThen point a client at it. For Claude Code, in .claude/settings.json:
{
"mcpServers": {
"buried-star": {
"command": "C:\\Claude\\dnd-universe\\.venv\\Scripts\\python.exe",
"args": ["C:\\Claude\\dnd-universe\\mcp_server.py"]
}
}
}Running it for the table
One machine hosts, everyone else connects over the network. Start the tunnel first, because you need its hostname to start the server:
cloudflared tunnel --url http://127.0.0.1:8787It prints a URL like https://some-words-here.trycloudflare.com. Then:
$env:UNIVERSE_MCP_TOKEN = (Get-Content .mcp-token -Raw).Trim().\.venv\Scripts\python.exe mcp_server.py --http --allowed-host some-words-here.trycloudflare.comYour players connect to <that-url>/mcp with an Authorization: Bearer <token>
header.
--allowed-host is not optional behind a tunnel. The MCP transport has DNS
rebinding protection that rejects any request whose Host header it doesn't
recognise. Through a tunnel the Host is the public name, not localhost, so
without this flag every correctly authenticated request comes back 421 Misdirected Request and the cause is not obvious.
Quick tunnels are ephemeral. Stop cloudflared and that URL is gone for
good; the next run gets a different one, and you must restart the server with
the new --allowed-host. Fine for a one-off, annoying for a group who have to
reconfigure every time.
A permanent URL, without buying a domain
Tailscale Funnel gives you a stable public address on a .ts.net hostname for
free, with no domain required. Your players don't need Tailscale; Funnel serves
the open internet.
winget install --id tailscale.tailscale& 'C:\Program Files\Tailscale\tailscale.exe' upThat opens a browser to sign in. Then:
powershell -ExecutionPolicy Bypass -File .\tools\setup_tailscale_funnel.ps1The -ExecutionPolicy Bypass is needed: Windows blocks unsigned local scripts
by default, so running it as .\tools\setup_tailscale_funnel.ps1 fails with
"running scripts is disabled on this system".
The script reads your machine's Tailscale name, turns on Funnel for port 8787,
and prints the permanent URL plus the exact command to restart the MCP server
with the right --allowed-host.
Funnel also needs enabling once for your tailnet. The first run prints an approval link if so; open it, approve, run the script again.
If your tailnet needs HTTPS certificates or the Funnel node attribute enabled first, the script says so and gives you the link.
The Cloudflare route is still there if you'd rather use your own domain:
tools/setup_named_tunnel.ps1 -Hostname wiki.yourdomain.com, after
cloudflared tunnel login.
The server also only runs while the host machine is on. That is the tradeoff of self-hosting, and your players will notice it.
The token is not optional. The server refuses to start in HTTP mode without one, and that refusal is deliberate: these tools can rewrite your campaign, and an unauthenticated endpoint behind a public tunnel is an open invitation. Pick a long random string and share it the way you'd share a password.
Use --read-only to serve the world without create_page, update_page and
link_pages. That's the right setting for a link you don't fully control, or
for anyone you'd rather have read the world than edit it.
What it's good at
The tools are shaped around how a table actually uses a wiki. get_page
returns backlinks along with the page, because "what else touches this" is
usually the real question. open_questions lists everything deliberately
unfinished, which is the fastest way for someone to find a useful contribution.
update_page appends to the body rather than replacing it, so adding what
happened last session can't wipe what was already written.
Page references are forgiving: get_page accepts "Korran Mossborn",
"korran", or "character/korran-mossborn".
Running it
powershell -ExecutionPolicy Bypass -File .\start.ps1That's the whole thing: it finds the Python environment, loads the token, asks Tailscale for this machine's hostname so the transport accepts requests through the funnel, and starts the wiki and MCP server together. Leave the window open.
Note there is no venv in this folder. The interpreter lives next door in
dnd-scribe\.venv, which is why .\.venv\Scripts\python.exe fails from here.
start.ps1 exists so you never have to remember that.
Tailscale runs as a service and returns on its own after a reboot, so this is normally the only thing that needs restarting.
Sharing the wiki as a website
The simplest thing to hand your table: a link. No Obsidian, no git, no Python.
.\.venv\Scripts\python.exe tools\export_site.pyThat writes site/: one HTML page per entity, an index, the art, and a
client-side search index. No build step and no JavaScript framework. Then serve
it alongside the MCP server, on the same address and tunnel:
.\.venv\Scripts\python.exe mcp_server.py --http --wiki site --allowed-host <your-host>https://<your-host>/wiki— the wiki, open, no tokenhttps://<your-host>/mcp— the MCP tools, token required
The split is deliberate. The wiki is a read-only rendering meant to be opened from a shared link. The MCP tools can rewrite the campaign, so they stay behind the bearer token.
Putting a password on it
Without --wiki-password the wiki is readable by anyone with the link. To lock
it:
.\.venv\Scripts\python.exe tools\make_wiki_password.pyThat writes a five-word passphrase to .wiki-password (gitignored). Words
rather than random characters, because five people have to type it on phones,
and a passphrase they'll use beats a stronger one they lose. Then:
$env:UNIVERSE_WIKI_PASSWORD = (Get-Content .wiki-password -Raw).Trim()and start the server as above. Browsers prompt once and remember it. The username is ignored; there's one shared secret.
Pages and images are both covered, and the MCP token is entirely separate, so changing one never affects the other.
Re-run export_site.py after changing content; the folder is rewritten each
time.
Reading the wiki in Obsidian
The content/ folder is the source of truth, but it isn't pleasant to browse:
links live in frontmatter as place/copper-vale and the art isn't referenced
from the markdown at all. Export an Obsidian vault instead:
.\.venv\Scripts\python.exe tools\export_obsidian.pyThen in Obsidian choose Open folder as vault and pick vault/. Open
Start Here, and press Ctrl+G for the graph.
What the export does that the raw files don't:
Turns every link into a real
[[wikilink]], so the graph and backlinks workEmbeds each page's art at the top
Adds a Mentioned by section, so pages read properly outside Obsidian too
Aliases each page to its slug, so
[[copper-vale]]resolves as well as[[Copper Vale]]Disambiguates pages that share a name by appending the kind, because Obsidian resolves links by filename and duplicates would silently point at the wrong page
The vault is generated and one-way. Edits inside it are overwritten on the
next export. Write through content/, the CLI, or the MCP server. It only
deletes files it created itself (tracked in .export-manifest.json), so
anything you add to the vault by hand survives.
Structure, and who gets to change it
What kinds of thing exist, how the front page is arranged and what the site is
called all live in structure.yaml, editable through the MCP tools or the
Structure page. Adding a kind is a config change, not a code change, and
renaming one migrates every page and repoints every link that pointed at them.
Anyone connected can do it. That was a deliberate decision by the person
running this table: the campaign is shared, so its shape is shared. The safety
net is git, not permissions. Every structural change commits first, so the
worst case is git revert rather than an evening lost.
The line that isn't crossed is code. Nothing here writes Python, edits templates or runs a command; a tool that did would hand a shell to anyone who ever leaked a token. Feature work goes through the repo instead.
Uploads
Two kinds, kept apart:
Pictures uploaded on the Art page join the same gallery as the generated ones, in
assets/.Attachments on the Files page (maps, handouts, PDFs, recordings) live in
files/, which is not gitignored: art can be redrawn from the content, a scanned map cannot.
What a file is gets decided by its leading bytes, never its name or the
browser's claim. SVG is refused outright since it can carry script and would
run on the wiki's own origin. Stored names are content hashes, so nothing a
person typed reaches the filesystem, and everything but plain images is served
as a download with X-Content-Type-Options: nosniff.
The Discord inbox
dnd-scribe pulls the campaign's Discord channels into lore/ on a schedule.
The wiki reads that archive and shows, at /wiki/inbox, everything that no
page accounts for yet.
A message stops being new when a page cites it (discord:<channel>:<id> in
sources), when someone presses Not lore, or when it predates the
watermark set the first time a channel is seen. That last rule is what keeps
four years of backlog from landing in the queue on day one; to review a channel
from the start anyway, set its watermark to 0 in .inbox.json.
Nothing is written to the wiki automatically, and that is the point. Discord is
four years of argument, jokes and half-ideas; the wiki is what the table
decided was true. Only a person can tell those apart, so the inbox hands them
the raw messages and a Write it up button that opens the new-page form with
the text already in it. Claude can work the same queue through whats_new and
mark_filed.
Point lore_dir in config.yaml somewhere else if the two projects aren't
side by side.
Everyday commands
python cli.py new character "Kira Ashvale" --appearance "half-elf rogue, silver hair, twin daggers" --link place/saltmere-keeppython cli.py ls place --tag settlementpython cli.py show saltmere-keeppython cli.py checkcheck is the one to run before a session. It finds broken links and entities
nothing points at, which is how a shared wiki quietly rots.
Layout
Path | What it does |
| The data model, and the merge rules that protect human writing. |
| Entity to image prompt. House style, framing, stable seeds. |
| Content-addressed art store and provenance sidecars. |
| Local SDXL generation. |
| The live wiki: sign-in, editing, art, inbox. |
| What's been said in Discord that no page accounts for. |
| Kinds, front page layout, site name, and editing them. |
| Uploaded pictures and attachments, and what's refused. |
| Azgaar export to entities. |
| All commands. |
| Your world. This is the real deliverable. |
| Generated art. Regenerable, gitignored. |
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- Flicense-qualityDmaintenanceEnables AI assistants to act as RPG Game Masters by managing campaign state including characters, inventory, quests, and logs through MCP tools. Supports campaign mutations and provides both MCP and HTTP API access to RPG session data.2
- Alicense-qualityBmaintenanceA KISS self-hosted wiki server for AI agents, providing MCP tools to list, read, search, write, and append notes to Markdown files on the filesystem.MIT

SagaSmith D&D MCPofficial
Alicense-qualityBmaintenanceA local MCP server for Dungeons & Dragons campaign management, combining core runtime with skill and module-generation packs. It enables campaign creation, module generation and import, rule and skill searching via tools, resources, and prompts.Apache 2.0- Alicense-qualityBmaintenanceConnect to your TTRPG campaign's repository and database. Instead of retrieving prose, its 48 tools (20 read, 28 write) return typed state: 14 entity schemas (NPCs, factions, locations, sessions, lore), relationship and knowledge-graph queries, wiki blocks, and a narrative-state bundle of open threads and canon facts.1MIT
Related MCP Connectors
Manage TTRPG campaigns: NPCs, locations, factions, quests, sessions, lore, and knowledge graphs.
Persistent docs and memory for AI agents — read, write, organize & search a shared workspace.
Serve a folder of Markdown notes as an MCP server: hybrid search, reading, and sourced answers.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/slambdi99-cyber/dnd-universe'
If you have feedback or need assistance with the MCP directory API, please join our Discord server