get_analytics
Retrieve Anki study analytics including deck statistics, review history, and card performance data for tracking learning progress.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| scope | Yes | Analytics scope | |
| deckName | No | Deck name for deck-specific stats | |
| cardIds | No | Card IDs for detailed analysis | |
| startTimestamp | No | Start timestamp for review history | |
| wholeCollection | No | Get whole collection stats vs current deck |
Implementation Reference
- src/tools/consolidated.ts:648-756 (handler)Main execution logic for get_analytics tool. Handles various analytics scopes (deck_stats, collection_stats, reviews_by_day, etc.) by invoking AnkiConnect API methods via ankiClient and returning formatted results.async ({ scope, deckName, cardIds, startTimestamp, wholeCollection }) => { try { switch (scope) { case 'deck_stats': { if (!deckName) { throw new Error('deck_stats requires deckName'); } const stats = await ankiClient.deck.getDeckStats({ decks: [deckName] }); return { content: [ { type: 'text', text: `๐ Statistics for "${deckName}":\n${JSON.stringify(stats, null, 2)}`, }, ], }; } case 'collection_stats': { const statsHTML = await ankiClient.statistic.getCollectionStatsHTML({ wholeCollection: wholeCollection ?? true, }); return { content: [ { type: 'text', text: `๐ Collection Statistics (${wholeCollection ? 'all decks' : 'current deck'}):\n${statsHTML}`, }, ], }; } case 'reviews_by_day': { const reviewsByDay = await ankiClient.statistic.getNumCardsReviewedByDay(); return { content: [ { type: 'text', text: `๐ Reviews by day (${reviewsByDay.length} days):\n${JSON.stringify(reviewsByDay, null, 2)}`, }, ], }; } case 'reviews_today': { const count = await ankiClient.statistic.getNumCardsReviewedToday(); return { content: [ { type: 'text', text: `๐ Cards reviewed today: ${count}`, }, ], }; } case 'card_reviews': { if (!deckName || startTimestamp === undefined) { throw new Error('card_reviews requires deckName and startTimestamp'); } const reviews = await ankiClient.statistic.cardReviews({ deck: deckName, startID: startTimestamp, }); return { content: [ { type: 'text', text: `๐ Found ${reviews.length} reviews for "${deckName}" after ${startTimestamp}:\n${JSON.stringify(reviews, null, 2)}`, }, ], }; } case 'card_details': { if (!cardIds || cardIds.length === 0) { throw new Error('card_details requires cardIds'); } const [easeFactors, intervals] = await Promise.all([ ankiClient.card.getEaseFactors({ cards: cardIds }), ankiClient.card.getIntervals({ cards: cardIds, complete: false }), ]); const details = cardIds.map((id, idx) => ({ cardId: id, easeFactor: easeFactors[idx], currentInterval: intervals[idx], })); return { content: [ { type: 'text', text: `๐ Card details:\n${JSON.stringify(details, null, 2)}`, }, ], }; } default: throw new Error(`Unknown scope: ${scope}`); } } catch (error) { throw new Error( `get_analytics failed: ${error instanceof Error ? error.message : String(error)}` ); } } );
- src/tools/consolidated.ts:628-647 (schema)Zod schema defining input parameters for get_analytics: scope (required enum), optional deckName, cardIds, startTimestamp, wholeCollection.{ scope: z .enum([ 'deck_stats', 'collection_stats', 'reviews_by_day', 'reviews_today', 'card_reviews', 'card_details', ]) .describe('Analytics scope'), deckName: z.string().optional().describe('Deck name for deck-specific stats'), cardIds: z.array(z.number()).optional().describe('Card IDs for detailed analysis'), startTimestamp: z.number().optional().describe('Start timestamp for review history'), wholeCollection: z .boolean() .optional() .describe('Get whole collection stats vs current deck'), },
- src/tools/consolidated.ts:626-757 (registration)MCP server tool registration call for get_analytics in consolidated tools module.server.tool( 'get_analytics', { scope: z .enum([ 'deck_stats', 'collection_stats', 'reviews_by_day', 'reviews_today', 'card_reviews', 'card_details', ]) .describe('Analytics scope'), deckName: z.string().optional().describe('Deck name for deck-specific stats'), cardIds: z.array(z.number()).optional().describe('Card IDs for detailed analysis'), startTimestamp: z.number().optional().describe('Start timestamp for review history'), wholeCollection: z .boolean() .optional() .describe('Get whole collection stats vs current deck'), }, async ({ scope, deckName, cardIds, startTimestamp, wholeCollection }) => { try { switch (scope) { case 'deck_stats': { if (!deckName) { throw new Error('deck_stats requires deckName'); } const stats = await ankiClient.deck.getDeckStats({ decks: [deckName] }); return { content: [ { type: 'text', text: `๐ Statistics for "${deckName}":\n${JSON.stringify(stats, null, 2)}`, }, ], }; } case 'collection_stats': { const statsHTML = await ankiClient.statistic.getCollectionStatsHTML({ wholeCollection: wholeCollection ?? true, }); return { content: [ { type: 'text', text: `๐ Collection Statistics (${wholeCollection ? 'all decks' : 'current deck'}):\n${statsHTML}`, }, ], }; } case 'reviews_by_day': { const reviewsByDay = await ankiClient.statistic.getNumCardsReviewedByDay(); return { content: [ { type: 'text', text: `๐ Reviews by day (${reviewsByDay.length} days):\n${JSON.stringify(reviewsByDay, null, 2)}`, }, ], }; } case 'reviews_today': { const count = await ankiClient.statistic.getNumCardsReviewedToday(); return { content: [ { type: 'text', text: `๐ Cards reviewed today: ${count}`, }, ], }; } case 'card_reviews': { if (!deckName || startTimestamp === undefined) { throw new Error('card_reviews requires deckName and startTimestamp'); } const reviews = await ankiClient.statistic.cardReviews({ deck: deckName, startID: startTimestamp, }); return { content: [ { type: 'text', text: `๐ Found ${reviews.length} reviews for "${deckName}" after ${startTimestamp}:\n${JSON.stringify(reviews, null, 2)}`, }, ], }; } case 'card_details': { if (!cardIds || cardIds.length === 0) { throw new Error('card_details requires cardIds'); } const [easeFactors, intervals] = await Promise.all([ ankiClient.card.getEaseFactors({ cards: cardIds }), ankiClient.card.getIntervals({ cards: cardIds, complete: false }), ]); const details = cardIds.map((id, idx) => ({ cardId: id, easeFactor: easeFactors[idx], currentInterval: intervals[idx], })); return { content: [ { type: 'text', text: `๐ Card details:\n${JSON.stringify(details, null, 2)}`, }, ], }; } default: throw new Error(`Unknown scope: ${scope}`); } } catch (error) { throw new Error( `get_analytics failed: ${error instanceof Error ? error.message : String(error)}` ); } } );