Skip to main content
Glama

List Cards

list_cards

Filter and view project cards in Codecks by deck, status, priority, owner, or search terms to manage tasks and track progress.

Instructions

List cards. Filters combine with AND.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
deckNoFilter by deck name
statusNoComma-separated: not_started, started, done, blocked, in_review
projectNoFilter by project name
searchNoSearch in title and content
milestoneNoFilter by milestone name
tagNoFilter by tag name
ownerNoOwner name, or 'none' for unassigned
priorityNoComma-separated: a, b, c, null
sortNo
card_typeNo
heroNoFilter by hero card UUID
hand_onlyNo
stale_daysNoCards not updated in N days
updated_afterNoYYYY-MM-DD
updated_beforeNoYYYY-MM-DD
archivedNo
include_statsNo
limitNo
offsetNo

Implementation Reference

  • Registration of the 'list_cards' tool with MCP server, including schema definition and handler that calls client.listCards() with pagination, sanitization, and response formatting.
    server.registerTool(
      "list_cards",
      {
        title: "List Cards",
        description: "List cards. Filters combine with AND.",
        inputSchema: z.object({
          deck: z.string().optional().describe("Filter by deck name"),
          status: z
            .string()
            .optional()
            .describe("Comma-separated: not_started, started, done, blocked, in_review"),
          project: z.string().optional().describe("Filter by project name"),
          search: z.string().optional().describe("Search in title and content"),
          milestone: z.string().optional().describe("Filter by milestone name"),
          tag: z.string().optional().describe("Filter by tag name"),
          owner: z.string().optional().describe("Owner name, or 'none' for unassigned"),
          priority: z.string().optional().describe("Comma-separated: a, b, c, null"),
          sort: z
            .enum(["status", "priority", "effort", "deck", "title", "owner", "updated", "created"])
            .optional(),
          card_type: z.enum(["hero", "doc"]).optional(),
          hero: z.string().optional().describe("Filter by hero card UUID"),
          hand_only: z.boolean().default(false),
          stale_days: z.number().optional().describe("Cards not updated in N days"),
          updated_after: z.string().optional().describe("YYYY-MM-DD"),
          updated_before: z.string().optional().describe("YYYY-MM-DD"),
          archived: z.boolean().default(false),
          include_stats: z.boolean().default(false),
          limit: z.number().default(50),
          offset: z.number().default(0),
        }),
      },
      async (args) => {
        try {
          const result = await client.listCards({
            deck: args.deck,
            status: args.status,
            project: args.project,
            search: args.search,
            milestone: args.milestone,
            tag: args.tag,
            owner: args.owner,
            priority: args.priority,
            sort: args.sort,
            cardType: args.card_type,
            hero: args.hero,
            handOnly: args.hand_only,
            staleDays: args.stale_days,
            updatedAfter: args.updated_after,
            updatedBefore: args.updated_before,
            archived: args.archived,
            includeStats: args.include_stats,
          });
    
          const allCards = (result.cards ?? []) as Record<string, unknown>[];
          const total = allCards.length;
          const page = allCards.slice(args.offset, args.offset + args.limit);
          const payload = {
            cards: page.map((c) => sanitizeCard(slimCard(c))),
            stats: result.stats,
            total_count: total,
            has_more: args.offset + args.limit < total,
            limit: args.limit,
            offset: args.offset,
          };
          return {
            content: [{ type: "text", text: JSON.stringify(finalizeToolResult(payload)) }],
          };
        } catch (err) {
          return {
            content: [
              {
                type: "text",
                text: JSON.stringify(finalizeToolResult(handleError(err))),
              },
            ],
          };
        }
      },
    );
  • Input schema definition for list_cards tool using zod, specifying all filter options (deck, status, project, search, milestone, tag, owner, priority, sort, card_type, hero, hand_only, stale_days, updated_after, updated_before, archived, include_stats, limit, offset).
    "list_cards",
    {
      title: "List Cards",
      description: "List cards. Filters combine with AND.",
      inputSchema: z.object({
        deck: z.string().optional().describe("Filter by deck name"),
        status: z
          .string()
          .optional()
          .describe("Comma-separated: not_started, started, done, blocked, in_review"),
        project: z.string().optional().describe("Filter by project name"),
        search: z.string().optional().describe("Search in title and content"),
        milestone: z.string().optional().describe("Filter by milestone name"),
        tag: z.string().optional().describe("Filter by tag name"),
        owner: z.string().optional().describe("Owner name, or 'none' for unassigned"),
        priority: z.string().optional().describe("Comma-separated: a, b, c, null"),
        sort: z
          .enum(["status", "priority", "effort", "deck", "title", "owner", "updated", "created"])
          .optional(),
        card_type: z.enum(["hero", "doc"]).optional(),
        hero: z.string().optional().describe("Filter by hero card UUID"),
        hand_only: z.boolean().default(false),
        stale_days: z.number().optional().describe("Cards not updated in N days"),
        updated_after: z.string().optional().describe("YYYY-MM-DD"),
        updated_before: z.string().optional().describe("YYYY-MM-DD"),
        archived: z.boolean().default(false),
        include_stats: z.boolean().default(false),
        limit: z.number().default(50),
        offset: z.number().default(0),
      }),
  • The tool handler function that processes list_cards requests, calls client.listCards(), applies pagination (offset/limit), sanitizes results with slimCard and sanitizeCard, and returns formatted response with cards array, stats, total_count, has_more, limit, and offset.
    async (args) => {
      try {
        const result = await client.listCards({
          deck: args.deck,
          status: args.status,
          project: args.project,
          search: args.search,
          milestone: args.milestone,
          tag: args.tag,
          owner: args.owner,
          priority: args.priority,
          sort: args.sort,
          cardType: args.card_type,
          hero: args.hero,
          handOnly: args.hand_only,
          staleDays: args.stale_days,
          updatedAfter: args.updated_after,
          updatedBefore: args.updated_before,
          archived: args.archived,
          includeStats: args.include_stats,
        });
    
        const allCards = (result.cards ?? []) as Record<string, unknown>[];
        const total = allCards.length;
        const page = allCards.slice(args.offset, args.offset + args.limit);
        const payload = {
          cards: page.map((c) => sanitizeCard(slimCard(c))),
          stats: result.stats,
          total_count: total,
          has_more: args.offset + args.limit < total,
          limit: args.limit,
          offset: args.offset,
        };
        return {
          content: [{ type: "text", text: JSON.stringify(finalizeToolResult(payload)) }],
        };
      } catch (err) {
        return {
          content: [
            {
              type: "text",
              text: JSON.stringify(finalizeToolResult(handleError(err))),
            },
          ],
        };
      }
    },
  • Implementation of CodecksClient.listCards() method - builds GraphQL query, handles server-side filters (status, priority, cardType, archived), performs client-side filtering (deck, search, owner, staleDays), applies sorting, computes optional stats, and returns cards array.
    async listCards(
      options: {
        deck?: string;
        status?: string;
        project?: string;
        search?: string;
        milestone?: string;
        tag?: string;
        owner?: string;
        priority?: string;
        sort?: string;
        cardType?: string;
        hero?: string;
        handOnly?: boolean;
        staleDays?: number;
        updatedAfter?: string;
        updatedBefore?: string;
        archived?: boolean;
        includeStats?: boolean;
      } = {},
    ): Promise<Record<string, unknown>> {
      // Build query filters
      const filters: Record<string, unknown> = {};
      if (options.status) {
        const statuses = options.status.split(",").map((s) => s.trim());
        filters.status = statuses.length === 1 ? statuses[0] : statuses;
      }
      if (options.priority) {
        const pris = options.priority.split(",").map((p) => p.trim());
        filters.priority = pris.length === 1 ? pris[0] : pris;
      }
      if (options.cardType === "hero") filters.cardType = "hero";
      if (options.cardType === "doc") filters.cardType = "doc";
      if (options.archived) filters.visibility = "archived";
    
      const cardFields = [
        "id",
        "title",
        "status",
        "priority",
        "effort",
        "createdAt",
        "lastUpdatedAt",
        { assignee: ["name"] },
        { deck: ["title"] },
        { milestone: ["title"] },
        { masterTags: ["name"] },
      ];
    
      const q: Record<string, unknown> = {
        _root: [
          {
            account: [
              {
                [`${options.archived ? "archivedCards" : "cards"}`]: cardFields,
              },
            ],
          },
        ],
      };
    
      const result = await query(q);
      let cards = this.extractCards(result);
    
      // Client-side filtering
      if (options.deck) {
        cards = cards.filter(
          (c) =>
            String(getField(c, "deck_name", "deckName") ?? "").toLowerCase() ===
            options.deck!.toLowerCase(),
        );
      }
      if (options.search) {
        const term = options.search.toLowerCase();
        cards = cards.filter(
          (c) =>
            String(c.title ?? "")
              .toLowerCase()
              .includes(term) ||
            String(c.content ?? "")
              .toLowerCase()
              .includes(term),
        );
      }
      if (options.owner) {
        if (options.owner === "none") {
          cards = cards.filter((c) => !c.owner_name);
        } else {
          const name = options.owner.toLowerCase();
          cards = cards.filter((c) => String(c.owner_name ?? "").toLowerCase() === name);
        }
      }
      if (options.staleDays) {
        const cutoff = new Date();
        cutoff.setDate(cutoff.getDate() - options.staleDays);
        cards = cards.filter((c) => {
          const updated = parseIsoTimestamp(getField(c, "last_updated_at", "lastUpdatedAt"));
          return updated && updated < cutoff;
        });
      }
    
      // Sort
      if (options.sort) {
        const sortField = options.sort;
        const reverse = sortField === "updated" || sortField === "created";
        cards.sort((a, b) => {
          const va = String(getField(a, sortField) ?? "");
          const vb = String(getField(b, sortField) ?? "");
          return reverse ? vb.localeCompare(va) : va.localeCompare(vb);
        });
      }
    
      const stats = options.includeStats ? this.computeStats(cards) : null;
      return { cards, stats };
    }
  • Helper function slimCard() that removes redundant raw IDs (deckId, milestoneId, assignee, projectId, childCardInfo, masterTags) from card objects for token efficiency.
    function slimCard(card: Record<string, unknown>): Record<string, unknown> {
      const out: Record<string, unknown> = {};
      for (const [k, v] of Object.entries(card)) {
        if (!SLIM_DROP.has(k)) out[k] = v;
      }
      return out;
    }
Behavior2/5

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

With no annotations provided, the description carries full burden but offers minimal behavioral information. It mentions 'Filters combine with AND' which is useful, but doesn't address pagination behavior (despite limit/offset parameters), authentication requirements, rate limits, or what happens when no filters are applied. For a tool with 19 parameters and no annotation coverage, this is inadequate.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is extremely concise - just two short sentences. While this could be seen as efficient, it borders on under-specification given the tool's complexity. Every word earns its place, but more words might be needed for adequate documentation.

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

Completeness2/5

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

For a tool with 19 parameters, no annotations, no output schema, and moderate schema coverage (63%), the description is severely incomplete. It doesn't explain what 'cards' represent in this system, what the return format looks like, or provide essential context for proper usage. The complexity demands more comprehensive documentation.

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?

The description adds minimal value beyond the schema. Schema description coverage is 63%, and the description only clarifies that 'Filters combine with AND' - which helps understand how multiple parameters interact but doesn't explain individual parameters. With 19 parameters, the description should do more to compensate for the 37% coverage gap, but it doesn't.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose2/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description 'List cards. Filters combine with AND.' is tautological - it restates the name/title without adding meaningful specificity. It doesn't clarify what 'cards' are in this context or distinguish this from other list operations like 'list_hand' or 'list_activity' among the sibling tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives. With sibling tools like 'list_hand', 'get_card', and 'list_activity', there's no indication of when this general listing tool is appropriate versus more specific retrieval operations.

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/rangogamedev/codecks-mcp'

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