Skip to main content
Glama

get_analytics

Retrieve Anki study analytics including deck statistics, review history, and card performance data for tracking learning progress.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
scopeYesAnalytics scope
deckNameNoDeck name for deck-specific stats
cardIdsNoCard IDs for detailed analysis
startTimestampNoStart timestamp for review history
wholeCollectionNoGet whole collection stats vs current deck

Implementation Reference

  • 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)}`
          );
        }
      }
    );
  • 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'),
    },
  • 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)}`
          );
        }
      }
    );

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/arielbk/anki-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server