Skip to main content
Glama

search_by_mechanic

Find Magic: The Gathering cards by keyword or mechanic like Flying or Trample, with optional rules definition and format filter.

Instructions

Find cards that have a specific keyword or mechanic (e.g., "Flying", "Trample", "Scry", "Cascade"). Use this when a user asks about cards with a particular ability or mechanic. Optionally includes the keyword's official rules definition.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
keywordYesKeyword/mechanic to search for (e.g., "Flying", "Trample")
include_definitionNoInclude keyword definition from keywords table
formatNoFilter by format legality
limitNoMax results (default 25, max 50)

Implementation Reference

  • Main handler function that searches for cards by keyword/mechanic. Queries the cards table using JSON keyword matching, optionally filters by format legality, optionally fetches the keyword definition, and falls back to FTS5 oracle_text search. Returns a SearchByMechanicResult with matching cards.
    export function handler(db: Database.Database, params: SearchByMechanicParams): SearchByMechanicResult {
      const limit = params.limit ?? 25;
      const result: SearchByMechanicResult = {
        keyword: params.keyword,
        cards: [],
        total: 0,
      };
    
      // Optionally fetch keyword definition
      if (params.include_definition) {
        const kwRow = db.prepare(
          'SELECT * FROM keywords WHERE LOWER(name) = LOWER(?)'
        ).get(params.keyword) as KeywordRow | undefined;
    
        if (kwRow) {
          result.definition = {
            name: kwRow.name,
            section: kwRow.section,
            type: kwRow.type,
            rules_text: kwRow.rules_text,
          };
        }
      }
    
      // Primary: search keywords JSON array
      const formatJoin = params.format
        ? 'JOIN legalities l ON l.card_id = c.id'
        : '';
      const formatWhere = params.format
        ? "AND l.format = ? AND l.status = 'legal'"
        : '';
    
      const keywordSql = `
        SELECT c.name, c.mana_cost, c.type_line, c.oracle_text
        FROM cards c
        ${formatJoin}
        WHERE c.keywords LIKE ?
        ${formatWhere}
        ORDER BY c.name
        LIMIT ?
      `;
    
      const keywordBindings: unknown[] = [`%"${params.keyword}"%`];
      if (params.format) {
        keywordBindings.push(params.format);
      }
      keywordBindings.push(limit);
    
      const keywordRows = db.prepare(keywordSql).all(...keywordBindings) as Array<{
        name: string;
        mana_cost: string | null;
        type_line: string | null;
        oracle_text: string | null;
      }>;
    
      const seenNames = new Set<string>();
      for (const row of keywordRows) {
        seenNames.add(row.name);
        result.cards.push({
          name: row.name,
          mana_cost: row.mana_cost,
          type_line: row.type_line,
          oracle_text_preview: row.oracle_text ? row.oracle_text.split('\n')[0] : null,
        });
      }
    
      // Fallback: FTS5 oracle_text search if we haven't hit the limit
      if (result.cards.length < limit) {
        const remaining = limit - result.cards.length;
        try {
          const ftsSql = `
            SELECT c.name, c.mana_cost, c.type_line, c.oracle_text
            FROM cards_fts fts
            JOIN cards c ON c.rowid = fts.rowid
            ${params.format ? 'JOIN legalities l ON l.card_id = c.id' : ''}
            WHERE cards_fts MATCH ?
            ${formatWhere}
            ORDER BY fts.rank
            LIMIT ?
          `;
          const ftsBindings: unknown[] = [params.keyword];
          if (params.format) {
            ftsBindings.push(params.format);
          }
          ftsBindings.push(remaining);
    
          const ftsRows = db.prepare(ftsSql).all(...ftsBindings) as Array<{
            name: string;
            mana_cost: string | null;
            type_line: string | null;
            oracle_text: string | null;
          }>;
    
          for (const row of ftsRows) {
            if (!seenNames.has(row.name)) {
              seenNames.add(row.name);
              result.cards.push({
                name: row.name,
                mana_cost: row.mana_cost,
                type_line: row.type_line,
                oracle_text_preview: row.oracle_text ? row.oracle_text.split('\n')[0] : null,
              });
            }
          }
        } catch {
          // FTS5 query might fail with certain keyword patterns — that's OK,
          // we already have results from the JSON keyword search
        }
      }
    
      result.total = result.cards.length;
      return result;
    }
  • Input schema (SearchByMechanicInput) using Zod with fields: keyword (string), include_definition (optional boolean), format (optional string), limit (optional number 1-50). Also defines output types: SearchByMechanicResult, KeywordDefinition, and MechanicCardSummary.
    export const SearchByMechanicInput = z.object({
      keyword: z.string().describe('Keyword/mechanic to search for (e.g., "Flying", "Trample")'),
      include_definition: z.boolean().optional().describe('Include keyword definition from keywords table'),
      format: z.string().optional().describe('Filter by format legality'),
      limit: z.number().min(1).max(50).optional().describe('Max results (default 25, max 50)'),
    });
    
    export type SearchByMechanicParams = z.infer<typeof SearchByMechanicInput>;
    
    // --- Output types ---
    
    export interface MechanicCardSummary {
      name: string;
      mana_cost: string | null;
      type_line: string | null;
      oracle_text_preview: string | null;
    }
    
    export interface KeywordDefinition {
      name: string;
      section: string;
      type: string;
      rules_text: string;
    }
    
    export interface SearchByMechanicResult {
      keyword: string;
      definition?: KeywordDefinition;
      cards: MechanicCardSummary[];
      total: number;
    }
  • src/server.ts:135-147 (registration)
    Tool registration on the MCP server using server.tool() with name 'search_by_mechanic', description, input schema (SearchByMechanicInput.shape), and handler callback that calls searchByMechanicHandler and formats with formatSearchByMechanic.
    server.tool(
      'search_by_mechanic',
      'Find cards that have a specific keyword or mechanic (e.g., "Flying", "Trample", "Scry", "Cascade"). Use this when a user asks about cards with a particular ability or mechanic. Optionally includes the keyword\'s official rules definition.',
      SearchByMechanicInput.shape,
      async (params) => {
        try {
          const result = searchByMechanicHandler(db, params);
          return { content: [{ type: 'text' as const, text: formatSearchByMechanic(result) }] };
        } catch (err) {
          return { content: [{ type: 'text' as const, text: `Error searching by mechanic: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
        }
      },
    );
  • Formatting helper that converts a SearchByMechanicResult into readable text for the LLM, including keyword definition (if present) and a list of matching cards with mana cost, type line, and oracle text preview.
    export function formatSearchByMechanic(result: SearchByMechanicResult): string {
      const lines: string[] = [];
    
      lines.push(`# Cards with "${result.keyword}"\n`);
    
      if (result.definition) {
        lines.push(`**${result.definition.name}** (${result.definition.type}, Section ${result.definition.section})`);
        lines.push(result.definition.rules_text);
        lines.push('');
      }
    
      if (result.cards.length === 0) {
        lines.push('No cards found with this keyword/mechanic.');
        return lines.join('\n');
      }
    
      lines.push(`Found ${result.total} card(s):\n`);
      for (const card of result.cards) {
        const costPart = card.mana_cost ? ` ${card.mana_cost}` : '';
        lines.push(`- **${card.name}**${costPart} — ${card.type_line ?? 'Unknown Type'}`);
        if (card.oracle_text_preview) {
          lines.push(`  ${card.oracle_text_preview}`);
        }
      }
    
      return lines.join('\n');
    }
Behavior3/5

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

No annotations are provided, so the description carries the full burden. It reveals that the tool searches cards and optionally includes definitions, but does not describe error handling, return format, or any behavioral traits beyond the basic operation.

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?

Two sentences, front-loaded with the core action and examples. Every word earns its place with no redundancy.

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

Completeness4/5

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

Given no output schema and no annotations, the description is fairly complete. It covers the main functionality and optional feature. Could mention default limit or pagination, but schema fills some gaps.

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?

Schema coverage is 100%, so the baseline is 3. The description adds minimal extra meaning beyond what the schema already provides for parameters like keyword, include_definition, format, and limit.

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?

Clearly states the tool finds cards by keyword/mechanic with examples. Distinguishes from sibling tools like search_cards which is more general, and get_keyword which focuses on definitions alone.

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?

Explicitly says 'Use this when a user asks about cards with a particular ability or mechanic.' Provides clear context for when to use, but does not mention when not to use or alternatives like get_keyword or search_cards.

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

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