Skip to main content
Glama
gregario

lorcana-oracle

Browse Franchise

browse_franchise

List all Disney Lorcana franchises with card counts, or input a franchise name to view its cards and statistics.

Instructions

Browse Disney Lorcana cards by franchise (story). Without a franchise name, lists all franchises with card counts. With a franchise name, shows cards and statistics.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
franchiseNoFranchise/story name (e.g. "Frozen", "Moana"). Omit to list all franchises.

Implementation Reference

  • The main handler function that registers and implements the browse_franchise tool. Without a franchise argument it lists all franchises with card counts; with a franchise it shows card details and statistics (ink colors, types, rarities, sets). Also provides partial-match suggestions when a franchise is not found.
    export function registerBrowseFranchise(server: McpServer, db: Database.Database): void {
      server.registerTool(
        'browse_franchise',
        {
          title: 'Browse Franchise',
          description:
            'Browse Disney Lorcana cards by franchise (story). Without a franchise name, lists all franchises with card counts. With a franchise name, shows cards and statistics.',
          inputSchema: {
            franchise: z.string().optional().describe('Franchise/story name (e.g. "Frozen", "Moana"). Omit to list all franchises.'),
          },
        },
        async (args) => {
          if (!args.franchise) {
            // List all franchises
            const franchises = listFranchises(db);
            if (franchises.length === 0) {
              return {
                content: [{ type: 'text' as const, text: 'No franchises found in the database.' }],
              };
            }
    
            const lines = franchises.map(
              (f) => `  ${f.story} (${f.count} card${f.count !== 1 ? 's' : ''})`,
            );
    
            return {
              content: [
                {
                  type: 'text' as const,
                  text: `Found ${franchises.length} franchises:\n\n${lines.join('\n')}`,
                },
              ],
            };
          }
    
          // Browse specific franchise
          const { rows, total } = getCardsByFranchise(db, args.franchise, 1000, 0);
    
          if (total === 0) {
            // Try partial match
            const allFranchises = listFranchises(db);
            const matches = allFranchises.filter(
              (f) => f.story.toLowerCase().includes(args.franchise!.toLowerCase()),
            );
    
            let text = `Franchise "${args.franchise}" not found.`;
            if (matches.length > 0) {
              text += `\n\nDid you mean one of these?\n${matches.map((f) => `  - ${f.story} (${f.count} cards)`).join('\n')}`;
            }
    
            return {
              content: [{ type: 'text' as const, text }],
              isError: true,
            };
          }
    
          // Compute stats
          const inkDist = computeDistribution(rows, 'color');
          const typeDist = computeDistribution(rows, 'type');
          const rarityDist = computeDistribution(rows, 'rarity');
          const setDist = computeDistribution(rows, 'set_code');
    
          const header = [
            `**${rows[0].story ?? args.franchise}** — ${total} card${total !== 1 ? 's' : ''}`,
            '',
            'Stats:',
            `  Ink colors: ${formatDistribution(inkDist)}`,
            `  Types: ${formatDistribution(typeDist)}`,
            `  Rarities: ${formatDistribution(rarityDist)}`,
            `  Sets: ${formatDistribution(setDist)}`,
            '',
            'Cards:',
          ].join('\n');
    
          const cardLines = rows.map(formatCardBrief);
    
          return {
            content: [
              {
                type: 'text' as const,
                text: header + '\n' + cardLines.join('\n'),
              },
            ],
          };
        },
      );
    }
  • Input schema for the browse_franchise tool. Defines an optional 'franchise' string parameter to specify the franchise/story name (e.g., 'Frozen', 'Moana'), or omit to list all franchises.
    {
      title: 'Browse Franchise',
      description:
        'Browse Disney Lorcana cards by franchise (story). Without a franchise name, lists all franchises with card counts. With a franchise name, shows cards and statistics.',
      inputSchema: {
        franchise: z.string().optional().describe('Franchise/story name (e.g. "Frozen", "Moana"). Omit to list all franchises.'),
      },
    },
  • src/server.ts:9-46 (registration)
    Import of registerBrowseFranchise from the browse-franchise tool module.
    import { registerBrowseFranchise } from './tools/browse-franchise.js';
    import { registerAnalyzeInkCurve } from './tools/analyze-ink-curve.js';
    import { registerAnalyzeLore } from './tools/analyze-lore.js';
    import { registerFindSongSynergies } from './tools/find-song-synergies.js';
    import { readFileSync, realpathSync } from 'node:fs';
    import { fileURLToPath } from 'node:url';
    import path from 'node:path';
    import type Database from 'better-sqlite3';
    
    export interface ServerOptions {
      db?: Database.Database;
      dataDir?: string;
    }
    
    export function createServer(options?: ServerOptions): McpServer {
      const __dirname = path.dirname(fileURLToPath(import.meta.url));
      let version = '0.0.0';
      try {
        const pkg = JSON.parse(
          readFileSync(path.join(__dirname, '..', 'package.json'), 'utf-8'),
        );
        version = pkg.version;
      } catch {
        // Fallback version if package.json not found (e.g., in tests)
      }
    
      const server = new McpServer({
        name: 'lorcana-oracle',
        version,
      });
    
      const db = options?.db ?? getDatabase(options?.dataDir);
    
      // Register tools
      registerSearchCards(server, db);
      registerBrowseSets(server, db);
      registerCharacterVersions(server, db);
      registerBrowseFranchise(server, db);
  • src/server.ts:46-46 (registration)
    Registration call to registerBrowseFranchise(server, db) in the createServer function.
    registerBrowseFranchise(server, db);
  • Database helper function listFranchises that queries all distinct stories with card counts, ordered by count descending.
    export function listFranchises(db: Database.Database): { story: string; count: number }[] {
      return db
        .prepare(
          "SELECT story, COUNT(*) as count FROM cards WHERE story IS NOT NULL AND story != '' GROUP BY story ORDER BY count DESC",
        )
        .all() as { story: string; count: number }[];
    }
  • Database helper function getCardsByFranchise that queries cards for a specific franchise (case-insensitive match) with limit/offset pagination.
    export function getCardsByFranchise(
      db: Database.Database,
      franchise: string,
      limit = 20,
      offset = 0,
    ): SearchResult {
      const countRow = db
        .prepare(
          'SELECT COUNT(*) as count FROM cards WHERE LOWER(story) = LOWER(?)',
        )
        .get(franchise) as { count: number };
      const rows = db
        .prepare(
          'SELECT * FROM cards WHERE LOWER(story) = LOWER(?) ORDER BY cost ASC, name ASC LIMIT ? OFFSET ?',
        )
        .all(franchise, limit, offset) as CardRow[];
      return { rows, total: countRow.count };
    }
  • Helper function formatCardBrief that formats a single card row into a display string including name, color, type, cost, stats and rarity.
    function formatCardBrief(card: CardRow): string {
      const stats: string[] = [];
      if (card.type === 'Character') {
        stats.push(`${card.strength ?? '—'}/${card.willpower ?? '—'}/${card.lore ?? '—'}`);
      } else if (card.type === 'Location' && card.lore !== null) {
        stats.push(`Lore: ${card.lore}`);
      }
      const statsStr = stats.length > 0 ? ` | ${stats.join(' ')}` : '';
      return `  ${card.full_name ?? card.name} — ${card.color} ${card.type} | Cost ${card.cost ?? '—'}${statsStr} | ${card.rarity ?? '—'}`;
    }
    
    function computeDistribution(cards: CardRow[], key: keyof CardRow): Map<string, number> {
      const dist = new Map<string, number>();
      for (const card of cards) {
        const val = String(card[key] ?? 'Unknown');
        dist.set(val, (dist.get(val) ?? 0) + 1);
      }
      return dist;
    }
  • Helper functions computeDistribution and formatDistribution for computing and formatting statistical distributions of card properties (ink, type, rarity, set).
    function computeDistribution(cards: CardRow[], key: keyof CardRow): Map<string, number> {
      const dist = new Map<string, number>();
      for (const card of cards) {
        const val = String(card[key] ?? 'Unknown');
        dist.set(val, (dist.get(val) ?? 0) + 1);
      }
      return dist;
    }
    
    function formatDistribution(dist: Map<string, number>): string {
      return [...dist.entries()]
        .sort((a, b) => b[1] - a[1])
        .map(([key, count]) => `${key}: ${count}`)
        .join(', ');
    }
Behavior4/5

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

No annotations are provided, so the description must communicate all behavioral traits. It discloses the two operational modes and implies a read-only browsing action. It does not mention any destructive side effects or limits, which is acceptable given the nature of browsing.

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?

The description is extremely concise, using two short sentences to convey the tool's full purpose and usage. It is front-loaded with the core action and efficiently covers both invocation patterns.

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 the absence of an output schema, the description sufficiently covers the tool's functionality for an agent. It clarifies the dual behavior and parameter usage, though it could briefly hint at the nature of 'statistics' or 'cards' output.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema describes the franchise parameter briefly. The description adds meaningful context: omitting the parameter lists all franchises, while providing it shows cards and statistics. This enhances understanding beyond the schema's description.

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?

The description clearly states the tool's purpose: browsing Disney Lorcana cards by franchise. It distinguishes two behaviors: listing all franchises with card counts when no name is given, and showing cards and statistics with a specific name. This differentiates it from sibling tools like analyze_ink_curve or browse_sets.

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?

The description provides explicit guidance on when to omit or provide the franchise parameter, covering both use cases. However, it does not mention when not to use this tool or point to alternatives like 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/lorcana-oracle'

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