Skip to main content
Glama

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; }

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