Skip to main content
Glama

create_character

Generate D&D 5e characters with customizable stats, classes, races, equipment, and abilities for your RPG campaigns.

Instructions

Create a new D&D 5e character with stats, class, race, and equipment

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
nameYes
raceNoHuman
classNoFighter
levelNo
backgroundNo
characterTypeNopc
statsNo
hpNo
maxHpNo
acNo
speedNo
resistancesNo
immunitiesNo
vulnerabilitiesNo
conditionImmunitiesNo
spellcastingAbilityNo
knownSpellsNo
preparedSpellsNo
cantripsNo
skillProficienciesNo
saveProficienciesNo
equipmentNo

Implementation Reference

  • Core handler function that creates a new D&D 5e character: generates ID, calculates derived stats (HP, proficiency), builds Character object, persists to JSON file in app data dir, formats and returns markdown character sheet.
    export function createCharacter(input: CreateCharacterInput): { success: boolean; character: Character; markdown: string; } { // Generate unique ID const id = randomUUID(); // Get stats with defaults const stats = input.stats || { str: 10, dex: 10, con: 10, int: 10, wis: 10, cha: 10, }; // Calculate derived stats const conModifier = calculateModifier(stats.con); const proficiencyBonus = calculateProficiencyBonus(input.level); // Calculate HP (use provided values or auto-calculate) const maxHp = input.maxHp || calculateMaxHP(input.class, input.level, conModifier); const hp = input.hp !== undefined ? input.hp : maxHp; // Build character object const character: Character = { id, name: input.name, race: input.race, class: input.class, level: input.level, background: input.background, characterType: input.characterType, stats, hp, maxHp, ac: input.ac, speed: input.speed, proficiencyBonus, resistances: input.resistances, immunities: input.immunities, vulnerabilities: input.vulnerabilities, conditionImmunities: input.conditionImmunities, spellcastingAbility: input.spellcastingAbility, knownSpells: input.knownSpells, preparedSpells: input.preparedSpells, cantrips: input.cantrips, skillProficiencies: input.skillProficiencies, saveProficiencies: input.saveProficiencies, equipment: input.equipment, createdAt: new Date().toISOString(), }; // Save to file const dataDir = path.join(DATA_ROOT, 'characters'); if (!fs.existsSync(dataDir)) { fs.mkdirSync(dataDir, { recursive: true }); } const filePath = path.join(dataDir, `${id}.json`); fs.writeFileSync(filePath, JSON.stringify(character, null, 2), 'utf-8'); // Format markdown output const markdown = formatCharacterSheet(character); return { success: true, character, markdown, }; }
  • Zod input schema defining all parameters for character creation: name, race, class, level, stats, HP, AC, equipment, spells, proficiencies, etc. with sensible defaults and validation.
    export const createCharacterSchema = z.object({ name: z.string().min(1, 'Name is required'), race: z.string().default('Human'), class: z.string().default('Fighter'), level: z.number().min(1, 'Level must be at least 1').max(20, 'Level cannot exceed 20').default(1), background: z.string().optional(), characterType: z.enum(['pc', 'npc', 'enemy', 'neutral']).default('pc'), stats: z.object({ str: z.number().min(1, 'Ability score must be at least 1').max(30, 'Ability score cannot exceed 30').default(10), dex: z.number().min(1, 'Ability score must be at least 1').max(30, 'Ability score cannot exceed 30').default(10), con: z.number().min(1, 'Ability score must be at least 1').max(30, 'Ability score cannot exceed 30').default(10), int: z.number().min(1, 'Ability score must be at least 1').max(30, 'Ability score cannot exceed 30').default(10), wis: z.number().min(1, 'Ability score must be at least 1').max(30, 'Ability score cannot exceed 30').default(10), cha: z.number().min(1, 'Ability score must be at least 1').max(30, 'Ability score cannot exceed 30').default(10), }).optional(), hp: z.number().optional(), maxHp: z.number().optional(), ac: z.number().default(10), speed: z.number().default(30), resistances: z.array(DamageTypeSchema).optional(), immunities: z.array(DamageTypeSchema).optional(), vulnerabilities: z.array(DamageTypeSchema).optional(), conditionImmunities: z.array(ConditionSchema).optional(), spellcastingAbility: AbilitySchema.optional(), knownSpells: z.array(z.string()).optional(), preparedSpells: z.array(z.string()).optional(), cantrips: z.array(z.string()).optional(), skillProficiencies: z.array(SkillSchema).optional(), saveProficiencies: z.array(AbilitySchema).optional(), equipment: z.array(z.string()).optional(), });
  • Tool registration in central registry: defines name, description, converts Zod schema to JSON schema for MCP, provides async handler that validates input, calls createCharacter, and returns formatted success response or error.
    create_character: { name: 'create_character', description: 'Create a new D&D 5e character with stats, class, race, and equipment', inputSchema: toJsonSchema(createCharacterSchema), handler: async (args) => { try { const validated = createCharacterSchema.parse(args); const result = createCharacter(validated); return success(result.markdown); } 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); } },
  • Helper function that generates rich ASCII art markdown character sheet including HP bar, ability scores table, combat stats, equipment, spells, conditions, etc. Called by createCharacter.
    function formatCharacterSheet(character: Character): string { const content: string[] = []; const box = BOX.LIGHT; const WIDTH = 68; // Calculate effective stats with conditions const effectiveStats = calculateEffectiveStats(character.id, { maxHp: character.maxHp, hp: character.hp, speed: character.speed, ac: character.ac, }); // Character Header const typeLabel = character.characterType.toUpperCase(); content.push(centerText(`${character.name}`, WIDTH)); content.push(centerText(`${typeLabel} - ${character.race} ${character.class} (Level ${character.level})`, WIDTH)); if (character.background) { content.push(centerText(`Background: ${character.background}`, WIDTH)); } content.push(''); content.push(box.H.repeat(WIDTH)); content.push(''); // Combat Stats Section content.push(padText('COMBAT STATS', WIDTH, 'center')); content.push(''); // HP Bar - use effective maxHp if modified by conditions const displayMaxHp = effectiveStats.maxHp.effective; const displayHp = Math.min(character.hp, displayMaxHp); // Clamp current HP to effective max const hpBar = createStatusBar(displayHp, displayMaxHp, 40, 'HP'); content.push(centerText(hpBar, WIDTH)); // Show HP modification if conditions affect it if (effectiveStats.maxHp.modified) { const hpNote = `Base: ${character.hp}/${character.maxHp} → Effective: ${displayHp}/${displayMaxHp}`; content.push(centerText(hpNote, WIDTH)); } content.push(''); // Core combat stats in table content.push(createTableRow(['AC', 'Speed', 'Initiative', 'Prof Bonus'], [10, 12, 12, 14], 'LIGHT')); // Show effective values (with condition modifications if any) const displayAC = effectiveStats.ac ? effectiveStats.ac.effective : character.ac; const displaySpeed = effectiveStats.speed.effective; content.push(createTableRow([ displayAC.toString() + (effectiveStats.ac?.modified ? '*' : ''), `${displaySpeed} ft` + (effectiveStats.speed.modified ? '*' : ''), `+${calculateModifier(character.stats.dex)}`, `+${character.proficiencyBonus}` ], [10, 12, 12, 14], 'LIGHT')); // Show condition notes if stats are modified if (effectiveStats.speed.modified && effectiveStats.speed.base !== displaySpeed) { content.push(padText(` * Speed: ${effectiveStats.speed.base} ft (base)`, WIDTH, 'left')); } if (effectiveStats.ac?.modified && effectiveStats.ac.base !== displayAC) { content.push(padText(` * AC: ${effectiveStats.ac.base} (base)`, WIDTH, 'left')); } content.push(''); content.push(box.H.repeat(WIDTH)); content.push(''); // Ability Scores Section content.push(padText('ABILITY SCORES', WIDTH, 'center')); content.push(''); // Ability scores in two rows of three const abilities = [ { key: 'str', name: 'STR' }, { key: 'dex', name: 'DEX' }, { key: 'con', name: 'CON' }, { key: 'int', name: 'INT' }, { key: 'wis', name: 'WIS' }, { key: 'cha', name: 'CHA' }, ]; // Header content.push(createTableRow( abilities.slice(0, 3).map(a => a.name), [10, 10, 10], 'LIGHT' )); // Scores content.push(createTableRow( abilities.slice(0, 3).map(a => { const score = character.stats[a.key as keyof typeof character.stats]; return ` ${score}`; }), [10, 10, 10], 'LIGHT' )); // Modifiers content.push(createTableRow( abilities.slice(0, 3).map(a => { const score = character.stats[a.key as keyof typeof character.stats]; const mod = calculateModifier(score); return ` ${mod >= 0 ? '+' : ''}${mod}`; }), [10, 10, 10], 'LIGHT' )); content.push(''); // Second row content.push(createTableRow( abilities.slice(3, 6).map(a => a.name), [10, 10, 10], 'LIGHT' )); content.push(createTableRow( abilities.slice(3, 6).map(a => { const score = character.stats[a.key as keyof typeof character.stats]; return ` ${score}`; }), [10, 10, 10], 'LIGHT' )); content.push(createTableRow( abilities.slice(3, 6).map(a => { const score = character.stats[a.key as keyof typeof character.stats]; const mod = calculateModifier(score); return ` ${mod >= 0 ? '+' : ''}${mod}`; }), [10, 10, 10], 'LIGHT' )); // Proficiencies if (character.skillProficiencies && character.skillProficiencies.length > 0) { content.push(''); content.push(box.H.repeat(WIDTH)); content.push(''); content.push(padText('SKILL PROFICIENCIES', WIDTH, 'center')); content.push(''); const skills = character.skillProficiencies.join(', '); content.push(padText(skills, WIDTH, 'left')); } if (character.saveProficiencies && character.saveProficiencies.length > 0) { content.push(''); content.push(padText('SAVING THROWS: ' + character.saveProficiencies.map(s => s.toUpperCase()).join(', '), WIDTH, 'left')); } // Resistances/Immunities/Vulnerabilities const defenses: string[] = []; if (character.resistances && character.resistances.length > 0) { defenses.push(`Resistances: ${character.resistances.join(', ')}`); } if (character.immunities && character.immunities.length > 0) { defenses.push(`Immunities: ${character.immunities.join(', ')}`); } if (character.vulnerabilities && character.vulnerabilities.length > 0) { defenses.push(`Vulnerabilities: ${character.vulnerabilities.join(', ')}`); } if (character.conditionImmunities && character.conditionImmunities.length > 0) { defenses.push(`Condition Immunities: ${character.conditionImmunities.join(', ')}`); } if (defenses.length > 0) { content.push(''); content.push(box.H.repeat(WIDTH)); content.push(''); content.push(padText('DEFENSES', WIDTH, 'center')); content.push(''); defenses.forEach(d => content.push(padText(d, WIDTH, 'left'))); } // Spellcasting if (character.spellcastingAbility) { content.push(''); content.push(box.H.repeat(WIDTH)); content.push(''); content.push(padText('SPELLCASTING', WIDTH, 'center')); content.push(''); content.push(padText(`Ability: ${character.spellcastingAbility.toUpperCase()}`, WIDTH, 'left')); if (character.cantrips && character.cantrips.length > 0) { content.push(padText(`Cantrips: ${character.cantrips.join(', ')}`, WIDTH, 'left')); } if (character.knownSpells && character.knownSpells.length > 0) { content.push(padText(`Known: ${character.knownSpells.join(', ')}`, WIDTH, 'left')); } if (character.preparedSpells && character.preparedSpells.length > 0) { content.push(padText(`Prepared: ${character.preparedSpells.join(', ')}`, WIDTH, 'left')); } } // Equipment if (character.equipment && character.equipment.length > 0) { content.push(''); content.push(box.H.repeat(WIDTH)); content.push(''); content.push(padText('EQUIPMENT', WIDTH, 'center')); content.push(''); character.equipment.forEach(item => { content.push(padText(`• ${item}`, WIDTH, 'left')); }); } // Active Conditions Section (if any) const activeConditions = getActiveConditions(character.id); if (activeConditions.length > 0 || effectiveStats.conditionEffects.length > 0) { content.push(''); content.push(box.H.repeat(WIDTH)); content.push(''); content.push(padText('ACTIVE CONDITIONS', WIDTH, 'center')); content.push(''); // List each condition for (const cond of activeConditions) { const condName = typeof cond.condition === 'string' ? cond.condition.charAt(0).toUpperCase() + cond.condition.slice(1) : cond.condition; let condLabel = cond.condition === 'exhaustion' && cond.exhaustionLevel ? `${condName} (Level ${cond.exhaustionLevel})` : condName; content.push(padText(`• ${condLabel}`, WIDTH, 'left')); // Show source if available if (cond.source) { content.push(padText(` Source: ${cond.source}`, WIDTH, 'left')); } // Show duration if (cond.roundsRemaining !== undefined) { content.push(padText(` Duration: ${cond.roundsRemaining} round${cond.roundsRemaining !== 1 ? 's' : ''}`, WIDTH, 'left')); } else if (cond.duration && typeof cond.duration === 'string') { content.push(padText(` Duration: ${cond.duration.replace(/_/g, ' ')}`, WIDTH, 'left')); } content.push(''); } // Show mechanical effects summary if (effectiveStats.conditionEffects.length > 0) { content.push(padText('MECHANICAL EFFECTS:', WIDTH, 'left')); for (const effect of effectiveStats.conditionEffects) { content.push(padText(` ${effect}`, WIDTH, 'left')); } } } // Footer content.push(''); content.push(box.H.repeat(WIDTH)); content.push(centerText(`Character ID: ${character.id}`, WIDTH)); content.push(centerText(`Created: ${new Date(character.createdAt).toLocaleString()}`, WIDTH)); return createBox('CHARACTER SHEET', content, undefined, 'HEAVY'); }

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/ChatRPG'

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