study_session
Manage Anki flashcard study sessions by finding due cards, answering reviews, suspending cards, and tracking learning progress.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| operation | Yes | Study session operation | |
| query | No | Search query to find cards (for find_due) | |
| cardIds | No | Card IDs to operate on | |
| answers | No | Card answers (for answer operation) | |
| days | No | Days from today for due date (for reschedule) |
Implementation Reference
- src/tools/consolidated.ts:336-477 (handler)Handler function implementing the core logic for the 'study_session' tool. Supports operations: find_due, answer, suspend, unsuspend, check_status, forget, relearn. Uses ankiClient to interact with Anki.async ({ operation, query, cardIds, answers }) => { try { switch (operation) { case 'find_due': { if (!query) { throw new Error('find_due requires query parameter'); } const foundCardIds = await ankiClient.card.findCards({ query }); if (foundCardIds.length > 0) { const dueStatus = await ankiClient.card.areDue({ cards: foundCardIds }); const dueCards = foundCardIds.filter((_, idx) => dueStatus[idx]); return { content: [ { type: 'text', text: `Found ${foundCardIds.length} cards matching "${query}"\n${dueCards.length} are due for review now\nDue card IDs: [${dueCards.join(', ')}]`, }, ], }; } return { content: [ { type: 'text', text: `No cards found matching "${query}"`, }, ], }; } case 'answer': { if (!answers || answers.length === 0) { throw new Error('answer requires answers array'); } const results = await ankiClient.card.answerCards({ answers }); const successCount = results.filter(Boolean).length; return { content: [ { type: 'text', text: `✓ Answered ${successCount}/${answers.length} cards successfully`, }, ], }; } case 'suspend': { if (!cardIds || cardIds.length === 0) { throw new Error('suspend requires cardIds'); } await ankiClient.card.suspend({ cards: cardIds }); return { content: [ { type: 'text', text: `✓ Suspended ${cardIds.length} card(s)`, }, ], }; } case 'unsuspend': { if (!cardIds || cardIds.length === 0) { throw new Error('unsuspend requires cardIds'); } await ankiClient.card.unsuspend({ cards: cardIds }); return { content: [ { type: 'text', text: `✓ Unsuspended ${cardIds.length} card(s)`, }, ], }; } case 'check_status': { if (!cardIds || cardIds.length === 0) { throw new Error('check_status requires cardIds'); } const [dueStatus, suspendedStatus] = await Promise.all([ ankiClient.card.areDue({ cards: cardIds }), ankiClient.card.areSuspended({ cards: cardIds }), ]); const statusInfo = cardIds.map((id, idx) => ({ cardId: id, isDue: dueStatus[idx], isSuspended: suspendedStatus[idx], })); return { content: [ { type: 'text', text: `Card status:\n${JSON.stringify(statusInfo, null, 2)}`, }, ], }; } case 'forget': { if (!cardIds || cardIds.length === 0) { throw new Error('forget requires cardIds'); } await ankiClient.card.forgetCards({ cards: cardIds }); return { content: [ { type: 'text', text: `✓ Reset ${cardIds.length} card(s) to new status`, }, ], }; } case 'relearn': { if (!cardIds || cardIds.length === 0) { throw new Error('relearn requires cardIds'); } await ankiClient.card.relearnCards({ cards: cardIds }); return { content: [ { type: 'text', text: `✓ Set ${cardIds.length} card(s) to relearn`, }, ], }; } default: throw new Error(`Unknown operation: ${operation}`); } } catch (error) { throw new Error( `study_session failed: ${error instanceof Error ? error.message : String(error)}` ); } }
- src/tools/consolidated.ts:315-335 (schema)Zod input schema validation for the study_session tool parameters.{ operation: z .enum(['find_due', 'answer', 'suspend', 'unsuspend', 'check_status', 'forget', 'relearn']) .describe('Study session operation'), query: z.string().optional().describe('Search query to find cards (for find_due)'), cardIds: z.array(z.number()).optional().describe('Card IDs to operate on'), answers: z .array( z.object({ cardId: z.number(), ease: z.number().min(1).max(4).describe('1=Again, 2=Hard, 3=Good, 4=Easy'), }) ) .optional() .describe('Card answers (for answer operation)'), days: z.string().optional().describe('Days from today for due date (for reschedule)'), },
- src/tools/consolidated.ts:313-478 (registration)MCP server.tool registration call for the 'study_session' tool, including inline schema and handler.server.tool( 'study_session', { operation: z .enum(['find_due', 'answer', 'suspend', 'unsuspend', 'check_status', 'forget', 'relearn']) .describe('Study session operation'), query: z.string().optional().describe('Search query to find cards (for find_due)'), cardIds: z.array(z.number()).optional().describe('Card IDs to operate on'), answers: z .array( z.object({ cardId: z.number(), ease: z.number().min(1).max(4).describe('1=Again, 2=Hard, 3=Good, 4=Easy'), }) ) .optional() .describe('Card answers (for answer operation)'), days: z.string().optional().describe('Days from today for due date (for reschedule)'), }, async ({ operation, query, cardIds, answers }) => { try { switch (operation) { case 'find_due': { if (!query) { throw new Error('find_due requires query parameter'); } const foundCardIds = await ankiClient.card.findCards({ query }); if (foundCardIds.length > 0) { const dueStatus = await ankiClient.card.areDue({ cards: foundCardIds }); const dueCards = foundCardIds.filter((_, idx) => dueStatus[idx]); return { content: [ { type: 'text', text: `Found ${foundCardIds.length} cards matching "${query}"\n${dueCards.length} are due for review now\nDue card IDs: [${dueCards.join(', ')}]`, }, ], }; } return { content: [ { type: 'text', text: `No cards found matching "${query}"`, }, ], }; } case 'answer': { if (!answers || answers.length === 0) { throw new Error('answer requires answers array'); } const results = await ankiClient.card.answerCards({ answers }); const successCount = results.filter(Boolean).length; return { content: [ { type: 'text', text: `✓ Answered ${successCount}/${answers.length} cards successfully`, }, ], }; } case 'suspend': { if (!cardIds || cardIds.length === 0) { throw new Error('suspend requires cardIds'); } await ankiClient.card.suspend({ cards: cardIds }); return { content: [ { type: 'text', text: `✓ Suspended ${cardIds.length} card(s)`, }, ], }; } case 'unsuspend': { if (!cardIds || cardIds.length === 0) { throw new Error('unsuspend requires cardIds'); } await ankiClient.card.unsuspend({ cards: cardIds }); return { content: [ { type: 'text', text: `✓ Unsuspended ${cardIds.length} card(s)`, }, ], }; } case 'check_status': { if (!cardIds || cardIds.length === 0) { throw new Error('check_status requires cardIds'); } const [dueStatus, suspendedStatus] = await Promise.all([ ankiClient.card.areDue({ cards: cardIds }), ankiClient.card.areSuspended({ cards: cardIds }), ]); const statusInfo = cardIds.map((id, idx) => ({ cardId: id, isDue: dueStatus[idx], isSuspended: suspendedStatus[idx], })); return { content: [ { type: 'text', text: `Card status:\n${JSON.stringify(statusInfo, null, 2)}`, }, ], }; } case 'forget': { if (!cardIds || cardIds.length === 0) { throw new Error('forget requires cardIds'); } await ankiClient.card.forgetCards({ cards: cardIds }); return { content: [ { type: 'text', text: `✓ Reset ${cardIds.length} card(s) to new status`, }, ], }; } case 'relearn': { if (!cardIds || cardIds.length === 0) { throw new Error('relearn requires cardIds'); } await ankiClient.card.relearnCards({ cards: cardIds }); return { content: [ { type: 'text', text: `✓ Set ${cardIds.length} card(s) to relearn`, }, ], }; } default: throw new Error(`Unknown operation: ${operation}`); } } catch (error) { throw new Error( `study_session failed: ${error instanceof Error ? error.message : String(error)}` ); } } );