Skip to main content
Glama

roll_dice

Generate random dice rolls for tabletop RPGs using standard notation like 2d6+4. Supports single rolls, batch rolling, and advantage/disadvantage mechanics for d20 checks.

Instructions

Roll dice using standard notation (e.g., "2d6+4", "4d6kh3"). Supports single rolls or batch rolling multiple expressions at once. Supports advantage/disadvantage for d20 rolls. Provide either "expression" for single roll or "batch" for multiple rolls.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
expressionNoDice expression for single roll (e.g., "2d6+4", "1d20", "4d6kh3")
batchNoArray of roll requests for batch rolling (alternative to expression)
reasonNoOptional reason for the roll(s)
advantageNoRoll with advantage (2d20, keep highest). Only works with single d20 rolls.
disadvantageNoRoll with disadvantage (2d20, keep lowest). Only works with single d20 rolls.

Implementation Reference

  • The async handler function implementing the core logic for roll_dice tool. Handles both single rolls and batch rolls, advantage/disadvantage for d20s, validation, parsing via parseDice, and formatted output via formatDiceResult.
    handler: async (args) => {
      // Check if this is a batch operation
      const argsObj = args as Record<string, unknown>;
      if ('batch' in argsObj && argsObj.batch) {
        const { batch, reason } = args as {
          batch?: Array<{ expression: string; label?: string; advantage?: boolean; disadvantage?: boolean }>;
          reason?: string;
        };
    
        if (!batch || batch.length === 0) {
          return error('Missing required parameter: batch (must be non-empty array)');
        }
    
        if (batch.length > 20) {
          return error('Too many rolls (maximum 20 per batch)');
        }
    
        try {
          const results: Array<{ label?: string; expression: string; total: number; rolls: number[]; kept: number[] }> = [];
    
          for (const roll of batch) {
            if (!roll.expression) {
              return error('Each roll must have an expression');
            }
    
            if (roll.advantage && roll.disadvantage) {
              return error(`Roll "${roll.label || roll.expression}": Cannot have both advantage and disadvantage`);
            }
    
            let finalExpression = roll.expression;
    
            // Auto-convert d20 rolls to advantage/disadvantage
            if ((roll.advantage || roll.disadvantage) && roll.expression.match(/^1d20([+-]\d+)?$/i)) {
              const modifier = roll.expression.match(/([+-]\d+)$/)?.[1] || '';
              finalExpression = roll.advantage ? `2d20kh1${modifier}` : `2d20kl1${modifier}`;
            } else if (roll.advantage || roll.disadvantage) {
              return error(`Roll "${roll.label || roll.expression}": Advantage/disadvantage only works with single d20 rolls`);
            }
    
            const result = parseDice(finalExpression);
            results.push({
              label: roll.label,
              expression: finalExpression,
              total: result.total,
              rolls: result.rolls,
              kept: result.kept,
            });
          }
    
          // Format batch output
          const content: string[] = [];
    
          if (reason) {
            content.push(reason.toUpperCase());
            content.push('');
          }
    
          content.push(`ROLLING ${results.length} DICE ${results.length === 1 ? 'EXPRESSION' : 'EXPRESSIONS'}`);
          content.push('');
          content.push('─'.repeat(40));
          content.push('');
    
          for (let i = 0; i < results.length; i++) {
            const r = results[i];
            const label = r.label || `Roll ${i + 1}`;
    
            content.push(`${label}:`);
            content.push(`  Expression: ${r.expression}`);
    
            if (r.rolls.length !== r.kept.length) {
              content.push(`  Rolled: [${r.rolls.join(', ')}]`);
              content.push(`  Kept: [${r.kept.join(', ')}]`);
            } else if (r.rolls.length > 1) {
              content.push(`  Rolled: [${r.rolls.join(', ')}]`);
            }
    
            content.push(`  Result: ${r.total}`);
            content.push('');
          }
    
          content.push('─'.repeat(40));
          content.push('');
          content.push(`TOTAL ACROSS ALL ROLLS: ${results.reduce((sum, r) => sum + r.total, 0)}`);
    
          return success(createBox('BATCH ROLL', content, undefined, 'HEAVY'));
        } catch (err) {
          const message = err instanceof Error ? err.message : String(err);
          return error(message);
        }
      }
    
      // Single roll mode
      const { expression, reason, advantage, disadvantage } = args as {
        expression?: string;
        reason?: string;
        advantage?: boolean;
        disadvantage?: boolean;
      };
    
      if (!expression) {
        return error('Missing required parameter: expression');
      }
    
      if (advantage && disadvantage) {
        return error('Cannot have both advantage and disadvantage');
      }
    
      try {
        let finalExpression = expression;
    
        // Auto-convert d20 rolls to advantage/disadvantage
        if ((advantage || disadvantage) && expression.match(/^1d20([+-]\d+)?$/i)) {
          const modifier = expression.match(/([+-]\d+)$/)?.[1] || '';
          finalExpression = advantage ? `2d20kh1${modifier}` : `2d20kl1${modifier}`;
        } else if (advantage || disadvantage) {
          return error('Advantage/disadvantage only works with single d20 rolls (e.g., "1d20" or "1d20+5")');
        }
    
        const result = parseDice(finalExpression);
        return success(formatDiceResult(result, reason));
      } catch (err) {
        const message = err instanceof Error ? err.message : String(err);
        return error(message);
      }
    },
  • JSON schema defining input parameters for roll_dice tool, supporting single expression or batch array, optional reason, advantage/disadvantage.
    inputSchema: {
      type: 'object',
      properties: {
        expression: {
          type: 'string',
          description: 'Dice expression for single roll (e.g., "2d6+4", "1d20", "4d6kh3")',
        },
        batch: {
          type: 'array',
          description: 'Array of roll requests for batch rolling (alternative to expression)',
          items: {
            type: 'object',
            properties: {
              expression: {
                type: 'string',
                description: 'Dice expression (e.g., "2d6+4", "1d20", "4d6kh3")',
              },
              label: {
                type: 'string',
                description: 'Label for this roll (e.g., "Attack 1", "Damage", "Goblin 1")',
              },
              advantage: {
                type: 'boolean',
                description: 'Roll with advantage (2d20, keep highest)',
              },
              disadvantage: {
                type: 'boolean',
                description: 'Roll with disadvantage (2d20, keep lowest)',
              },
            },
            required: ['expression'],
          },
          minItems: 1,
          maxItems: 20,
        },
        reason: {
          type: 'string',
          description: 'Optional reason for the roll(s)',
        },
        advantage: {
          type: 'boolean',
          description: 'Roll with advantage (2d20, keep highest). Only works with single d20 rolls.',
        },
        disadvantage: {
          type: 'boolean',
          description: 'Roll with disadvantage (2d20, keep lowest). Only works with single d20 rolls.',
        },
      },
    },
  • Registration of the roll_dice tool in the toolRegistry object, including name, description, inputSchema, and inline handler.
    roll_dice: {
      name: 'roll_dice',
      description: 'Roll dice using standard notation (e.g., "2d6+4", "4d6kh3"). Supports single rolls or batch rolling multiple expressions at once. Supports advantage/disadvantage for d20 rolls. Provide either "expression" for single roll or "batch" for multiple rolls.',
      inputSchema: {
        type: 'object',
        properties: {
          expression: {
            type: 'string',
            description: 'Dice expression for single roll (e.g., "2d6+4", "1d20", "4d6kh3")',
          },
          batch: {
            type: 'array',
            description: 'Array of roll requests for batch rolling (alternative to expression)',
            items: {
              type: 'object',
              properties: {
                expression: {
                  type: 'string',
                  description: 'Dice expression (e.g., "2d6+4", "1d20", "4d6kh3")',
                },
                label: {
                  type: 'string',
                  description: 'Label for this roll (e.g., "Attack 1", "Damage", "Goblin 1")',
                },
                advantage: {
                  type: 'boolean',
                  description: 'Roll with advantage (2d20, keep highest)',
                },
                disadvantage: {
                  type: 'boolean',
                  description: 'Roll with disadvantage (2d20, keep lowest)',
                },
              },
              required: ['expression'],
            },
            minItems: 1,
            maxItems: 20,
          },
          reason: {
            type: 'string',
            description: 'Optional reason for the roll(s)',
          },
          advantage: {
            type: 'boolean',
            description: 'Roll with advantage (2d20, keep highest). Only works with single d20 rolls.',
          },
          disadvantage: {
            type: 'boolean',
            description: 'Roll with disadvantage (2d20, keep lowest). Only works with single d20 rolls.',
          },
        },
      },
      handler: async (args) => {
        // Check if this is a batch operation
        const argsObj = args as Record<string, unknown>;
        if ('batch' in argsObj && argsObj.batch) {
          const { batch, reason } = args as {
            batch?: Array<{ expression: string; label?: string; advantage?: boolean; disadvantage?: boolean }>;
            reason?: string;
          };
    
          if (!batch || batch.length === 0) {
            return error('Missing required parameter: batch (must be non-empty array)');
          }
    
          if (batch.length > 20) {
            return error('Too many rolls (maximum 20 per batch)');
          }
    
          try {
            const results: Array<{ label?: string; expression: string; total: number; rolls: number[]; kept: number[] }> = [];
    
            for (const roll of batch) {
              if (!roll.expression) {
                return error('Each roll must have an expression');
              }
    
              if (roll.advantage && roll.disadvantage) {
                return error(`Roll "${roll.label || roll.expression}": Cannot have both advantage and disadvantage`);
              }
    
              let finalExpression = roll.expression;
    
              // Auto-convert d20 rolls to advantage/disadvantage
              if ((roll.advantage || roll.disadvantage) && roll.expression.match(/^1d20([+-]\d+)?$/i)) {
                const modifier = roll.expression.match(/([+-]\d+)$/)?.[1] || '';
                finalExpression = roll.advantage ? `2d20kh1${modifier}` : `2d20kl1${modifier}`;
              } else if (roll.advantage || roll.disadvantage) {
                return error(`Roll "${roll.label || roll.expression}": Advantage/disadvantage only works with single d20 rolls`);
              }
    
              const result = parseDice(finalExpression);
              results.push({
                label: roll.label,
                expression: finalExpression,
                total: result.total,
                rolls: result.rolls,
                kept: result.kept,
              });
            }
    
            // Format batch output
            const content: string[] = [];
    
            if (reason) {
              content.push(reason.toUpperCase());
              content.push('');
            }
    
            content.push(`ROLLING ${results.length} DICE ${results.length === 1 ? 'EXPRESSION' : 'EXPRESSIONS'}`);
            content.push('');
            content.push('─'.repeat(40));
            content.push('');
    
            for (let i = 0; i < results.length; i++) {
              const r = results[i];
              const label = r.label || `Roll ${i + 1}`;
    
              content.push(`${label}:`);
              content.push(`  Expression: ${r.expression}`);
    
              if (r.rolls.length !== r.kept.length) {
                content.push(`  Rolled: [${r.rolls.join(', ')}]`);
                content.push(`  Kept: [${r.kept.join(', ')}]`);
              } else if (r.rolls.length > 1) {
                content.push(`  Rolled: [${r.rolls.join(', ')}]`);
              }
    
              content.push(`  Result: ${r.total}`);
              content.push('');
            }
    
            content.push('─'.repeat(40));
            content.push('');
            content.push(`TOTAL ACROSS ALL ROLLS: ${results.reduce((sum, r) => sum + r.total, 0)}`);
    
            return success(createBox('BATCH ROLL', content, undefined, 'HEAVY'));
          } catch (err) {
            const message = err instanceof Error ? err.message : String(err);
            return error(message);
          }
        }
    
        // Single roll mode
        const { expression, reason, advantage, disadvantage } = args as {
          expression?: string;
          reason?: string;
          advantage?: boolean;
          disadvantage?: boolean;
        };
    
        if (!expression) {
          return error('Missing required parameter: expression');
        }
    
        if (advantage && disadvantage) {
          return error('Cannot have both advantage and disadvantage');
        }
    
        try {
          let finalExpression = expression;
    
          // Auto-convert d20 rolls to advantage/disadvantage
          if ((advantage || disadvantage) && expression.match(/^1d20([+-]\d+)?$/i)) {
            const modifier = expression.match(/([+-]\d+)$/)?.[1] || '';
            finalExpression = advantage ? `2d20kh1${modifier}` : `2d20kl1${modifier}`;
          } else if (advantage || disadvantage) {
            return error('Advantage/disadvantage only works with single d20 rolls (e.g., "1d20" or "1d20+5")');
          }
    
          const result = parseDice(finalExpression);
          return success(formatDiceResult(result, reason));
        } catch (err) {
          const message = err instanceof Error ? err.message : String(err);
          return error(message);
        }
      },
    },
  • parseDice helper function that parses complex dice expressions (NdX, keep/drop highest/lowest, +mod), simulates rolls, and returns structured DiceResult used by the handler.
    export function parseDice(expression: string): DiceResult {
      const cleaned = expression.replace(/\s/g, '').toLowerCase();
      const match = cleaned.match(DICE_REGEX);
    
      if (!match) {
        throw new Error(`Invalid dice expression: "${expression}"`);
      }
    
      const numDice = parseInt(match[1]);
      const dieSize = parseInt(match[2]);
      const keepDrop = match[3] || '';
      const modifierStr = match[4] || '';
    
      if (numDice < 1 || numDice > 100) {
        throw new Error(`Invalid number of dice: ${numDice}`);
      }
      if (dieSize < 2 || dieSize > 1000) {
        throw new Error(`Invalid die size: ${dieSize}`);
      }
    
      // Roll dice
      const rolls: number[] = [];
      for (let i = 0; i < numDice; i++) {
        rolls.push(Math.floor(Math.random() * dieSize) + 1);
      }
    
      // Apply keep/drop modifier
      let kept = [...rolls];
      if (keepDrop) {
        const type = keepDrop.slice(0, 2);
        const count = parseInt(keepDrop.slice(2));
        
        if (count > rolls.length) {
          throw new Error(`Cannot keep/drop ${count} from ${rolls.length} dice`);
        }
    
        const sorted = [...rolls].sort((a, b) => b - a); // Descending
        
        switch (type) {
          case 'kh': // Keep highest
            kept = sorted.slice(0, count);
            break;
          case 'kl': // Keep lowest
            kept = sorted.slice(-count);
            break;
          case 'dh': // Drop highest
            kept = sorted.slice(count);
            break;
          case 'dl': // Drop lowest
            kept = sorted.slice(0, -count);
            break;
        }
      }
    
      // Parse modifier
      const modifier = modifierStr ? parseInt(modifierStr) : 0;
    
      // Calculate total
      const total = kept.reduce((sum, n) => sum + n, 0) + modifier;
    
      return {
        expression,
        rolls,
        kept,
        modifier,
        total,
      };
    }
  • formatDiceResult helper that renders the DiceResult into formatted ASCII output with visualizations, used for success responses in the handler.
    export function formatDiceResult(result: DiceResult, reason?: string): string {
      const content: string[] = [];
      const box = BOX.LIGHT;
    
      // Title line
      content.push(`DICE ROLL: ${result.expression}`);
      if (reason) {
        content.push(`(${reason})`);
      }
      content.push('');
    
      // For d20 critical hits/fails, show special display
      if (result.expression.toLowerCase().includes('d20') && result.rolls.length === 1) {
        const roll = result.rolls[0];
        if (roll === 1 || roll === 20) {
          const special = D20_SPECIAL[roll];
          special.forEach(line => content.push(line));
          content.push('');
          content.push(`TOTAL: ${result.total}`);
          return createBox('DICE ROLL', content, undefined, 'HEAVY');
        }
      }
    
      // Show dice faces (for reasonable number of dice)
      if (result.rolls.length <= 6 && result.rolls.every(r => r <= 6)) {
        const diceFaces = renderDiceHorizontal(result.rolls);
        diceFaces.forEach(line => content.push(line));
        content.push('');
    
        // Show which were kept if different
        if (result.rolls.length !== result.kept.length) {
          const keptIndices = result.rolls.map((r, i) =>
            result.kept.includes(r) && !result.kept.slice(0, result.kept.indexOf(r)).includes(r) ? i : -1
          ).filter(i => i >= 0);
    
          content.push(`KEPT: [${result.kept.join(', ')}]`);
          content.push(`DROPPED: [${result.rolls.filter(r => !result.kept.includes(r)).join(', ')}]`);
          content.push('');
        }
      } else {
        // For many dice or large dice, show numbers
        if (result.rolls.length !== result.kept.length) {
          content.push(`ROLLED: [${result.rolls.join(', ')}]`);
          content.push(`KEPT: [${result.kept.join(', ')}]`);
        } else {
          content.push(`ROLLED: [${result.rolls.join(', ')}]`);
        }
        content.push('');
      }
    
      // Show calculation
      const diceSum = result.kept.reduce((sum, n) => sum + n, 0);
      if (result.modifier !== 0) {
        const sign = result.modifier > 0 ? '+' : '';
        content.push(`CALCULATION: ${diceSum} ${sign}${result.modifier} = ${result.total}`);
      } else {
        content.push(`TOTAL: ${result.total}`);
      }
    
      // Add bottom divider
      content.push('');
      content.push('─'.repeat(40)); // Will be adjusted by auto-sizing
      content.push(`FINAL RESULT: ${result.total}`);
    
      return createBox('DICE ROLL', content, undefined, 'HEAVY');
    }
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It describes key behavioral traits: supports standard notation, single/batch rolling, and advantage/disadvantage for d20 rolls. However, it doesn't mention error handling, rate limits, or what happens with invalid expressions, leaving some behavioral aspects unclear.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is perfectly front-loaded and concise - three sentences with zero waste. The first sentence establishes core functionality, the second adds key features, and the third provides clear parameter guidance. Every sentence earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's moderate complexity (5 parameters, batch rolling capability) and no output schema, the description is adequate but has gaps. It explains what the tool does and basic usage, but doesn't describe return values, error conditions, or how results are formatted, which would be helpful for a dice-rolling tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 100% schema description coverage, the baseline is 3. The description adds some value by explaining the 'expression' vs 'batch' choice and mentioning advantage/disadvantage support, but doesn't provide additional parameter semantics beyond what's already documented in the comprehensive schema descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose with specific verbs ('roll dice') and resources ('using standard notation'), and distinguishes it from siblings by focusing on dice rolling rather than character management or encounter operations. It provides concrete examples of notation like '2d6+4' and '4d6kh3'.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear context for when to use the tool (for dice rolling with standard notation) and distinguishes between single rolls ('expression') and batch rolls ('batch'). However, it doesn't explicitly state when NOT to use it or mention specific alternatives among the sibling tools like 'roll_check' or 'roll_death_save'.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

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/Mnehmos/mnehmos.chatrpg.game'

If you have feedback or need assistance with the MCP directory API, please join our Discord server