Skip to main content
Glama

search_spells

Find D&D 5e spells by name, level, school, class, concentration, ritual, components, damage type, or saving throw using SRD data.

Instructions

Search D&D 5e SRD spells by name, level, school, class, concentration, ritual, components, damage type, or save type.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryNoSearch term for spell name or description
levelNoSpell level (0 for cantrips, 1-9 for leveled spells)
schoolNoSchool of magic (e.g. "evocation", "necromancy")
class_nameNoSpellcasting class (e.g. "Wizard", "Cleric")
concentrationNoFilter by concentration requirement
ritualNoFilter by ritual casting
has_materialNoFilter by material component requirement
damage_typeNoDamage type (e.g. "fire", "necrotic", "radiant")
save_typeNoSaving throw type (e.g. "DEX", "WIS", "CON")
limitNoResults per page (max 50)
offsetNoOffset for pagination

Implementation Reference

  • The MCP tool handler function for 'search_spells', which invokes the DB query and formats the results for the client.
    async ({
      query,
      level,
      school,
      class_name,
      concentration,
      ritual,
      has_material,
      damage_type,
      save_type,
      limit,
      offset,
    }) => {
      const result = searchSpells(db, {
        query,
        level,
        school,
        class_name,
        concentration,
        ritual,
        has_material,
        damage_type,
        save_type,
        limit,
        offset,
      });
    
      if (result.rows.length === 0) {
        return {
          content: [
            {
              type: 'text' as const,
              text: 'No spells found matching your criteria. Try a broader search — for example, remove some filters or use a partial name.',
            },
          ],
          isError: true,
        };
      }
    
      const start = (offset ?? 0) + 1;
      const end = (offset ?? 0) + result.rows.length;
      const header = `Found ${result.total} spell${result.total === 1 ? '' : 's'} (showing ${start}-${end})\n`;
    
      const spells = result.rows.map(formatSpell).join('\n\n---\n\n');
    
      return {
        content: [{ type: 'text' as const, text: header + '\n' + spells }],
      };
    },
  • The input schema definition for the 'search_spells' tool using Zod validation.
    inputSchema: {
      query: z.string().optional().describe('Search term for spell name or description'),
      level: z.number().min(0).max(9).optional().describe('Spell level (0 for cantrips, 1-9 for leveled spells)'),
      school: z.string().optional().describe('School of magic (e.g. "evocation", "necromancy")'),
      class_name: z.string().optional().describe('Spellcasting class (e.g. "Wizard", "Cleric")'),
      concentration: z.boolean().optional().describe('Filter by concentration requirement'),
      ritual: z.boolean().optional().describe('Filter by ritual casting'),
      has_material: z.boolean().optional().describe('Filter by material component requirement'),
      damage_type: z.string().optional().describe('Damage type (e.g. "fire", "necrotic", "radiant")'),
      save_type: z.string().optional().describe('Saving throw type (e.g. "DEX", "WIS", "CON")'),
      limit: z.number().min(1).max(50).default(10).describe('Results per page (max 50)'),
      offset: z.number().min(0).default(0).describe('Offset for pagination'),
    },
  • Registration function that adds 'search_spells' to the MCP server instance.
    export function registerSearchSpells(
      server: McpServer,
      db: Database.Database,
    ): void {
      server.registerTool(
        'search_spells',
        {
          description:
            'Search D&D 5e SRD spells by name, level, school, class, concentration, ritual, components, damage type, or save type.',
          inputSchema: {
            query: z.string().optional().describe('Search term for spell name or description'),
            level: z.number().min(0).max(9).optional().describe('Spell level (0 for cantrips, 1-9 for leveled spells)'),
            school: z.string().optional().describe('School of magic (e.g. "evocation", "necromancy")'),
            class_name: z.string().optional().describe('Spellcasting class (e.g. "Wizard", "Cleric")'),
            concentration: z.boolean().optional().describe('Filter by concentration requirement'),
            ritual: z.boolean().optional().describe('Filter by ritual casting'),
            has_material: z.boolean().optional().describe('Filter by material component requirement'),
            damage_type: z.string().optional().describe('Damage type (e.g. "fire", "necrotic", "radiant")'),
            save_type: z.string().optional().describe('Saving throw type (e.g. "DEX", "WIS", "CON")'),
            limit: z.number().min(1).max(50).default(10).describe('Results per page (max 50)'),
            offset: z.number().min(0).default(0).describe('Offset for pagination'),
          },
        },
        async ({
          query,
          level,
          school,
          class_name,
          concentration,
          ritual,
          has_material,
          damage_type,
          save_type,
          limit,
          offset,
        }) => {
          const result = searchSpells(db, {
            query,
            level,
            school,
            class_name,
            concentration,
            ritual,
            has_material,
            damage_type,
            save_type,
            limit,
            offset,
          });
    
          if (result.rows.length === 0) {
            return {
              content: [
                {
                  type: 'text' as const,
                  text: 'No spells found matching your criteria. Try a broader search — for example, remove some filters or use a partial name.',
                },
              ],
              isError: true,
            };
          }
    
          const start = (offset ?? 0) + 1;
          const end = (offset ?? 0) + result.rows.length;
          const header = `Found ${result.total} spell${result.total === 1 ? '' : 's'} (showing ${start}-${end})\n`;
    
          const spells = result.rows.map(formatSpell).join('\n\n---\n\n');
    
          return {
            content: [{ type: 'text' as const, text: header + '\n' + spells }],
          };
        },
      );
    }
  • The underlying data helper function that performs the SQL query to search the spells database based on the provided filters.
    export function searchSpells(
      db: Database.Database,
      filters: SpellFilters,
    ): SearchResult<SpellRow> {
      const conditions: string[] = [];
      const params: (string | number)[] = [];
    
      if (filters.query) {
        const ftsQuery = sanitizeFtsQuery(filters.query);
        if (ftsQuery.length > 0) {
          conditions.push(
            's.id IN (SELECT rowid FROM spells_fts WHERE spells_fts MATCH ?)',
          );
          params.push(ftsQuery);
        }
      }
    
      if (filters.level !== undefined) {
        conditions.push('s.level = ?');
        params.push(filters.level);
      }
    
      if (filters.school) {
        conditions.push('LOWER(s.school) = LOWER(?)');
        params.push(filters.school);
      }
    
      if (filters.class_name) {
        conditions.push('LOWER(s.classes) LIKE LOWER(?)');
        params.push(`%${filters.class_name}%`);
      }
    
      if (filters.concentration !== undefined) {
        conditions.push('s.concentration = ?');
        params.push(filters.concentration ? 1 : 0);
      }
    
      if (filters.ritual !== undefined) {
        conditions.push('s.ritual = ?');
        params.push(filters.ritual ? 1 : 0);
      }
    
      if (filters.has_material !== undefined) {
        conditions.push('s.components_m = ?');
        params.push(filters.has_material ? 1 : 0);
      }
    
      if (filters.damage_type) {
        conditions.push('LOWER(s.damage_type) = LOWER(?)');
        params.push(filters.damage_type);
      }
    
      if (filters.save_type) {
        conditions.push('LOWER(s.save_type) = LOWER(?)');
        params.push(filters.save_type);
      }
    
      const where =
        conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
      const limit = filters.limit ?? 20;
      const offset = filters.offset ?? 0;
    
      const countRow = db
        .prepare(`SELECT COUNT(*) as count FROM spells s ${where}`)
        .get(...params) as { count: number };
    
      const rows = db
        .prepare(
          `SELECT s.* FROM spells s ${where} ORDER BY s.level ASC, s.name ASC LIMIT ? OFFSET ?`,
        )
        .all(...params, limit, offset) as SpellRow[];
    
      return { rows, total: countRow.count };

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/gregario/dnd-oracle'

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