Skip to main content
Glama
guan-tends

dice-mcp-server

by guan-tends

@guan-tends/dice-mcp-server

Stateless dice engine MCP tool server. Pure dice math — no character sheets, no sessions, no persistence.

Three tools via the Model Context Protocol:

Tool

System

Character

dice_roll

Generic d20-style notation

expression in, number out

l5r4_roll

Legend of the Five Rings 4e Roll & Keep (XkY+Z)

structured in, number + raises out

l5r5_roll

Legend of the Five Rings 5e (FFG) ring/skill symbol dice

structured in, symbols out

Built on @guan-tends/mcp-ai SimpleServer (HTTP transport, raw zod-shape schemas). Same composition-root pattern as @guan-tends/matrix-mcp-server.

Quick Start

npm install
npm test    # 154 tests
npm start   # serves MCP HTTP on 127.0.0.1:3777

Point any MCP client at http://localhost:3777/ (Streamable HTTP transport).

Example: a d20 attack roll with advantage

{
  "name": "dice_roll",
  "arguments": { "expression": "2d20kl1+5", "dc": 15 }
}
{
  "success": true,
  "expression": "2d20kl1+5",
  "terms": [
    {
      "notation": "2d20kl1",
      "rolls": [
        { "face": 17, "chain": [], "final": 17, "rerolled": false },
        { "face": 8, "chain": [], "final": 8, "rerolled": false }
      ],
      "kept": [{ "face": 8, "chain": [], "final": 8, "rerolled": false }],
      "dropped": [{ "face": 17, "chain": [], "final": 17, "rerolled": false }],
      "subtotal": 13
    }
  ],
  "total": 18,
  "dc": 15,
  "success": true
}

Example: a 4e katana strike (Agility 3, Kenjutsu 4, emphasis, Void spent)

{
  "name": "l5r4_roll",
  "arguments": {
    "trait": 3,
    "skill": 4,
    "tn": 25,
    "emphasis": true,
    "rollBonus": 1,
    "keepBonus": 1,
    "label": "Katana strike"
  }
}

Returns 7d10k4 — pool, kept/dropped dice with explosion chains, totals, TN verdict, and a notes[] array explaining every rule that fired. A failed roll with raises carries wouldSucceedWithoutRaises: true — the GM-narration hook ("he'd have made it, but the flourish cost him").

Example: a 5e Fire check (Fire 3, Tactics 2, advantage)

{
  "name": "l5r5_roll",
  "arguments": { "ring": 3, "skill": 2, "tn": 3, "advantage": true }
}

Returns the full pool with per-die symbols, kept indices, conversions, and tallies:

{
  "tallies": { "successes": 3, "opportunities": 1, "strife": 1, "explosive": 2 },
  "success": true,
  "composureExceeded": false
}

Related MCP server: MCP Dice Roller

Tool Reference

dice_roll — generic notation

2d6+3        sum of 2d6 plus 3
d20          one d20
4d6kh3       keep highest 3 of 4d6 (ability scores)
2d20kl1      keep lowest 1 (disadvantage)
1d10!        exploding: reroll-and-sum while max faces appear
1d12!11,12   exploding on specific faces (5e skill die: 11 and 12)
3d6r1        reroll initial 1s once (emphasis shape)
2d6+1d4+2    multi-term

Param

Type

Notes

expression

string

required; whitespace-tolerant, case-insensitive

dc

int, optional

adds dc + success (total >= dc) to the result

Parse errors are position-aware (... at position 4) so callers can fix their own syntax. Doubled signs (2d6--3) and signless term concatenation (2d61d4) are rejected with named errors.

l5r4_roll — Roll & Keep (4e)

Pools (Trait + Skill) d10s, keeps the highest Trait. D10s explode on 10s (trained rolls only).

Param

Range

Default

Notes

trait

1–10

required

also the keep count

skill

0–10

0

0 = untrained

tn

5 trivial · 10 easy · 15 average · 20 difficult · 25 very hard · 30 extreme · 40 near-impossible

raises

0–10

0

+5 TN each; capped by voidRing

freeRaises

0–10

0

effect only, no TN, never counts vs cap

voidRing

1–10

caps declared raises

emphasis

bool

false

reroll initial 1s once, BEFORE explosions

penalty

int

0

wound/stance penalty, applied once to the total

rollBonus / keepBonus

0–10

0

Void Point = +1k1

Untrained (skill 0): trait dice only, ALL kept, no explosions, no raises (raises on untrained rolls are rejected with an explanatory error).

l5r5_roll — Ring & Skill dice (5e)

Rolls Ring d6 ring dice + Skill d12 skill dice, keeps Ring-rating dice chosen AFTER seeing the pool.

Param

Range

Default

Notes

ring

1–5

required

also the keep count

skill

0–5

0

0 = untrained (ring dice only)

tn

1–10

successes needed: 1 easy · 2 average · 3 difficult · 4 very hard · 5 extremely hard · 6 extraordinary · 7+ heroic

advantage

bool

false

convert non-explosive ring dice → skill dice

disadvantage

bool

false

convert worst skill die → ring die

conversions

string

explicit "2:skill,4:ring" (1-based, overrides adv/disadv)

policy

enum

success_first

min_strife, max_opportunity

kept

string

"1,3" explicit keep (1-based, exactly ring-count)

composure

int

advisory composureExceeded flag (kept strife ≥ value)

includeExplosionBonuses

bool

true

expand explosive faces into bonus dice

Verified symbol charts (cross-checked against two independent 5e references):

Ring d6

1

2

3

4

5

6

symbols

opp+strife

opp

succ+strife

succ

succ+strife+expl

Skill d12

1–2

3–5

6–7

8–9

10

11

12

symbols

opp

succ+strife

succ

succ+opp

succ+strife+expl

expl (no strife)

Explosive faces add one bonus die of the same type — chained, capped, marked bonusFor, never consuming keep slots.

Architecture

Functional core, imperative shell. All randomness is constructor-injected (rng(sides) => int in [1, sides]): production uses crypto.randomInt (unbiased CSPRNG); tests inject sequence RNGs, making every engine test deterministic. Symbol tables are data — adding Genesys/Star Wars dice later is a table entry, not a rewrite.

src/
├── index.js        bootstrap only (lifecycle, graceful shutdown)
├── config.js       config layers (defaults ← JSON5 ← env) + validation
├── mcp-server.js   tool definitions + SimpleServer wiring
└── engine/
    ├── rng.js      createCryptoRng / createSequenceRng
    ├── core.js     rollDice: explode-on-faces, reroll, caps
    ├── notation.js expression parser (position-aware errors)
    ├── d20.js      expression evaluator
    ├── symbols.js  5e symbol tables (data)
    ├── l5r4.js     Roll & Keep engine
    └── l5r5.js     ring/skill symbol engine

Error rejection is two-layer: zod-schema violations (out-of-range params) are rejected at the MCP protocol layer before the tool body runs; cross-field rule violations (e.g. raises > Void Ring) surface as isError JSON results with the engine's human-readable message.

Design Decisions

  • Stateless. No sheets, no sessions, no persistence. The caller is the GM brain; the server is dice math. Restart-safe by construction.

  • Teaching descriptions. Every tool description embeds its system's TN scale and worked examples — an LLM caller learns the system from the tool itself.

  • Deterministic tests. Sequence RNG injection makes explosion chains and keep decisions exactly reproducible; distribution tests with derived constants cover the real CSPRNG.

  • Dice-as-data. 5e symbol tables are frozen data; new symbol systems are new tables, not new engines.

  • No array-typed tool params. Kept indices, conversions, and explode faces are comma-strings — an MCP-gateway compatibility choice.

  • Interpretation notes (parameterized, documented, adjustable if a table ruling differs): 5e untrained = ring dice only; advantage converts pre-keep and never sacrifices an explosive ring die; composure is an explicit input with an advisory flag (no hardcoded formula); 4e wound penalties apply once to the total. dc is permissive (any integer) — permissive inputs, strict dice math.

Deployment

Systemd unit (example)

Adjust paths/user for your host. If you use nvm, point PATH and ExecStart at your node install (or source nvm in an ExecStartPre).

[Unit]
Description=Dice MCP Server
After=network.target

[Service]
Type=simple
User=YOURUSER
WorkingDirectory=/opt/dice-mcp-server
ExecStart=/usr/bin/node src/index.js
Restart=on-failure
RestartSec=5
# Loopback-only enforcement (see below)
IPAddressDeny=any
IPAddressAllow=localhost
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

Loopback enforcement

mcp-ai SimpleServer's express listen() binds all interfaces and ignores the host config field. Loopback-only is therefore enforced at the deployment layer — systemd IPAddressDeny=any + IPAddressAllow=localhost (cgroup packet filter) with UFW default-deny. Until mcp-ai supports a server-side bind host, deploy with both controls. (The port config IS honored.)

Gateway wiring (mcp-ai aggregator)

Add to the aggregator's mcps array (back up config.json first):

{ "id": "dice", "connection": { "type": "http", "url": "http://localhost:3777" } }

Then restart the gateway.

Testing

npm test              # 154 tests: engines, tools, e2e, distribution
npm run test:coverage # with coverage
npm run lint          # eslint
npm run format:check  # prettier

Test layers:

  1. Engine units — deterministic via sequence RNG; every rule path (explosions, emphasis ordering, untrained, raises/void, policies, conversions, overrides) asserted exactly.

  2. Tool units — schema shape contract (raw zod shapes), happy paths, engine-error surfacing.

  3. E2E — real MCP client over Streamable HTTP: initialize → listTools → callTool for every tool, both error layers.

  4. Distribution sanity — real CSPRNG with derived constants (geometric-series explosion means, binomial success expectations).

Support

If this server powers your table or your agents, tips are appreciated — they fund compute, inference, and the rest of the tool fleet:

  • GitHub Sponsors: github.com/sponsors/guan-tends

  • Solana: Eu8wQcW68TKMs1a6eqzZu8znzU52QLqQugAMG8uCD6y6

  • EVM: 0x2733ff7c865C56d565a99BE1DC11B81cc76850A5

  • XRP: r4X6e7McAQj7e8vBCeued1RYu4mCJrREDG

Roadmap

  • Genesys / Star Wars dice — new symbol tables (data), same engine.

  • mcp-ai upstream: server-side bind-host support for express listen() (removes the systemd-level loopback workaround).

  • Fate / other systems — candidate engines behind the same tool interface.

License

MIT — see LICENSE.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables dice rolling mechanics for tabletop RPGs including standard dice notation, coin flips, advantage/disadvantage rolls, exploding dice, and maintains a history of recent rolls. Supports D\&D 5e mechanics and various gaming dice types (d4, d6, d8, d10, d12, d20, d100).
    -
  • A
    license
    A
    quality
    C
    maintenance
    An MCP server that provides tools for rolling dice using standard notation, flipping coins, and selecting random items from lists. It supports advanced tabletop gaming features such as character stat generation and keep-highest/lowest mechanics.
    6
    MIT
  • A
    license
    A
    quality
    D
    maintenance
    Provides comprehensive TRPG dice rolling functionality including standard notation, advantage/disadvantage mechanics, and success-counting dice pools. It enables users to perform complex dice logic and track roll history through an MCP-compliant interface.
    2
    MIT
  • A
    license
    A
    quality
    D
    maintenance
    Enables dice rolling for RPG games using standard dice notation, supporting complex expressions like keep/drop, reroll, exploding dice, and batch rolls.
    4
    3
    MIT