List Cards
list_cardsFilter 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
| Name | Required | Description | Default |
|---|---|---|---|
| deck | No | Filter by deck name | |
| status | No | Comma-separated: not_started, started, done, blocked, in_review | |
| project | No | Filter by project name | |
| search | No | Search in title and content | |
| milestone | No | Filter by milestone name | |
| tag | No | Filter by tag name | |
| owner | No | Owner name, or 'none' for unassigned | |
| priority | No | Comma-separated: a, b, c, null | |
| sort | No | ||
| card_type | No | ||
| hero | No | Filter by hero card UUID | |
| hand_only | No | ||
| stale_days | No | Cards not updated in N days | |
| updated_after | No | YYYY-MM-DD | |
| updated_before | No | YYYY-MM-DD | |
| archived | No | ||
| include_stats | No | ||
| limit | No | ||
| offset | No |
Implementation Reference
- src/tools/read.ts:67-146 (registration)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))), }, ], }; } }, ); - src/tools/read.ts:68-97 (schema)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), }), - src/tools/read.ts:99-145 (handler)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))), }, ], }; } }, - src/client.ts:44-158 (handler)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 }; } - src/tools/read.ts:26-32 (helper)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; }