Skip to main content
Glama

update_character

Modify D&D 5e character attributes including stats, level, equipment, and abilities to reflect gameplay changes and progression.

Instructions

Update an existing D&D 5e character with new stats, HP, level, equipment, etc.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
characterIdNo
characterNameNo
nameNo
raceNo
classNo
levelNo
backgroundNo
characterTypeNo
statsNo
hpNo
healingNo
maxHpNo
acNo
speedNo
resistancesNo
immunitiesNo
vulnerabilitiesNo
conditionImmunitiesNo
spellcastingAbilityNo
knownSpellsNo
preparedSpellsNo
cantripsNo
skillProficienciesNo
saveProficienciesNo
equipmentNo
batchNo

Implementation Reference

  • Main execution logic for updating characters: handles single/batch updates, HP deltas/healing, stat changes, persistence to JSON files, derived stat recalculation, and formatted output.
    export function updateCharacter(input: UpdateCharacterInput): { success: boolean; character?: Character; markdown: string; error?: string; } { // Check if this is a batch operation if ('batch' in input && input.batch) { return updateCharacterBatch(input.batch); } // Type assertion: we know it's a single update at this point const singleInput = input as z.infer<typeof singleUpdateSchema>; const dataDir = path.join(DATA_ROOT, 'characters'); // Resolve character ID (either from direct ID or by name lookup) let characterId: string | undefined = singleInput.characterId; if (!characterId && singleInput.characterName) { const foundId = findCharacterByName(singleInput.characterName); if (!foundId) { return { success: false, markdown: '', error: `Character not found with name: ${singleInput.characterName}`, }; } characterId = foundId; } if (!characterId) { return { success: false, markdown: '', error: 'Character ID or name is required', }; } const filePath = path.join(dataDir, `${characterId}.json`); // Check if character exists if (!fs.existsSync(filePath)) { return { success: false, markdown: '', error: `Character not found: ${characterId}`, }; } // Read existing character let originalCharacter: Character; try { const fileContent = fs.readFileSync(filePath, 'utf-8'); originalCharacter = JSON.parse(fileContent); } catch (err) { const message = err instanceof Error ? err.message : String(err); return { success: false, markdown: '', error: `Failed to read character: ${message}`, }; } // Create updated character by merging changes const updatedCharacter: Character = { ...originalCharacter }; // Apply updates if (singleInput.name !== undefined) updatedCharacter.name = singleInput.name; if (singleInput.race !== undefined) updatedCharacter.race = singleInput.race; if (singleInput.class !== undefined) updatedCharacter.class = singleInput.class; if (singleInput.level !== undefined) updatedCharacter.level = singleInput.level; if (singleInput.background !== undefined) updatedCharacter.background = singleInput.background; if (singleInput.characterType !== undefined) updatedCharacter.characterType = singleInput.characterType; if (singleInput.ac !== undefined) updatedCharacter.ac = singleInput.ac; if (singleInput.speed !== undefined) updatedCharacter.speed = singleInput.speed; if (singleInput.resistances !== undefined) updatedCharacter.resistances = singleInput.resistances; if (singleInput.immunities !== undefined) updatedCharacter.immunities = singleInput.immunities; if (singleInput.vulnerabilities !== undefined) updatedCharacter.vulnerabilities = singleInput.vulnerabilities; if (singleInput.conditionImmunities !== undefined) updatedCharacter.conditionImmunities = singleInput.conditionImmunities; if (singleInput.spellcastingAbility !== undefined) updatedCharacter.spellcastingAbility = singleInput.spellcastingAbility; if (singleInput.knownSpells !== undefined) updatedCharacter.knownSpells = singleInput.knownSpells; if (singleInput.preparedSpells !== undefined) updatedCharacter.preparedSpells = singleInput.preparedSpells; if (singleInput.cantrips !== undefined) updatedCharacter.cantrips = singleInput.cantrips; if (singleInput.skillProficiencies !== undefined) updatedCharacter.skillProficiencies = singleInput.skillProficiencies; if (singleInput.saveProficiencies !== undefined) updatedCharacter.saveProficiencies = singleInput.saveProficiencies; if (singleInput.equipment !== undefined) updatedCharacter.equipment = singleInput.equipment; // Handle stats (partial update) if (singleInput.stats !== undefined) { updatedCharacter.stats = { ...originalCharacter.stats, ...singleInput.stats, }; } // Recalculate derived stats if needed const conModifier = calculateModifier(updatedCharacter.stats.con); // Recalculate proficiency bonus if level changed if (singleInput.level !== undefined) { updatedCharacter.proficiencyBonus = calculateProficiencyBonus(updatedCharacter.level); } // Recalculate maxHp if class, level, or CON changed if (singleInput.class !== undefined || singleInput.level !== undefined || singleInput.stats?.con !== undefined) { const newMaxHp = calculateMaxHP(updatedCharacter.class, updatedCharacter.level, conModifier); updatedCharacter.maxHp = newMaxHp; // If current HP is over the new max, cap it if (updatedCharacter.hp > newMaxHp) { updatedCharacter.hp = newMaxHp; } } // Apply maxHp override if provided if (singleInput.maxHp !== undefined) { updatedCharacter.maxHp = singleInput.maxHp; } // Apply healing if provided (explicit healing parameter takes precedence) if (singleInput.healing !== undefined) { const newHp = Math.min(updatedCharacter.maxHp, updatedCharacter.hp + singleInput.healing); updatedCharacter.hp = newHp; } // Apply HP update if provided (supports relative values like "+8" or "-15") else if (singleInput.hp !== undefined) { let newHp: number; if (typeof singleInput.hp === 'string') { // String delta notation (e.g., "+8" for healing, "-15" for damage) const delta = parseInt(singleInput.hp, 10); newHp = Math.max(0, Math.min(updatedCharacter.maxHp, updatedCharacter.hp + delta)); } else if (singleInput.hp < 0) { // Numeric delta notation (e.g., -12 for damage) // Negative numbers are always treated as deltas const delta = singleInput.hp; newHp = Math.max(0, Math.min(updatedCharacter.maxHp, updatedCharacter.hp + delta)); } else { // Absolute HP update (positive number or zero) newHp = singleInput.hp; // Validate HP doesn't exceed maxHp if (newHp > updatedCharacter.maxHp) { return { success: false, markdown: '', error: `HP (${newHp}) cannot exceed max HP (${updatedCharacter.maxHp})`, }; } } updatedCharacter.hp = newHp; } // Save updated character try { fs.writeFileSync(filePath, JSON.stringify(updatedCharacter, null, 2), 'utf-8'); } catch (err) { const message = err instanceof Error ? err.message : String(err); return { success: false, markdown: '', error: `Failed to save character: ${message}`, }; } // Format output showing before/after comparison const markdown = formatCharacterUpdate(originalCharacter, updatedCharacter, singleInput); return { success: true, character: updatedCharacter, markdown, }; }
  • Zod input schema definition supporting single character updates or batch (up to 20), with fields for all character properties including relative HP changes, equipment lists, resistances, spells, etc.
    const singleUpdateSchema = z.object({ characterId: z.string().min(1).optional(), characterName: z.string().min(1).optional(), name: z.string().min(1).optional(), race: z.string().optional(), class: z.string().optional(), level: z.number().min(1, 'Level must be at least 1').max(20, 'Level cannot exceed 20').optional(), background: z.string().optional(), characterType: z.enum(['pc', 'npc', 'enemy', 'neutral']).optional(), stats: z.object({ str: z.number().min(1).max(30).optional(), dex: z.number().min(1).max(30).optional(), con: z.number().min(1).max(30).optional(), int: z.number().min(1).max(30).optional(), wis: z.number().min(1).max(30).optional(), cha: z.number().min(1).max(30).optional(), }).optional(), hp: z.union([ z.string().regex(/^[+-]\d+$/, 'Relative HP must be in format "+5" or "-10"'), z.number() // Supports both absolute (positive) and delta (negative) values ]).optional(), healing: z.number().positive('Healing must be a positive number').optional(), maxHp: z.number().min(1).optional(), ac: z.number().optional(), speed: z.number().optional(), 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(), }).refine(data => data.characterId || data.characterName, { message: 'Either characterId or characterName must be provided', }); // Support both single and batch updates export const updateCharacterSchema = z.union([ singleUpdateSchema, z.object({ batch: z.array(singleUpdateSchema).min(1).max(20), }), ]); export type UpdateCharacterInput = z.infer<typeof updateCharacterSchema>;
  • Tool registration entry in central registry: converts Zod schema to JSON schema, wraps handler with validation and error handling, provides description.
    update_character: { name: 'update_character', description: 'Update an existing D&D 5e character with new stats, HP, level, equipment, etc.', inputSchema: toJsonSchema(updateCharacterSchema), handler: async (args) => { try { const validated = updateCharacterSchema.parse(args); const result = updateCharacter(validated); if (!result.success) { return error(result.error || 'Failed to update character'); } 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); } }, },

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