5eMCP
<h1><picture><source media="(prefers-color-scheme: dark)" srcset="assets/icon-dark.png"><source media="(prefers-color-scheme: light)" srcset="assets/icon-light.png"><img src="assets/icon-light.png" width="40" height="40" alt="D20"></picture> 5eMCP</h1>
[](https://github.com/jazzsequence/5eMCP/actions/workflows/test.yml)
[](https://github.com/jazzsequence/5eMCP/releases/latest)
[](https://github.com/jazzsequence/5eMCP/releases/latest)
A complete D&D 5e reference and utility MCP server backed by live [5etools](https://5e.tools) data.
## Quick Start
### Claude Desktop — One-Click Install (recommended)
**Prerequisite:** Node.js ≥ 22 must be your system's default `node`. Claude Desktop uses the macOS launch environment PATH, not your shell session — your terminal's active `nvm` version doesn't apply. If you use nvm, run `nvm alias default 22` (or `nvm install 22 && nvm alias default 22` if you don't have it) and fully quit and relaunch Claude Desktop. If you don't use nvm, install Node.js from [nodejs.org](https://nodejs.org).
1. Download `5eMCP.mcpb` from the [latest release](https://github.com/jazzsequence/5eMCP/releases/latest)
2. Open the file — Claude Desktop will prompt you to install it, **or** go to **Settings → Extensions → Install Extension** and select the file
3. Enter your GitHub personal access token when prompted (required — create a free one at [github.com/settings/tokens](https://github.com/settings/tokens) with `public_repo` scope or read-only access to public repos)
4. Choose your default ruleset (`2024` or `2014`)
5. Restart Claude Desktop
Your token is stored securely in the OS keychain (macOS Keychain / Windows Credential Manager) — never in plain text.
---
### Developers (Claude Code, Cursor, manual config)
**Requirements:** Node.js ≥ 22, optional GitHub personal access token (read-only, public repos)
```bash
git clone https://github.com/jazzsequence/5eMCP.git
cd 5eMCP
npm install
npm run build
pwd # copy this — you'll use it as your path in the config below
```
In the configs below, replace `/path/to/5eMCP` with the output of `pwd` above (e.g. `/Users/you/Projects/5eMCP`).
#### Claude Code
Add to `~/.claude.json`:
```json
{
"mcpServers": {
"5etools": {
"command": "node",
"args": ["/path/to/5eMCP/dist/index.js"],
"env": {
"GITHUB_TOKEN": "ghp_your_token_here",
"DEFAULT_RULESET": "2024"
}
}
}
}
```
#### Cursor
Add to `.cursor/mcp.json` in your project (or `~/.cursor/mcp.json` globally):
```json
{
"mcpServers": {
"5etools": {
"command": "node",
"args": ["/path/to/5eMCP/dist/index.js"],
"env": {
"GITHUB_TOKEN": "ghp_your_token_here",
"DEFAULT_RULESET": "2024"
}
}
}
}
```
#### Claude Desktop (manual config) {#claude-desktop-manual-config}
Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
```json
{
"mcpServers": {
"5etools": {
"command": "node",
"args": ["/path/to/5eMCP/dist/index.js"],
"env": {
"GITHUB_TOKEN": "ghp_your_token_here",
"DEFAULT_RULESET": "2024"
}
}
}
}
```
`DEFAULT_RULESET` can be `"2024"` (default) or `"2014"` for legacy rules. `GITHUB_TOKEN` is optional but strongly recommended — unauthenticated requests are rate-limited to 60/hr.
## How It Works
[5e.tools](https://5e.tools) is fully client-side. When `spells.html` loads, the browser fetches `data/spells/spells-phb.json` directly from GitHub and renders it in JavaScript. This server replicates that pattern server-side:
```
GitHub Contents API
→ manifest: { spells: [...], bestiary: [...], book: [...], ... }
→ SHA-keyed disk/Redis cache
→ raw.githubusercontent.com (fetch on miss)
→ translation layer (resolve {@tags}, merge fluff, normalize)
→ MCP tool response
```
The manifest is schema-agnostic and self-updating. When 5etools adds a new content type, the next manifest refresh picks it up automatically — no code change required. Unknown types run through the passthrough handler (tags resolved, internal fields stripped) and return clean JSON. Nothing is ever inaccessible.
## Available Tools
### Meta Tools
| Tool | Description |
|---|---|
| `help` | Full decision guide to every tool — call first when unsure which one to use. |
| `manifest_status` | Build time, file counts by type, unknown types discovered. |
| `list_sources` | All source abbreviations with content types. |
| `fetch_content` | Fetch and translate any file in the manifest by content type + file name. Universal fallback for any content type. |
### Search Tools (`*_search`)
All search tools accept `query` (name substring), `ruleset` (`"2024"` or `"2014"`), `limit`, `fields` (optional list of field names to include in each result — default is all fields, e.g. `["name","cr","source"]`), and `include_homebrew` (boolean, default false — when true also searches TheGiddyLimit/homebrew alongside official results). Results match on name, source abbreviation, pantheon/setting, and any top-level array-of-strings field in the data (e.g. `damageInflict`, `conditionInflict`, `environment`, `property` tags like `"Vst|EGW"`).
Selected tools support additional structured filter parameters:
| Tool | Extra Parameters |
|---|---|
| `spell_search` | `level` (int 0–9), `school` (full name: evocation, necromancy, etc.) |
| `monster_search` | `type` (beast, humanoid, undead…), `cr_max` (max CR inclusive: "1/4", "1/2", "5"…), `environment` (habitat substring: "underdark", "forest", "nine hells"…) |
| `item_search` | `rarity` (common, uncommon, rare, very rare, legendary, artifact), `type` (weapon, armor, wondrous…) |
| `classfeature_search` | `class_name` (e.g. "Wizard"), `level` (int 1–20) |
| `subclassfeature_search` | `class_name` (e.g. "Wizard"), `subclass_name` (e.g. "Abjurer"), `level` (int 1–20) |
| Tool | Content |
|---|---|
| `spell_search` | Spells |
| `monster_search` | Monsters and creatures |
| `item_search` | Magic and mundane items |
| `race_search` | Playable species / races |
| `background_search` | Character backgrounds |
| `feat_search` | Feats |
| `condition_search` | Conditions and diseases |
| `vehicle_search` | Vehicles and vessels |
| `object_search` | Objects |
| `trap_search` | Traps and hazards |
| `psionic_search` | Psionic powers and disciplines |
| `deck_search` | Decks (e.g. Deck of Many Things) |
| `reward_search` | Supernatural gifts and boons |
| `optfeature_search` | Optional class features and invocations |
| `table_search` | Random tables |
| `variantrule_search` | Variant rules |
| `deity_search` | Deities and gods (searchable by pantheon/setting) |
| `language_search` | Languages |
| `skill_search` | Skills |
| `sense_search` | Senses (darkvision, tremorsense, etc.) |
| `book_search` | Sourcebooks (name, ID, source, publication date) |
| `adventure_search` | Published adventures |
| `class_search` | Character classes (official + homebrew) |
| `subclass_search` | Subclasses and archetypes |
| `classfeature_search` | Class features (searchable by class and level) |
| `subclassfeature_search` | Subclass features (searchable by class, subclass, and level) |
### Get Tools (`*_get`)
Exact lookup by name with full fluff/description merged in. Accept `name`, optional `source`, and `ruleset`.
| Tool | Content |
|---|---|
| `spell_get` | Full spell entry with description |
| `monster_get` | Full stat block with lore |
| `item_get` | Full item entry with description |
| `race_get` | Full race entry with traits and fluff |
| `background_get` | Full background entry with fluff |
| `feat_get` | Full feat entry |
| `book_get` | Sourcebook metadata by name |
| `adventure_get` | Adventure metadata by name |
| `class_get` | Full class entry by name |
| `subclass_get` | Full subclass entry by name |
### Sourcebook & Adventure Content
| Tool | Description |
|---|---|
| `book_content_get` | Retrieve full prose from a sourcebook or adventure by source abbreviation (e.g. `SCC`, `EGW`, `SCC-CK`). Without `section`: returns a table of contents. With `section` only: returns that section's subsection names, or its full text if it has no subsections. With `section` + `subsection`: returns that subsection's text rendered as clean markdown. Matching is case-insensitive substring throughout. |
### Omnisearch
| Tool | Description |
|---|---|
| `omnisearch` | Search all 24 content types at once. Returns results tagged with `entityType`. Accepts `include_homebrew` (default **true** — homebrew is included by default). |
### DM Calculator Tools
All calculators are purely local — no network calls, no API key needed.
| Tool | Description |
|---|---|
| `cr_calculate` | Calculate a monster's Challenge Rating from its stats (HP, AC, DPR, attack bonus). Applies DMG Appendix B algorithm. Supports damage immunity (effective HP ×2), resistance (×1.5), and vulnerability (×0.5). Returns final CR, XP, and the defensive/offensive sub-CRs. |
| `cr_scale` | Look up the expected stat ranges (HP, AC, attack bonus, DPR, save DC, proficiency bonus) for a given CR from the DMG reference table. Useful for designing or adjusting homebrew monsters. |
| `encounter_build` | Evaluate encounter difficulty for a party vs a set of monsters. Supports both 2014 (easy/medium/hard/deadly, with monster-count XP multiplier) and 2024 (low/moderate/high, no multiplier) rulesets. Returns adjusted XP, difficulty label, and full threshold breakdown. |
| `loot_generate` | Generate individual treasure loot for a monster by CR, using the DMG individual treasure tables. Returns all possible outcomes for the CR bracket with probability percentages and average coin amounts. |
## Session Guidance
The server declares an `instructions` string on the MCP `initialize` handshake — a condensed tool decision guide (which tool to reach for, ruleset/homebrew defaults, class feature lookups, calculators). Spec-compliant clients fold this into context automatically on connect, with no user action required. This is what actually reaches Claude Desktop, since Desktop doesn't expose MCP-registered prompts as an invokable command the way Claude Code does.
The same content is also registered as an MCP prompt named `5eMCP`, for clients that support explicit prompt invocation — in Claude Code this shows up as `/mcp__<server-name>__5eMCP` (using whichever key you gave the server in your `mcpServers` config, e.g. `5etools` per the examples above). There's also a `help` tool with the same guidance, callable like any other tool from clients (including Claude Desktop) that don't support MCP prompts at all.
## Environment Variables
| Variable | Default | Description |
|---|---|---|
| `GITHUB_TOKEN` | — | Read-only GitHub PAT. Strongly recommended. |
| `DEFAULT_RULESET` | `"2024"` | Which ruleset to use (`"2024"` or `"2014"`). |
| `MANIFEST_TTL_SECONDS` | `3600` | How often to rebuild the manifest (seconds). |
| `CACHE_DIR` | `~/.cache/5emcp` | Disk cache location (local stdio mode). |
| `REDIS_URL` | — | Redis connection URL (e.g. `redis://localhost:6379`). When set and reachable, Redis is used instead of disk cache. Falls back to disk on connection failure. |
| `LOCAL_BASE_URL` | — | Base URL of a self-hosted 5etools static mirror (e.g. `https://5e.example.com`). When set, spell/monster/item/etc. content for the `2024` and `2014` ruleset repos is fetched from this mirror instead of `raw.githubusercontent.com` — faster, no GitHub rate limit for content fetches. Ignored if `LOCAL_DATA_DIR` is also set. Manifest indexing (file listing) still uses the GitHub Contents API, since a static mirror has no equivalent listing endpoint. Homebrew content is never redirected. |
| `LOCAL_DATA_DIR` | — | Filesystem path to a local 5etools `data/` directory (e.g. `/opt/5etools/data`) — typically used when the MCP server runs colocated with a self-hosted mirror. When set, **both** manifest indexing and content fetching read directly from disk, bypassing GitHub entirely for core ruleset content (no rate limit, no network round-trip at all). Homebrew still goes through the GitHub Contents API regardless, since self-hosted mirrors don't bundle it — that call degrades gracefully (logged, non-fatal) if it hits a rate limit. Assumes the directory matches the ruleset(s) you query; a single local mirror generally only reflects one ruleset. |
| `PORT` | `3000` | Port for the HTTP transport (`npm start` / `dist/http.js`). |
| `MCP_HTTP_TOKEN` | — | Bearer token required on the HTTP transport's `/mcp` endpoint. If unset, the endpoint is unauthenticated — fine on a private network, not recommended for public exposure. |
## HTTP Transport
In addition to stdio (used by Claude Desktop/Code/Cursor above), the server supports the [MCP Streamable HTTP transport](https://modelcontextprotocol.io/), useful for running the server remotely (e.g. colocated with a self-hosted 5etools mirror) and connecting to it from clients that can't spawn a local process.
Colocated with a self-hosted mirror (reads the mirror's `data/` directory straight off disk — fastest, no GitHub calls for core content):
```
LOCAL_DATA_DIR=/opt/5etools/data MCP_HTTP_TOKEN=your-secret npm start
```
Or pointing at a mirror over HTTP (e.g. the MCP server runs elsewhere than the mirror):
```
LOCAL_BASE_URL=https://5e.example.com MCP_HTTP_TOKEN=your-secret npm start
```
`npm start` runs the TypeScript source directly via `tsx` — no separate build step needed. If you do want a compiled build (e.g. for `npm run build:mcpb`), note that `tsc` is memory-hungry; on RAM-constrained hosts it can OOM, in which case `npm start` is the way to go anyway.
This starts a stateless HTTP server:
- `POST /mcp` — MCP JSON-RPC endpoint (Streamable HTTP transport, one server instance per request)
- `GET /health` — health check, returns `{"status":"ok","service":"5eMCP"}`
If `MCP_HTTP_TOKEN` is set, requests to `/mcp` must include `Authorization: Bearer <token>`; `/health` is always open.
### Connecting Claude Desktop / claude.ai to a Remote Instance
Once the HTTP server is deployed and reachable, connect to it as a **Custom Connector** rather than editing `claude_desktop_config.json` — that file is for stdio servers that Claude spawns as a local process, which doesn't apply to a server running elsewhere:
1. Claude Desktop (or claude.ai) → **Settings → Connectors → Add custom connector**
2. Enter your server's URL, e.g. `https://5emcp.example.com/mcp`
3. Click **Add**
If `MCP_HTTP_TOKEN` is unset, that's all — the connector works immediately. Note that the Custom Connector UI's "Advanced settings" are built for OAuth (Client ID/Secret), not a raw static bearer token, so `MCP_HTTP_TOKEN` isn't directly pluggable there. If you need auth on a Custom Connector, put a reverse proxy in front (e.g. Cloudflare Access, Caddy with `basicauth`) rather than relying on `MCP_HTTP_TOKEN` alone.
## Ruleset Support
Most tools accept `ruleset: "2024" | "2014"`:
- `"2024"` → `5etools-mirror-3/5etools-src` (current rules)
- `"2014"` → `5etools-mirror-3/5etools-2014-src` (legacy rules)
Exceptions: `cr_calculate`, `cr_scale`, and `loot_generate` are ruleset-agnostic (the DMG reference tables they use don't differ by edition) — of the DM calculators, only `encounter_build` takes a `ruleset` param. `help` takes no parameters at all.
## Development
```bash
npm run dev # Run without compile step (tsx)
npm run build # Compile TypeScript
npm run typecheck # Type-check without emitting
npm test # Run tests (Vitest)
npm run lint # ESLint
```
This project uses TDD. Tests are written before implementation. See `AGENTS.md` for the full workflow including mandatory reviewer agent approval before commits.
```bash
# First-time setup: install git hooks
./.githooks/install.sh
```
## Architecture
```mermaid
flowchart TD
Client["MCP Client\n(Claude Desktop / Claude Code / Cursor)"]
Server["MCP Server (Node.js)"]
Manifest["Manifest Layer\n1-hour TTL refresh"]
Cache["Cache Layer\nSHA-keyed disk or Redis"]
Translation["Translation Layer\ntag resolver · typed handlers · passthrough"]
Calculators["Calculators\nCR · encounter · loot · scaling"]
GH["GitHub Contents API"]
Raw["raw.githubusercontent.com"]
Redis[("Redis / Disk")]
Client -->|stdio| Server
Server --> Manifest
Server --> Cache
Server --> Translation
Server --> Calculators
Manifest -->|"index all files"| GH
Cache -->|"fetch on miss"| Raw
Cache <-->|"read / write"| Redis
```
## Legal
5etools data is fetched live from public GitHub repositories. This server does not store or redistribute any content. The GitHub API rate limit applies. A GitHub token is required for sustained use.
Calculator logic is ported from 5etools' MIT-licensed JavaScript source.
## Credits
D20 icon by [Delapouite](https://delapouite.com) via [game-icons.net](https://game-icons.net), licensed [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
TDQS
Scored across 46 tools
The search/get pairs for each content type are clearly distinguished, and omnisearch is explicitly positioned as the fallback when the content type is unknown. However, book_get vs book_content_get and class_get vs classfeature_search have somewhat overlapping boundaries that could cause misselection.
The vast majority of tools follow a consistent contenttype_search/contenttype_get convention, making the names highly predictable. Utility tools like help, manifest_status, fetch_content, and cr_calculate break the pattern, but they form a recognizable secondary category.
At 46 tools, this is far above the 25+ threshold and creates a heavy selection burden on the agent. The typed search/get pairs are comprehensive but could be consolidated into a smaller set of parameterized tools, and the utility tools could reasonably be separated.
The surface covers nearly every meaningful D&D content type with search and exact-get access, plus class feature resolution, book content navigation, DM calculators, and a fetch_content fallback. The manifest_status and fallback tools ensure even untracked content types are reachable, so there are no significant dead ends.