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