@iamjameslennon/ddb-mcp 2.3.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 +520 -0
- package/dist/auth.d.ts +3 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +69 -0
- package/dist/auth.js.map +1 -0
- package/dist/browser.d.ts +9 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +68 -0
- package/dist/browser.js.map +1 -0
- package/dist/cache.d.ts +18 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +45 -0
- package/dist/cache.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +654 -0
- package/dist/index.js.map +1 -0
- package/dist/open5e.d.ts +74 -0
- package/dist/open5e.d.ts.map +1 -0
- package/dist/open5e.js +455 -0
- package/dist/open5e.js.map +1 -0
- package/dist/session-fetch.d.ts +35 -0
- package/dist/session-fetch.d.ts.map +1 -0
- package/dist/session-fetch.js +155 -0
- package/dist/session-fetch.js.map +1 -0
- package/dist/tools/campaign.d.ts +4 -0
- package/dist/tools/campaign.d.ts.map +1 -0
- package/dist/tools/campaign.js +72 -0
- package/dist/tools/campaign.js.map +1 -0
- package/dist/tools/character.d.ts +21 -0
- package/dist/tools/character.d.ts.map +1 -0
- package/dist/tools/character.js +1128 -0
- package/dist/tools/character.js.map +1 -0
- package/dist/tools/encounter.d.ts +22 -0
- package/dist/tools/encounter.d.ts.map +1 -0
- package/dist/tools/encounter.js +453 -0
- package/dist/tools/encounter.js.map +1 -0
- package/dist/tools/library.d.ts +4 -0
- package/dist/tools/library.d.ts.map +1 -0
- package/dist/tools/library.js +112 -0
- package/dist/tools/library.js.map +1 -0
- package/dist/tools/monster.d.ts +27 -0
- package/dist/tools/monster.d.ts.map +1 -0
- package/dist/tools/monster.js +378 -0
- package/dist/tools/monster.js.map +1 -0
- package/dist/tools/navigate.d.ts +5 -0
- package/dist/tools/navigate.d.ts.map +1 -0
- package/dist/tools/navigate.js +67 -0
- package/dist/tools/navigate.js.map +1 -0
- package/dist/tools/reference.d.ts +58 -0
- package/dist/tools/reference.d.ts.map +1 -0
- package/dist/tools/reference.js +850 -0
- package/dist/tools/reference.js.map +1 -0
- package/dist/tools/search.d.ts +4 -0
- package/dist/tools/search.d.ts.map +1 -0
- package/dist/tools/search.js +64 -0
- package/dist/tools/search.js.map +1 -0
- package/dist/tools/treasure.d.ts +12 -0
- package/dist/tools/treasure.d.ts.map +1 -0
- package/dist/tools/treasure.js +522 -0
- package/dist/tools/treasure.js.map +1 -0
- package/dist/utils.d.ts +5 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +21 -0
- package/dist/utils.js.map +1 -0
- package/package.json +36 -0
package/README.md
ADDED
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
# D&D Beyond MCP Server
|
|
2
|
+
|
|
3
|
+
A [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server that gives Claude direct access to your D&D Beyond account — characters, campaigns, sourcebooks, spells, monsters, rules, encounter planning, treasure generation, and more.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## For Players
|
|
8
|
+
|
|
9
|
+
Use Claude as a session companion that knows your character as well as you do.
|
|
10
|
+
|
|
11
|
+
**Know your character inside out**
|
|
12
|
+
```
|
|
13
|
+
Give me a full summary of my character Torvin
|
|
14
|
+
```
|
|
15
|
+
```
|
|
16
|
+
What spells do I have prepared right now?
|
|
17
|
+
```
|
|
18
|
+
```
|
|
19
|
+
Which of my spells can I cast as rituals without using a slot?
|
|
20
|
+
```
|
|
21
|
+
```
|
|
22
|
+
What's my passive perception and investigation?
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Look up your abilities mid-session**
|
|
26
|
+
```
|
|
27
|
+
How does Uncanny Dodge work on Kestrel?
|
|
28
|
+
```
|
|
29
|
+
```
|
|
30
|
+
What does Hunter's Mark do — can I move it as a bonus action?
|
|
31
|
+
```
|
|
32
|
+
```
|
|
33
|
+
Show me the full text of the Alert feat on my character
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Check the rules without leaving the table**
|
|
37
|
+
```
|
|
38
|
+
What are the rules for grappling?
|
|
39
|
+
```
|
|
40
|
+
```
|
|
41
|
+
What does the Stunned condition do?
|
|
42
|
+
```
|
|
43
|
+
```
|
|
44
|
+
How does concentration work?
|
|
45
|
+
```
|
|
46
|
+
```
|
|
47
|
+
Remind me of the rules for death saving throws
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Look up spells and equipment in the compendium**
|
|
51
|
+
```
|
|
52
|
+
What does Silvery Barbs do?
|
|
53
|
+
```
|
|
54
|
+
```
|
|
55
|
+
Show me the stats for a Flame Tongue longsword
|
|
56
|
+
```
|
|
57
|
+
```
|
|
58
|
+
What's the difference between a Shortbow and a Longbow?
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Read your sourcebooks**
|
|
62
|
+
```
|
|
63
|
+
Show me the table of contents for the Player's Handbook
|
|
64
|
+
```
|
|
65
|
+
```
|
|
66
|
+
Read the Ranger class section from the 2024 Player's Handbook
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## For Dungeon Masters & Game Masters
|
|
72
|
+
|
|
73
|
+
Use Claude to plan sessions, build encounters, and run the table faster.
|
|
74
|
+
|
|
75
|
+
**Plan and rate encounters**
|
|
76
|
+
```
|
|
77
|
+
How hard is this encounter for my party of 4 level 5 players:
|
|
78
|
+
2 trolls and a hill giant?
|
|
79
|
+
```
|
|
80
|
+
```
|
|
81
|
+
What CR should I target for a Moderate difficulty encounter
|
|
82
|
+
for my party of 3 level 8 characters?
|
|
83
|
+
```
|
|
84
|
+
```
|
|
85
|
+
Give me CR targets for a solo boss fight vs my party of 5 level 10s
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Generate treasure**
|
|
89
|
+
```
|
|
90
|
+
Roll a hoard for a CR 15 encounter for a level 12 party
|
|
91
|
+
```
|
|
92
|
+
```
|
|
93
|
+
Generate individual treasure for 4 bandits and their CR 5 captain
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Look up monsters on the fly**
|
|
97
|
+
```
|
|
98
|
+
Show me the full stat block for a Young Red Dragon
|
|
99
|
+
```
|
|
100
|
+
```
|
|
101
|
+
Find all Large undead with CR 5 or lower
|
|
102
|
+
```
|
|
103
|
+
```
|
|
104
|
+
What monsters have the Legendary Resistance trait?
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Manage your campaign**
|
|
108
|
+
```
|
|
109
|
+
List all the characters in my campaign
|
|
110
|
+
```
|
|
111
|
+
```
|
|
112
|
+
Show me the details for campaign 6709239 — who's playing what?
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Read sourcebooks for prep**
|
|
116
|
+
```
|
|
117
|
+
Read the Lair Actions section from the Monster Manual
|
|
118
|
+
```
|
|
119
|
+
```
|
|
120
|
+
What does the DMG say about setting DCs for ability checks?
|
|
121
|
+
```
|
|
122
|
+
```
|
|
123
|
+
Show me the treasure tables chapter from the Dungeon Master's Guide
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Rule lookups mid-session**
|
|
127
|
+
```
|
|
128
|
+
What are the rules for improvised weapons?
|
|
129
|
+
```
|
|
130
|
+
```
|
|
131
|
+
How does the Exhaustion condition work in 2024 rules?
|
|
132
|
+
```
|
|
133
|
+
```
|
|
134
|
+
Can a character use the Help action to assist with a skill check?
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Full Tool Reference
|
|
140
|
+
|
|
141
|
+
### Character Tools
|
|
142
|
+
|
|
143
|
+
| Tool | Description |
|
|
144
|
+
|------|-------------|
|
|
145
|
+
| `ddb_login` | Authenticate with D&D Beyond (Wizards ID). Run once — session is saved to disk and reused. |
|
|
146
|
+
| `ddb_list_characters` | List all characters in your account with ID, level, race, and class. |
|
|
147
|
+
| `ddb_get_character` | Parse a character into a compact, readable sheet. Covers all stats, skills, spells, actions, and inventory. Use `sections` to get just `summary`, `combat`, `spells`, `inventory`, `features`, or `full`. Accepts name (fuzzy matched) or numeric ID. |
|
|
148
|
+
| `ddb_get_character_raw` | Fetch raw character JSON from the D&D Beyond API. Use `ddb_get_character` for all normal use. |
|
|
149
|
+
| `ddb_download_character` | Save a character's full JSON to a local file (must be under ~/Downloads or ~/Documents). |
|
|
150
|
+
| `ddb_character_lookup` | Look up the full description of a spell, feat, class feature, racial trait, or item on a character sheet. Supports partial and fuzzy name matching. |
|
|
151
|
+
|
|
152
|
+
### Campaign Tools
|
|
153
|
+
|
|
154
|
+
| Tool | Description |
|
|
155
|
+
|------|-------------|
|
|
156
|
+
| `ddb_list_campaigns` | List all campaigns you're part of (as DM or player). |
|
|
157
|
+
| `ddb_get_campaign` | Fetch campaign details — DM, description, and active characters with their levels. |
|
|
158
|
+
|
|
159
|
+
### Monster & Encounter Tools
|
|
160
|
+
|
|
161
|
+
| Tool | Description |
|
|
162
|
+
|------|-------------|
|
|
163
|
+
| `ddb_search_monsters` | Search the D&D Beyond monster compendium by name, CR, type, or size. |
|
|
164
|
+
| `ddb_get_monster` | Get the full formatted stat block for a monster by name. |
|
|
165
|
+
| `ddb_rate_encounter` | Rate encounter difficulty for a party. Defaults to 2024 XDMG rules (Low/Moderate/High). Set `rules_edition: "2014"` for classic DMG (Easy/Medium/Hard/Deadly). Monsters are looked up automatically. |
|
|
166
|
+
| `ddb_encounter_cr` | Given a party and target difficulty, returns recommended CRs broken down by encounter shape: solo boss, duo, squad, horde. |
|
|
167
|
+
| `ddb_roll_treasure` | Generate treasure using 2024 XDMG tables. `hoard` rolls once at the highest CR and includes magic items; `individual` rolls per monster. |
|
|
168
|
+
|
|
169
|
+
### Spell & Reference Tools
|
|
170
|
+
|
|
171
|
+
| Tool | Description |
|
|
172
|
+
|------|-------------|
|
|
173
|
+
| `ddb_search_spells` | Search the full D&D Beyond spell compendium by name, level, school, concentration, or ritual. |
|
|
174
|
+
| `ddb_get_spell` | Get the full description of any spell by name. |
|
|
175
|
+
| `ddb_search_equipment` | Search the item/equipment compendium by name, rarity, or type. Covers mundane weapons, armour, and magic items. |
|
|
176
|
+
| `ddb_get_equipment` | Get the full stats and description of any item — weapon damage, properties, range, STR requirement, etc. |
|
|
177
|
+
| `ddb_get_condition` | Look up the rules text for a condition (Poisoned, Stunned, Grappled, etc.). No login required. |
|
|
178
|
+
| `ddb_search_rules` | Search all 45 SRD rules sections by keyword. No login required. |
|
|
179
|
+
| `ddb_get_rules` | Read the full text of any SRD rules section (Spellcasting, Attacking, Combat, Multiclassing, Rest, Environment, etc.). Supports `query` to jump to a keyword within long sections. No login required. |
|
|
180
|
+
| `ddb_search_races` | Search all races and subraces in the D&D Beyond compendium (including homebrew). |
|
|
181
|
+
| `ddb_search_classes` | Search all classes with hit die, spellcasting info, and subclasses. |
|
|
182
|
+
| `ddb_search_backgrounds` | Search all backgrounds (including homebrew). |
|
|
183
|
+
| `ddb_search_feats` | Search feats by name or prerequisite. |
|
|
184
|
+
| `ddb_search_class_features` | Search class features by name, class, or level gained. |
|
|
185
|
+
| `ddb_search_racial_traits` | Search racial traits by name or race. |
|
|
186
|
+
|
|
187
|
+
### Library & Navigation Tools
|
|
188
|
+
|
|
189
|
+
| Tool | Description |
|
|
190
|
+
|------|-------------|
|
|
191
|
+
| `ddb_list_library` | List all sourcebooks you own, purchased, or have shared with you. |
|
|
192
|
+
| `ddb_read_book` | Read content from an owned sourcebook by book slug and optional chapter. |
|
|
193
|
+
| `ddb_search_site` | Search D&D Beyond by keyword across spells, monsters, items, races, classes, and feats. |
|
|
194
|
+
| `ddb_navigate` | Navigate to any D&D Beyond URL and return its text content. Keeps the browser open for follow-up calls. |
|
|
195
|
+
| `ddb_interact` | Click, fill (requires `confirm_fill: true`), or screenshot the currently loaded page. |
|
|
196
|
+
| `ddb_get_page` | Return the text content of the currently loaded page. |
|
|
197
|
+
| `ddb_close_browser` | Close the background browser window. Call this when done with `ddb_navigate`, `ddb_interact`, or `ddb_get_page`. |
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Prerequisites
|
|
202
|
+
|
|
203
|
+
- [Node.js](https://nodejs.org) 18 or later
|
|
204
|
+
- [Claude Code](https://claude.ai/claude-code) CLI
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Installation
|
|
209
|
+
|
|
210
|
+
### Option A — Install directly from GitHub (recommended)
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
npm install -g "https://github.com/iamjameslennon/ddb-mcp/archive/refs/heads/main.tar.gz"
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Then install the browser:
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
npx playwright install chromium
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Find the install path and register with Claude Code:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
npm root -g
|
|
226
|
+
# outputs something like /usr/local/lib/node_modules
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
claude mcp add dndbeyond node /usr/local/lib/node_modules/ddb-mcp/dist/index.js
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
### Option B — Clone and build manually
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
git clone https://github.com/iamjameslennon/ddb-mcp.git
|
|
239
|
+
cd ddb-mcp
|
|
240
|
+
npm ci
|
|
241
|
+
npx playwright install chromium
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Register with Claude Code:
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
claude mcp add dndbeyond node /absolute/path/to/ddb-mcp/dist/index.js
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Or edit `~/.claude/settings.json` manually:
|
|
251
|
+
|
|
252
|
+
```json
|
|
253
|
+
{
|
|
254
|
+
"mcpServers": {
|
|
255
|
+
"dndbeyond": {
|
|
256
|
+
"command": "node",
|
|
257
|
+
"args": ["/absolute/path/to/ddb-mcp/dist/index.js"]
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## First-time login
|
|
266
|
+
|
|
267
|
+
Run `ddb_login` once to authenticate:
|
|
268
|
+
|
|
269
|
+
```
|
|
270
|
+
ddb_login
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
A browser window will open and navigate to the D&D Beyond login page. Complete the login using your Wizards ID account. Once redirected back to D&D Beyond, your session is automatically saved to `~/.config/ddb-mcp/session.json` and reused on all future calls — no browser needed again until the session expires.
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## Finding character and campaign IDs
|
|
278
|
+
|
|
279
|
+
- **Character ID**: the number in the character URL — `dndbeyond.com/characters/140476673`
|
|
280
|
+
- **Campaign ID**: the number in the campaign URL — `dndbeyond.com/campaigns/6709239`
|
|
281
|
+
|
|
282
|
+
You can also use `ddb_list_characters` and `ddb_list_campaigns` to get IDs without leaving Claude.
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## Book slugs for `ddb_read_book`
|
|
287
|
+
|
|
288
|
+
Use `ddb_list_library` to get the slug for any book you own. Common examples:
|
|
289
|
+
|
|
290
|
+
| Book | Slug |
|
|
291
|
+
|------|------|
|
|
292
|
+
| Player's Handbook (2024) | `dnd/phb-2024` |
|
|
293
|
+
| Dungeon Master's Guide (2024) | `dnd/dmg-2024` |
|
|
294
|
+
| Monster Manual (2024) | `dnd/mm-2024` |
|
|
295
|
+
| Player's Handbook (2014) | `dnd/phb-2014` |
|
|
296
|
+
|
|
297
|
+
To read a specific chapter, pass the chapter path after the book slug:
|
|
298
|
+
|
|
299
|
+
```
|
|
300
|
+
Read the Ranger class section from the 2024 Player's Handbook
|
|
301
|
+
→ book_slug: "dnd/phb-2024", chapter_slug: "character-classes/ranger"
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## Sample `ddb_get_character` output
|
|
307
|
+
|
|
308
|
+
The output below is real — truncated slightly for length. It shows a Tiefling Wizard 2 with prepared spells, unprepared rituals, and spells from racial traits and feats.
|
|
309
|
+
|
|
310
|
+
```
|
|
311
|
+
═══════════════════════════════════════
|
|
312
|
+
Claude Skamos
|
|
313
|
+
Tiefling | Wizard 2 | Level 2
|
|
314
|
+
Background: Sage | XP: 0
|
|
315
|
+
Inspiration: No
|
|
316
|
+
═══════════════════════════════════════
|
|
317
|
+
|
|
318
|
+
HP: 10/10 Temp HP: — Prof Bonus: +2
|
|
319
|
+
Hit Dice: 2d6 (2 remaining)
|
|
320
|
+
AC: 10 Initiative: +0 Speed: 30 ft.
|
|
321
|
+
Death Saves: Successes 0/3 Failures 0/3
|
|
322
|
+
|
|
323
|
+
ABILITY SCORES
|
|
324
|
+
STR 8 (-1) DEX 10 (+0) CON 10 (+0) INT 17 (+3) WIS 15 (+2) CHA 14 (+2)
|
|
325
|
+
|
|
326
|
+
SAVING THROWS
|
|
327
|
+
STR -1 DEX +0 CON +0 INT +5* WIS +4* CHA +2
|
|
328
|
+
(* proficient)
|
|
329
|
+
|
|
330
|
+
SKILLS
|
|
331
|
+
Acrobatics (DEX) +0
|
|
332
|
+
Animal Handling (WIS) +2
|
|
333
|
+
Arcana (INT) +5 *
|
|
334
|
+
Athletics (STR) -1
|
|
335
|
+
Deception (CHA) +2
|
|
336
|
+
History (INT) +7 **
|
|
337
|
+
Insight (WIS) +2
|
|
338
|
+
Investigation (INT) +5 *
|
|
339
|
+
Perception (WIS) +4 *
|
|
340
|
+
... (all 18 skills shown)
|
|
341
|
+
(* proficient, ** expertise)
|
|
342
|
+
|
|
343
|
+
SENSES
|
|
344
|
+
Passive Perception: 14 Passive Investigation: 15 Passive Insight: 12
|
|
345
|
+
Darkvision 60 ft.
|
|
346
|
+
|
|
347
|
+
PROFICIENCIES & TRAINING
|
|
348
|
+
Armor: None
|
|
349
|
+
Weapons: Simple Weapons
|
|
350
|
+
Tools: Calligraphers supplies
|
|
351
|
+
Languages: Common, Common sign language, Draconic
|
|
352
|
+
|
|
353
|
+
DEFENSES
|
|
354
|
+
Resistances: Fire
|
|
355
|
+
Immunities: (none)
|
|
356
|
+
Vulnerabilities: (none)
|
|
357
|
+
CONDITIONS: (none)
|
|
358
|
+
|
|
359
|
+
FEATS (2)
|
|
360
|
+
• Magic Initiate (Wizard): Two Cantrips. You learn two cantrips of your choice...
|
|
361
|
+
• Sage Ability Score Improvements: ...
|
|
362
|
+
|
|
363
|
+
CLASS FEATURES
|
|
364
|
+
• Core Wizard Traits (Wizard 1)
|
|
365
|
+
• Spellcasting (Wizard 1)
|
|
366
|
+
• Ritual Adept (Wizard 1)
|
|
367
|
+
• Arcane Recovery (Wizard 1)
|
|
368
|
+
• Scholar (Wizard 2)
|
|
369
|
+
|
|
370
|
+
ACTIONS
|
|
371
|
+
• Dagger +2 to hit 1d4 piercing reach 5 ft. Finesse, Light, Thrown, Nick
|
|
372
|
+
• Quarterstaff +1 to hit 1d6-1 bludgeoning reach 5 ft. Versatile, Topple
|
|
373
|
+
|
|
374
|
+
BONUS ACTIONS
|
|
375
|
+
(none)
|
|
376
|
+
|
|
377
|
+
REACTIONS
|
|
378
|
+
• Opportunity Attack
|
|
379
|
+
• Shield (spell, 1st-level slot)
|
|
380
|
+
|
|
381
|
+
LIMITED USE
|
|
382
|
+
• Arcane Recovery 0 used / 1 max (Long Rest)
|
|
383
|
+
|
|
384
|
+
SPELLCASTING
|
|
385
|
+
Wizard: INT Spell Attack: +5 Save DC: 13
|
|
386
|
+
|
|
387
|
+
SPELL SLOTS
|
|
388
|
+
Level 1: 3/3
|
|
389
|
+
|
|
390
|
+
SPELLS
|
|
391
|
+
Cantrips: Mage Hand, Light, Message
|
|
392
|
+
Spells: Comprehend Languages (L1 [ritual]), Detect Magic (L1 [ritual]),
|
|
393
|
+
Find Familiar (L1 [ritual]), Magic Missile (L1), Shield (L1),
|
|
394
|
+
Mage Armor (L1), Chromatic Orb (L1), Grease (L1), ...
|
|
395
|
+
From Racial Trait: Fire Bolt, Thaumaturgy
|
|
396
|
+
From Feat: Dancing Lights, Mending, Tasha's Hideous Laughter (L1)
|
|
397
|
+
|
|
398
|
+
INVENTORY
|
|
399
|
+
Spellbook, Parchment ×18, Backpack, Calligrapher's Supplies, Robe, ...
|
|
400
|
+
|
|
401
|
+
ATTUNEMENT: 0/3 slots used
|
|
402
|
+
CURRENCY: 34gp, 7sp
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
Key things `ddb_get_character` handles correctly:
|
|
406
|
+
|
|
407
|
+
- **Wizards**: only shows prepared spells and unprepared rituals (castable from spellbook without a slot) — not the full spellbook
|
|
408
|
+
- **Spell sources**: racial traits, class features, feats, and magic items are all labelled separately
|
|
409
|
+
- **Ritual spells**: marked with `[ritual]` in the spell list
|
|
410
|
+
- **Actions**: weapons include to-hit bonus, damage, range, and mastery properties; magic item bonuses (+1/+2/+3) are applied to both hit and damage
|
|
411
|
+
- **Bonus actions / reactions**: spell-based bonus actions and reactions (Healing Word, Shield, Hunter's Mark, Hellish Rebuke, etc.) appear in the correct section with slot cost
|
|
412
|
+
- **AC**: correctly calculates Unarmored Defense for Barbarians and Monks; selects best armor when multiple items are equipped
|
|
413
|
+
- **Skills**: Jack of All Trades applied for Bards; expertise marked with `**`
|
|
414
|
+
- **Initiative**: Alert feat and Jack of All Trades bonuses applied correctly, with 2014/2024 rule differences handled
|
|
415
|
+
- **Multiclass**: hit dice shown per class, spell slots computed from combined caster levels
|
|
416
|
+
|
|
417
|
+
---
|
|
418
|
+
|
|
419
|
+
## Upgrading
|
|
420
|
+
|
|
421
|
+
To upgrade to the latest release, run the install command again:
|
|
422
|
+
|
|
423
|
+
```bash
|
|
424
|
+
npm install -g "https://github.com/iamjameslennon/ddb-mcp/archive/refs/heads/main.tar.gz"
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
To install a specific tagged version:
|
|
428
|
+
|
|
429
|
+
```bash
|
|
430
|
+
npm install -g "https://github.com/iamjameslennon/ddb-mcp/archive/refs/tags/v2.0.0.tar.gz"
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
Then restart Claude Code and run `/mcp` to reconnect the server.
|
|
434
|
+
|
|
435
|
+
---
|
|
436
|
+
|
|
437
|
+
## Session storage
|
|
438
|
+
|
|
439
|
+
Your session is saved to `~/.config/ddb-mcp/session.json`. This file contains browser cookies from your D&D Beyond login. Keep it private — it grants access to your account.
|
|
440
|
+
|
|
441
|
+
To log out or reset your session:
|
|
442
|
+
|
|
443
|
+
```bash
|
|
444
|
+
rm ~/.config/ddb-mcp/session.json
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
---
|
|
448
|
+
|
|
449
|
+
## Troubleshooting
|
|
450
|
+
|
|
451
|
+
**"Not logged in" or 403 errors**
|
|
452
|
+
Your session has expired. Run `ddb_login` to re-authenticate.
|
|
453
|
+
|
|
454
|
+
**Chromium not found / browser won't launch**
|
|
455
|
+
```bash
|
|
456
|
+
npx playwright install chromium
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
**Character returns 403 or "private"**
|
|
460
|
+
The character is set to private on D&D Beyond. You must be logged in as the owner, or the owner must make it public.
|
|
461
|
+
|
|
462
|
+
**MCP server not appearing in Claude Code**
|
|
463
|
+
Run `/mcp` in Claude Code to reconnect. If it still doesn't appear, verify the path in `claude mcp list` points to the correct `dist/index.js`.
|
|
464
|
+
|
|
465
|
+
**Server crashes on startup**
|
|
466
|
+
Make sure you're running Node.js 18 or later: `node --version`.
|
|
467
|
+
|
|
468
|
+
---
|
|
469
|
+
|
|
470
|
+
## Development
|
|
471
|
+
|
|
472
|
+
```bash
|
|
473
|
+
# Install dependencies (always use npm ci — not npm install)
|
|
474
|
+
npm ci
|
|
475
|
+
|
|
476
|
+
# Run in development mode (no build step needed)
|
|
477
|
+
npm run dev
|
|
478
|
+
|
|
479
|
+
# Build
|
|
480
|
+
npm run build
|
|
481
|
+
|
|
482
|
+
# Watch mode
|
|
483
|
+
npm run build:watch
|
|
484
|
+
|
|
485
|
+
# Run tests
|
|
486
|
+
npm test
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
---
|
|
490
|
+
|
|
491
|
+
## Credits
|
|
492
|
+
|
|
493
|
+
Forked from [ddb-mcp/ddb-mcp](https://github.com/ddb-mcp/ddb-mcp). The monster, reference, and session-workflow tooling was inspired by [dndbeyond-mcp](https://www.npmjs.com/package/dndbeyond-mcp). This fork significantly expands character parsing, adds session-based API fetching, and introduces compendium, reference, encounter, and treasure tools.
|
|
494
|
+
|
|
495
|
+
**Character parsing improvements:**
|
|
496
|
+
|
|
497
|
+
- **Complete stat block**: saving throws, all 18 skills with proficiency/expertise markers, senses (passive scores + darkvision/tremorsense), proficiencies & training (armor, weapons, tools, languages), damage resistances/immunities/vulnerabilities, conditions, inspiration, death saves, hit dice per class
|
|
498
|
+
- **Accurate AC**: Unarmored Defense calculated correctly for Barbarians (10 + DEX + CON) and Monks (10 + DEX + WIS); when multiple armors are equipped, picks the best-AC combination; shield stacks additively
|
|
499
|
+
- **Correct spell display for Wizards**: shows only prepared spells and unprepared rituals — not the full spellbook
|
|
500
|
+
- **All spell sources**: racial traits, class features, feats, and magic items each labelled separately
|
|
501
|
+
- **Ritual spells**: marked with `[ritual]`
|
|
502
|
+
- **Full action classification**: bonus actions and reactions list both class features and spell-based entries with slot cost
|
|
503
|
+
- **Magic weapon bonuses**: enhancement bonuses (+1/+2/+3) applied to both to-hit and damage
|
|
504
|
+
- **Weapon properties and mastery**: all properties and mastery tags shown per weapon
|
|
505
|
+
- **Skill and initiative accuracy**: Jack of All Trades and Alert feat handled correctly for both 2014 and 2024 rules
|
|
506
|
+
- **Multiclass support**: hit dice per class, spell slots from combined caster levels
|
|
507
|
+
|
|
508
|
+
**Other improvements:**
|
|
509
|
+
|
|
510
|
+
- Session-based API fetching (no browser required after login for most tools)
|
|
511
|
+
- Encounter difficulty rating for both 2024 XDMG and 2014 DMG rules
|
|
512
|
+
- Treasure generation using 2024 XDMG tables
|
|
513
|
+
- SRD rules search and retrieval (no login required)
|
|
514
|
+
- Security: path constraints on file writes, slug validation, session file permissions (0600), prompt injection gate on browser form fills
|
|
515
|
+
|
|
516
|
+
---
|
|
517
|
+
|
|
518
|
+
## License
|
|
519
|
+
|
|
520
|
+
MIT
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAQ,MAAM,YAAY,CAAC;AAGlD,wBAAsB,KAAK,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAsDpE"}
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { saveSession, getPage } from "./browser.js";
|
|
2
|
+
export async function login(context) {
|
|
3
|
+
const page = await getPage(context);
|
|
4
|
+
// Navigate to D&D Beyond and check if already logged in
|
|
5
|
+
await page.goto("https://www.dndbeyond.com", { waitUntil: "domcontentloaded", timeout: 30000 });
|
|
6
|
+
await page.waitForSelector(".c-site-header, .top-header-bar, .ddb-site-header", { timeout: 5000 }).catch(() => { });
|
|
7
|
+
const landedUrl = page.url();
|
|
8
|
+
const onDdb = landedUrl.includes("dndbeyond.com") && !landedUrl.includes("/login") && !landedUrl.includes("/sign-in");
|
|
9
|
+
if (onDdb && await checkLoggedInOnCurrentPage(page)) {
|
|
10
|
+
// Save session to disk even when already logged in, so it persists across restarts
|
|
11
|
+
await saveSession(context);
|
|
12
|
+
return "Already logged in. Session is active.";
|
|
13
|
+
}
|
|
14
|
+
// Navigate directly to the DDB login page — this ensures the return_to URL
|
|
15
|
+
// is set correctly to the main DDB site (not a support page).
|
|
16
|
+
// The browser window is non-headless, so the user can complete login manually.
|
|
17
|
+
console.error("[ddb-mcp] Opening D&D Beyond login page. Please complete login in the browser window.");
|
|
18
|
+
await page.goto("https://www.dndbeyond.com/login", { waitUntil: "domcontentloaded", timeout: 30000 });
|
|
19
|
+
await page.waitForTimeout(1000);
|
|
20
|
+
// Poll until the browser lands back on www.dndbeyond.com (not the login page).
|
|
21
|
+
// This handles the full Wizards ID redirect flow automatically.
|
|
22
|
+
try {
|
|
23
|
+
await page.waitForFunction(() => {
|
|
24
|
+
const url = window.location.href;
|
|
25
|
+
return (url.startsWith("https://www.dndbeyond.com") &&
|
|
26
|
+
!url.includes("/login") &&
|
|
27
|
+
!url.includes("/sign-in"));
|
|
28
|
+
}, { timeout: 180000, polling: 2000 });
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
throw new Error("Login timed out. Please complete login in the browser window and try again.");
|
|
32
|
+
}
|
|
33
|
+
await page.waitForTimeout(2000);
|
|
34
|
+
// Verify login succeeded by checking the current page
|
|
35
|
+
const loggedIn = await checkLoggedInOnCurrentPage(page);
|
|
36
|
+
if (!loggedIn) {
|
|
37
|
+
throw new Error("Login may not have completed successfully. Please try again or check if DDB requires additional verification.");
|
|
38
|
+
}
|
|
39
|
+
// Save session to disk
|
|
40
|
+
await saveSession(context);
|
|
41
|
+
return "Successfully logged in to D&D Beyond. Session saved to disk.";
|
|
42
|
+
}
|
|
43
|
+
// Check login state on the page that's already loaded — does not navigate.
|
|
44
|
+
async function checkLoggedInOnCurrentPage(page) {
|
|
45
|
+
try {
|
|
46
|
+
return await page.evaluate(() => {
|
|
47
|
+
// Positive signal: selectors verified against live DDB DOM on 2026-05-08.
|
|
48
|
+
// [data-testid="signedInUserButton"] is the user button in the top nav.
|
|
49
|
+
// [class*="UserNavigation"] matches the UserNavigation component family.
|
|
50
|
+
const loggedInSelectors = [
|
|
51
|
+
'[data-testid="signedInUserButton"]',
|
|
52
|
+
'[class*="UserNavigation"]',
|
|
53
|
+
];
|
|
54
|
+
if (loggedInSelectors.some(sel => document.querySelector(sel)))
|
|
55
|
+
return true;
|
|
56
|
+
// Fallback: no visible "Sign In" / "Log In" button
|
|
57
|
+
const allElements = Array.from(document.querySelectorAll("a, button"));
|
|
58
|
+
const signInEl = allElements.find((el) => {
|
|
59
|
+
const text = (el.textContent || "").trim().toLowerCase();
|
|
60
|
+
return text === "sign in" || text === "log in";
|
|
61
|
+
});
|
|
62
|
+
return !signInEl;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Browser, BrowserContext, Page } from "playwright";
|
|
2
|
+
export declare const SESSION_DIR: string;
|
|
3
|
+
export declare const SESSION_PATH: string;
|
|
4
|
+
export declare function getBrowser(headless?: boolean): Promise<Browser>;
|
|
5
|
+
export declare function getContext(browser: Browser): Promise<BrowserContext>;
|
|
6
|
+
export declare function saveSession(context: BrowserContext): Promise<void>;
|
|
7
|
+
export declare function getPage(context: BrowserContext): Promise<Page>;
|
|
8
|
+
export declare function closeBrowser(): Promise<void>;
|
|
9
|
+
//# sourceMappingURL=browser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAMrE,eAAO,MAAM,WAAW,QAAwC,CAAC;AACjE,eAAO,MAAM,YAAY,QAAoC,CAAC;AAM9D,wBAAsB,UAAU,CAAC,QAAQ,UAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAclE;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,cAAc,CAAC,CAiB1E;AAED,wBAAsB,WAAW,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CASxE;AAED,wBAAsB,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAIpE;AAED,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAUlD"}
|
package/dist/browser.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { chromium } from "playwright";
|
|
2
|
+
import { existsSync, mkdirSync, chmodSync } from "fs";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { invalidateSessionCache } from "./session-fetch.js";
|
|
6
|
+
export const SESSION_DIR = join(homedir(), ".config", "ddb-mcp");
|
|
7
|
+
export const SESSION_PATH = join(SESSION_DIR, "session.json");
|
|
8
|
+
let browserInstance = null;
|
|
9
|
+
let browserHeadless = null;
|
|
10
|
+
let contextInstance = null;
|
|
11
|
+
export async function getBrowser(headless = true) {
|
|
12
|
+
// If a browser is already running with a different headless setting, close it
|
|
13
|
+
// first so ddb_login always gets a visible window even if headless tools ran before.
|
|
14
|
+
if (browserInstance && browserHeadless !== headless) {
|
|
15
|
+
await closeBrowser();
|
|
16
|
+
}
|
|
17
|
+
if (browserInstance)
|
|
18
|
+
return browserInstance;
|
|
19
|
+
const args = ["--disable-blink-features=AutomationControlled"];
|
|
20
|
+
// Sandbox should stay enabled. Only disable it in constrained container
|
|
21
|
+
// environments (e.g. CI/Docker) where the kernel doesn't support it.
|
|
22
|
+
if (process.env["DDB_NO_SANDBOX"] === "1")
|
|
23
|
+
args.push("--no-sandbox");
|
|
24
|
+
browserInstance = await chromium.launch({ headless, args });
|
|
25
|
+
browserHeadless = headless;
|
|
26
|
+
return browserInstance;
|
|
27
|
+
}
|
|
28
|
+
export async function getContext(browser) {
|
|
29
|
+
if (contextInstance)
|
|
30
|
+
return contextInstance;
|
|
31
|
+
if (!existsSync(SESSION_DIR)) {
|
|
32
|
+
mkdirSync(SESSION_DIR, { recursive: true, mode: 0o700 });
|
|
33
|
+
}
|
|
34
|
+
const contextOptions = {
|
|
35
|
+
userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
|
|
36
|
+
viewport: { width: 1280, height: 800 },
|
|
37
|
+
};
|
|
38
|
+
contextInstance = await browser.newContext(existsSync(SESSION_PATH) ? { ...contextOptions, storageState: SESSION_PATH } : contextOptions);
|
|
39
|
+
return contextInstance;
|
|
40
|
+
}
|
|
41
|
+
export async function saveSession(context) {
|
|
42
|
+
if (!existsSync(SESSION_DIR)) {
|
|
43
|
+
mkdirSync(SESSION_DIR, { recursive: true, mode: 0o700 });
|
|
44
|
+
}
|
|
45
|
+
await context.storageState({ path: SESSION_PATH });
|
|
46
|
+
// Restrict session file to owner-only access — it contains sensitive auth cookies.
|
|
47
|
+
chmodSync(SESSION_PATH, 0o600);
|
|
48
|
+
// Invalidate the in-memory cookie/token cache so the next request reads the new session.
|
|
49
|
+
invalidateSessionCache();
|
|
50
|
+
}
|
|
51
|
+
export async function getPage(context) {
|
|
52
|
+
const pages = context.pages();
|
|
53
|
+
if (pages.length > 0)
|
|
54
|
+
return pages[0];
|
|
55
|
+
return context.newPage();
|
|
56
|
+
}
|
|
57
|
+
export async function closeBrowser() {
|
|
58
|
+
if (contextInstance) {
|
|
59
|
+
await contextInstance.close();
|
|
60
|
+
contextInstance = null;
|
|
61
|
+
}
|
|
62
|
+
if (browserInstance) {
|
|
63
|
+
await browserInstance.close();
|
|
64
|
+
browserInstance = null;
|
|
65
|
+
}
|
|
66
|
+
browserHeadless = null;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=browser.js.map
|