use_scroll
Cast spells from D&D 5e scrolls with automatic success for known spells or Arcana checks for higher-level magic, consuming the scroll on use.
Instructions
Use a spell scroll in D&D 5e. If spell is on your class list and same/lower level: auto-success. If spell is higher level: Arcana check DC 10 + spell level. On failure: scroll is consumed with no effect. On success: spell is cast from scroll, scroll consumed.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| characterId | Yes | ||
| scrollName | Yes | ||
| spellLevel | Yes | ||
| casterLevel | Yes | ||
| targetId | No | ||
| targetIds | No | ||
| targetPosition | No | ||
| arcanaBonus | No | ||
| rollMode | No | ||
| manualRoll | No | ||
| manualRolls | No | ||
| isAttackSpell | No | ||
| spellSchool | No |
Implementation Reference
- src/modules/magic.ts:1003-1107 (handler)The main handler function that implements the use_scroll tool logic. Handles Arcana checks when using spell scrolls according to D&D 5e rules (auto-success if spell level <= caster level, otherwise DC 10 + spell level). Generates ASCII art output with scroll stats, targeting info, and result.export function useScroll(input: UseScrollInput): string { const content: string[] = []; const { characterId, scrollName, spellLevel, casterLevel, targetId, targetIds, targetPosition, arcanaBonus = 0, rollMode = 'normal', manualRoll, manualRolls, isAttackSpell, spellSchool, } = input; // Get scroll stats const stats = SCROLL_STATS[spellLevel]; // Extract spell name from scroll name const spellName = scrollName.replace(/^Scroll of /i, ''); content.push(centerText('SPELL SCROLL', DISPLAY_WIDTH)); content.push(''); content.push(`Caster: ${characterId}`); content.push(`Scroll: ${scrollName}`); content.push(`Spell Level: ${spellLevel}`); if (spellSchool) { content.push(`School: ${spellSchool}`); } content.push(''); content.push(BOX.LIGHT.H.repeat(DISPLAY_WIDTH)); content.push(''); // Check if Arcana roll is needed const needsArcanaCheck = spellLevel > casterLevel; let success = true; if (needsArcanaCheck) { const arcanaDC = 10 + spellLevel; content.push(`Spell level (${spellLevel}) exceeds caster level (${casterLevel})`); content.push(`Arcana Check Required: DC ${arcanaDC}`); content.push(''); // Roll Arcana check const { finalRoll, rollDisplay } = resolveArcanaRoll(rollMode, manualRoll, manualRolls); const total = finalRoll + arcanaBonus; success = total >= arcanaDC; content.push(`Roll: ${rollDisplay}`); if (arcanaBonus !== 0) { content.push(`Modifier: ${formatModifier(arcanaBonus)}`); content.push(`Total: ${finalRoll} ${formatModifier(arcanaBonus)} = ${total}`); } else { content.push(`Total: ${total}`); } content.push(''); content.push(BOX.LIGHT.H.repeat(DISPLAY_WIDTH)); content.push(''); } // Show targeting info if provided if (targetId || targetIds || targetPosition) { content.push('Targeting:'); if (targetId) { content.push(` Target: ${targetId}`); } if (targetIds && targetIds.length > 0) { content.push(` Targets: ${targetIds.join(', ')}`); } if (targetPosition) { content.push(` Position: (${targetPosition.x}, ${targetPosition.y}${targetPosition.z !== undefined ? `, ${targetPosition.z}` : ''})`); } content.push(''); } // Show spell stats content.push(`Spell Save DC: DC ${stats.saveDC}`); if (isAttackSpell) { content.push(`Spell Attack: +${stats.attackBonus}`); } content.push(''); content.push(BOX.LIGHT.H.repeat(DISPLAY_WIDTH)); content.push(''); // Result if (success) { content.push(centerText('SUCCESS', DISPLAY_WIDTH)); content.push(''); content.push(`${spellName} has been cast!`); content.push('The scroll crumbles to dust (consumed).'); } else { content.push(centerText('FAILED', DISPLAY_WIDTH)); content.push(''); content.push('The magic fizzles and the scroll is lost!'); content.push('The scroll crumbles to dust (consumed).'); } const title = success ? 'SCROLL CAST' : 'SCROLL FAILED'; return createBox(title, content); }
- src/modules/magic.ts:974-994 (schema)Zod input schema for the use_scroll tool, defining required fields like characterId, scrollName, spellLevel, casterLevel, and optional targeting, roll modes, and spell properties.export const useScrollSchema = z.object({ characterId: z.string(), scrollName: z.string(), spellLevel: z.number().min(0).max(9), casterLevel: z.number().min(1).max(20), // Targeting (all optional) targetId: z.string().optional(), targetIds: z.array(z.string()).optional(), targetPosition: PositionSchema.optional(), // Arcana check for higher-level scrolls arcanaBonus: z.number().optional(), rollMode: RollModeSchema.optional(), manualRoll: z.number().min(1).max(20).optional(), manualRolls: z.array(z.number().min(1).max(20)).length(2).optional(), // Optional spell properties isAttackSpell: z.boolean().optional(), spellSchool: z.string().optional(), });
- src/registry.ts:1000-1018 (registration)Tool registration entry in the central registry. Converts Zod schema to JSON schema for MCP, wraps the handler with validation and error handling using createTypedHandler pattern.use_scroll: { name: 'use_scroll', description: 'Use a spell scroll in D&D 5e. If spell is on your class list and same/lower level: auto-success. If spell is higher level: Arcana check DC 10 + spell level. On failure: scroll is consumed with no effect. On success: spell is cast from scroll, scroll consumed.', inputSchema: toJsonSchema(useScrollSchema), handler: async (args) => { try { const validated = useScrollSchema.parse(args); const result = useScroll(validated); return success(result); } catch (err) { if (err instanceof z.ZodError) { const messages = err.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', '); return error(`Validation failed: ${messages}`); } const message = err instanceof Error ? err.message : String(err); return error(message); } }, },