@ankimcp/anki-mcp-server 0.11.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +19 -1
  2. package/dist/mcp/primitives/essential/index.d.ts +7 -1
  3. package/dist/mcp/primitives/essential/index.js +13 -1
  4. package/dist/mcp/primitives/essential/index.js.map +1 -1
  5. package/dist/mcp/primitives/essential/tools/collection-stats/collection-stats.tool.d.ts +104 -0
  6. package/dist/mcp/primitives/essential/tools/collection-stats/collection-stats.tool.js +212 -0
  7. package/dist/mcp/primitives/essential/tools/collection-stats/collection-stats.tool.js.map +1 -0
  8. package/dist/mcp/primitives/essential/tools/collection-stats/collection-stats.types.d.ts +32 -0
  9. package/dist/mcp/primitives/essential/tools/collection-stats/collection-stats.types.js +3 -0
  10. package/dist/mcp/primitives/essential/tools/collection-stats/collection-stats.types.js.map +1 -0
  11. package/dist/mcp/primitives/essential/tools/collection-stats/index.d.ts +2 -0
  12. package/dist/mcp/primitives/essential/tools/collection-stats/index.js +6 -0
  13. package/dist/mcp/primitives/essential/tools/collection-stats/index.js.map +1 -0
  14. package/dist/mcp/primitives/essential/tools/deck-stats/deck-stats.tool.d.ts +107 -0
  15. package/dist/mcp/primitives/essential/tools/deck-stats/deck-stats.tool.js +165 -0
  16. package/dist/mcp/primitives/essential/tools/deck-stats/deck-stats.tool.js.map +1 -0
  17. package/dist/mcp/primitives/essential/tools/deck-stats/deck-stats.types.d.ts +25 -0
  18. package/dist/mcp/primitives/essential/tools/deck-stats/deck-stats.types.js +3 -0
  19. package/dist/mcp/primitives/essential/tools/deck-stats/deck-stats.types.js.map +1 -0
  20. package/dist/mcp/primitives/essential/tools/deck-stats/index.d.ts +2 -0
  21. package/dist/mcp/primitives/essential/tools/deck-stats/index.js +6 -0
  22. package/dist/mcp/primitives/essential/tools/deck-stats/index.js.map +1 -0
  23. package/dist/mcp/primitives/essential/tools/review-stats/index.d.ts +2 -0
  24. package/dist/mcp/primitives/essential/tools/review-stats/index.js +6 -0
  25. package/dist/mcp/primitives/essential/tools/review-stats/index.js.map +1 -0
  26. package/dist/mcp/primitives/essential/tools/review-stats/review-stats.tool.d.ts +108 -0
  27. package/dist/mcp/primitives/essential/tools/review-stats/review-stats.tool.js +149 -0
  28. package/dist/mcp/primitives/essential/tools/review-stats/review-stats.tool.js.map +1 -0
  29. package/dist/mcp/primitives/essential/tools/review-stats/review-stats.types.d.ts +47 -0
  30. package/dist/mcp/primitives/essential/tools/review-stats/review-stats.types.js +3 -0
  31. package/dist/mcp/primitives/essential/tools/review-stats/review-stats.types.js.map +1 -0
  32. package/dist/mcp/utils/stats.utils.d.ts +28 -0
  33. package/dist/mcp/utils/stats.utils.js +139 -0
  34. package/dist/mcp/utils/stats.utils.js.map +1 -0
  35. package/dist/tsconfig.build.tsbuildinfo +1 -1
  36. package/package.json +2 -2
@@ -0,0 +1,165 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var DeckStatsTool_1;
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.DeckStatsTool = void 0;
14
+ const common_1 = require("@nestjs/common");
15
+ const mcp_nest_1 = require("@rekog/mcp-nest");
16
+ const zod_1 = require("zod");
17
+ const anki_connect_client_1 = require("../../../../clients/anki-connect.client");
18
+ const anki_utils_1 = require("../../../../utils/anki.utils");
19
+ const stats_utils_1 = require("../../../../utils/stats.utils");
20
+ let DeckStatsTool = DeckStatsTool_1 = class DeckStatsTool {
21
+ ankiClient;
22
+ logger = new common_1.Logger(DeckStatsTool_1.name);
23
+ constructor(ankiClient) {
24
+ this.ankiClient = ankiClient;
25
+ }
26
+ async execute(params, context) {
27
+ try {
28
+ const { deck, ease_buckets = [2.0, 2.5, 3.0], interval_buckets = [7, 21, 90], } = params;
29
+ this.logger.log(`Getting statistics for deck: ${deck}`);
30
+ await context.reportProgress({ progress: 10, total: 100 });
31
+ this.logger.log("Fetching deck statistics...");
32
+ const deckStatsResponse = await this.ankiClient.invoke("getDeckStats", {
33
+ decks: [deck],
34
+ });
35
+ if (!deckStatsResponse || Object.keys(deckStatsResponse).length === 0) {
36
+ throw new Error(`Deck "${deck}" not found`);
37
+ }
38
+ const deckStatsArray = Object.values(deckStatsResponse);
39
+ const deckStats = deckStatsArray.find((s) => s.name === deck);
40
+ if (!deckStats) {
41
+ throw new Error(`Deck "${deck}" not found in statistics response`);
42
+ }
43
+ const counts = {
44
+ total: deckStats.total_in_deck || 0,
45
+ new: deckStats.new_count || 0,
46
+ learning: deckStats.learn_count || 0,
47
+ review: deckStats.review_count || 0,
48
+ };
49
+ await context.reportProgress({ progress: 30, total: 100 });
50
+ if (counts.total === 0) {
51
+ this.logger.log(`Deck "${deck}" is empty`);
52
+ const result = {
53
+ deck,
54
+ counts,
55
+ ease: (0, stats_utils_1.computeDistribution)([], { boundaries: ease_buckets }),
56
+ intervals: (0, stats_utils_1.computeDistribution)([], {
57
+ boundaries: interval_buckets,
58
+ unitSuffix: "d",
59
+ }),
60
+ };
61
+ await context.reportProgress({ progress: 100, total: 100 });
62
+ return (0, anki_utils_1.createSuccessResponse)(result);
63
+ }
64
+ this.logger.log("Finding cards in deck...");
65
+ const escapedDeckName = deck.replace(/"/g, '\\"');
66
+ const cardIds = await this.ankiClient.invoke("findCards", {
67
+ query: `"deck:${escapedDeckName}"`,
68
+ });
69
+ if (!cardIds || cardIds.length === 0) {
70
+ this.logger.warn(`No cards found via findCards for deck "${deck}", using counts from getDeckStats`);
71
+ const result = {
72
+ deck,
73
+ counts,
74
+ ease: (0, stats_utils_1.computeDistribution)([], { boundaries: ease_buckets }),
75
+ intervals: (0, stats_utils_1.computeDistribution)([], {
76
+ boundaries: interval_buckets,
77
+ unitSuffix: "d",
78
+ }),
79
+ };
80
+ await context.reportProgress({ progress: 100, total: 100 });
81
+ return (0, anki_utils_1.createSuccessResponse)(result);
82
+ }
83
+ await context.reportProgress({ progress: 50, total: 100 });
84
+ this.logger.log(`Fetching ease factors for ${cardIds.length} cards...`);
85
+ const easeFactorsRaw = await this.ankiClient.invoke("getEaseFactors", {
86
+ cards: cardIds,
87
+ });
88
+ const easeValues = easeFactorsRaw
89
+ .map((e) => e / 1000)
90
+ .filter((e) => e > 0);
91
+ await context.reportProgress({ progress: 70, total: 100 });
92
+ this.logger.log(`Fetching intervals for ${cardIds.length} cards...`);
93
+ const intervalsRaw = await this.ankiClient.invoke("getIntervals", {
94
+ cards: cardIds,
95
+ });
96
+ const intervalValues = intervalsRaw.filter((i) => i > 0);
97
+ await context.reportProgress({ progress: 90, total: 100 });
98
+ this.logger.log("Computing distributions...");
99
+ const ease = (0, stats_utils_1.computeDistribution)(easeValues, {
100
+ boundaries: ease_buckets,
101
+ });
102
+ const intervals = (0, stats_utils_1.computeDistribution)(intervalValues, {
103
+ boundaries: interval_buckets,
104
+ unitSuffix: "d",
105
+ });
106
+ const result = {
107
+ deck,
108
+ counts,
109
+ ease,
110
+ intervals,
111
+ };
112
+ await context.reportProgress({ progress: 100, total: 100 });
113
+ this.logger.log(`Successfully retrieved statistics for deck "${deck}": ${counts.total} total cards, ` +
114
+ `${ease.count} cards with ease values, ${intervals.count} review cards`);
115
+ return (0, anki_utils_1.createSuccessResponse)(result);
116
+ }
117
+ catch (error) {
118
+ this.logger.error(`Failed to get deck statistics`, error);
119
+ return (0, anki_utils_1.createErrorResponse)(error, {
120
+ hint: "Make sure Anki is running and the deck name is correct. Use list_decks to see available decks.",
121
+ });
122
+ }
123
+ }
124
+ };
125
+ exports.DeckStatsTool = DeckStatsTool;
126
+ __decorate([
127
+ (0, mcp_nest_1.Tool)({
128
+ name: "deck_stats",
129
+ description: "Get comprehensive statistics for a single deck including card counts, ease factor distribution, and interval distribution. " +
130
+ "Use this to analyze deck health, identify struggling cards (low ease), or understand review scheduling patterns. " +
131
+ "Ease buckets and interval buckets can be customized to focus on specific ranges.",
132
+ parameters: zod_1.z.object({
133
+ deck: zod_1.z
134
+ .string()
135
+ .min(1)
136
+ .describe("Deck name to get statistics for (e.g., 'Japanese::JLPT N5')"),
137
+ ease_buckets: zod_1.z
138
+ .array(zod_1.z.number().positive())
139
+ .optional()
140
+ .default([2.0, 2.5, 3.0])
141
+ .refine((arr) => arr.length === 0 || arr.every((v, i, a) => i === 0 || v > a[i - 1]), {
142
+ message: "Bucket boundaries must be in ascending order",
143
+ })
144
+ .describe("Bucket boundaries for ease factor distribution. Default: [2.0, 2.5, 3.0]. " +
145
+ "Example: [2.0, 2.5, 3.0] creates buckets: <2.0, 2.0-2.5, 2.5-3.0, >3.0"),
146
+ interval_buckets: zod_1.z
147
+ .array(zod_1.z.number().positive())
148
+ .optional()
149
+ .default([7, 21, 90])
150
+ .refine((arr) => arr.length === 0 || arr.every((v, i, a) => i === 0 || v > a[i - 1]), {
151
+ message: "Bucket boundaries must be in ascending order",
152
+ })
153
+ .describe("Bucket boundaries for interval distribution in days. Default: [7, 21, 90]. " +
154
+ "Example: [7, 21, 90] creates buckets: <7d, 7-21d, 21-90d, >90d"),
155
+ }),
156
+ }),
157
+ __metadata("design:type", Function),
158
+ __metadata("design:paramtypes", [Object, Object]),
159
+ __metadata("design:returntype", Promise)
160
+ ], DeckStatsTool.prototype, "execute", null);
161
+ exports.DeckStatsTool = DeckStatsTool = DeckStatsTool_1 = __decorate([
162
+ (0, common_1.Injectable)(),
163
+ __metadata("design:paramtypes", [anki_connect_client_1.AnkiConnectClient])
164
+ ], DeckStatsTool);
165
+ //# sourceMappingURL=deck-stats.tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deck-stats.tool.js","sourceRoot":"","sources":["../../../../../../src/mcp/primitives/essential/tools/deck-stats/deck-stats.tool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import { DistributionMetrics } from "@/mcp/utils/stats.utils";
2
+ export interface AnkiDeckStatsResponse {
3
+ deck_id: number;
4
+ name: string;
5
+ new_count: number;
6
+ learn_count: number;
7
+ review_count: number;
8
+ total_in_deck: number;
9
+ }
10
+ export interface DeckStatsParams {
11
+ deck: string;
12
+ ease_buckets?: number[];
13
+ interval_buckets?: number[];
14
+ }
15
+ export interface DeckStatsResult {
16
+ deck: string;
17
+ counts: {
18
+ total: number;
19
+ new: number;
20
+ learning: number;
21
+ review: number;
22
+ };
23
+ ease: DistributionMetrics;
24
+ intervals: DistributionMetrics;
25
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=deck-stats.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deck-stats.types.js","sourceRoot":"","sources":["../../../../../../src/mcp/primitives/essential/tools/deck-stats/deck-stats.types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export { DeckStatsTool } from "./deck-stats.tool";
2
+ export type { DeckStatsParams, DeckStatsResult } from "./deck-stats.types";
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DeckStatsTool = void 0;
4
+ var deck_stats_tool_1 = require("./deck-stats.tool");
5
+ Object.defineProperty(exports, "DeckStatsTool", { enumerable: true, get: function () { return deck_stats_tool_1.DeckStatsTool; } });
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../src/mcp/primitives/essential/tools/deck-stats/index.ts"],"names":[],"mappings":";;;AAAA,qDAAkD;AAAzC,gHAAA,aAAa,OAAA"}
@@ -0,0 +1,2 @@
1
+ export { ReviewStatsTool } from "./review-stats.tool";
2
+ export type { ReviewStatsParams, ReviewStatsResult, } from "./review-stats.types";
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ReviewStatsTool = void 0;
4
+ var review_stats_tool_1 = require("./review-stats.tool");
5
+ Object.defineProperty(exports, "ReviewStatsTool", { enumerable: true, get: function () { return review_stats_tool_1.ReviewStatsTool; } });
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../src/mcp/primitives/essential/tools/review-stats/index.ts"],"names":[],"mappings":";;;AAAA,yDAAsD;AAA7C,oHAAA,eAAe,OAAA"}
@@ -0,0 +1,108 @@
1
+ import type { Context } from "@rekog/mcp-nest";
2
+ import { AnkiConnectClient } from "@/mcp/clients/anki-connect.client";
3
+ export declare class ReviewStatsTool {
4
+ private readonly ankiClient;
5
+ private readonly logger;
6
+ constructor(ankiClient: AnkiConnectClient);
7
+ execute(params: {
8
+ deck: string;
9
+ start_date: string;
10
+ end_date?: string;
11
+ }, context: Context): Promise<{
12
+ [x: string]: unknown;
13
+ content: ({
14
+ type: "text";
15
+ text: string;
16
+ annotations?: {
17
+ audience?: ("user" | "assistant")[] | undefined;
18
+ priority?: number | undefined;
19
+ lastModified?: string | undefined;
20
+ } | undefined;
21
+ _meta?: {
22
+ [x: string]: unknown;
23
+ } | undefined;
24
+ } | {
25
+ type: "image";
26
+ data: string;
27
+ mimeType: string;
28
+ annotations?: {
29
+ audience?: ("user" | "assistant")[] | undefined;
30
+ priority?: number | undefined;
31
+ lastModified?: string | undefined;
32
+ } | undefined;
33
+ _meta?: {
34
+ [x: string]: unknown;
35
+ } | undefined;
36
+ } | {
37
+ type: "audio";
38
+ data: string;
39
+ mimeType: string;
40
+ annotations?: {
41
+ audience?: ("user" | "assistant")[] | undefined;
42
+ priority?: number | undefined;
43
+ lastModified?: string | undefined;
44
+ } | undefined;
45
+ _meta?: {
46
+ [x: string]: unknown;
47
+ } | undefined;
48
+ } | {
49
+ uri: string;
50
+ name: string;
51
+ type: "resource_link";
52
+ description?: string | undefined;
53
+ mimeType?: string | undefined;
54
+ annotations?: {
55
+ audience?: ("user" | "assistant")[] | undefined;
56
+ priority?: number | undefined;
57
+ lastModified?: string | undefined;
58
+ } | undefined;
59
+ _meta?: {
60
+ [x: string]: unknown;
61
+ } | undefined;
62
+ icons?: {
63
+ src: string;
64
+ mimeType?: string | undefined;
65
+ sizes?: string[] | undefined;
66
+ theme?: "light" | "dark" | undefined;
67
+ }[] | undefined;
68
+ title?: string | undefined;
69
+ } | {
70
+ type: "resource";
71
+ resource: {
72
+ uri: string;
73
+ text: string;
74
+ mimeType?: string | undefined;
75
+ _meta?: {
76
+ [x: string]: unknown;
77
+ } | undefined;
78
+ } | {
79
+ uri: string;
80
+ blob: string;
81
+ mimeType?: string | undefined;
82
+ _meta?: {
83
+ [x: string]: unknown;
84
+ } | undefined;
85
+ };
86
+ annotations?: {
87
+ audience?: ("user" | "assistant")[] | undefined;
88
+ priority?: number | undefined;
89
+ lastModified?: string | undefined;
90
+ } | undefined;
91
+ _meta?: {
92
+ [x: string]: unknown;
93
+ } | undefined;
94
+ })[];
95
+ _meta?: {
96
+ [x: string]: unknown;
97
+ progressToken?: string | number | undefined;
98
+ "io.modelcontextprotocol/related-task"?: {
99
+ taskId: string;
100
+ } | undefined;
101
+ } | undefined;
102
+ structuredContent?: {
103
+ [x: string]: unknown;
104
+ } | undefined;
105
+ isError?: boolean | undefined;
106
+ }>;
107
+ private getTodayISO;
108
+ }
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var ReviewStatsTool_1;
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.ReviewStatsTool = void 0;
14
+ const common_1 = require("@nestjs/common");
15
+ const mcp_nest_1 = require("@rekog/mcp-nest");
16
+ const zod_1 = require("zod");
17
+ const anki_connect_client_1 = require("../../../../clients/anki-connect.client");
18
+ const anki_utils_1 = require("../../../../utils/anki.utils");
19
+ const stats_utils_1 = require("../../../../utils/stats.utils");
20
+ const MS_PER_DAY = 86400000;
21
+ let ReviewStatsTool = ReviewStatsTool_1 = class ReviewStatsTool {
22
+ ankiClient;
23
+ logger = new common_1.Logger(ReviewStatsTool_1.name);
24
+ constructor(ankiClient) {
25
+ this.ankiClient = ankiClient;
26
+ }
27
+ async execute(params, context) {
28
+ try {
29
+ const { deck, start_date } = params;
30
+ const end_date = params.end_date || this.getTodayISO();
31
+ this.logger.log(`Getting review statistics from ${start_date} to ${end_date} for deck: ${deck}`);
32
+ await context.reportProgress({ progress: 10, total: 100 });
33
+ const startTimestamp = new Date(start_date).getTime();
34
+ const endTimestamp = new Date(end_date).getTime() + MS_PER_DAY;
35
+ this.logger.log(`Fetching detailed review data for deck: ${deck}...`);
36
+ const reviews = await this.ankiClient.invoke("cardReviews", {
37
+ startID: startTimestamp,
38
+ deck: deck,
39
+ });
40
+ await context.reportProgress({ progress: 40, total: 100 });
41
+ const filteredReviews = reviews.filter((review) => review[0] <= endTimestamp);
42
+ this.logger.log("Calculating daily review counts from reviews...");
43
+ const reviewsByDayMap = new Map();
44
+ for (const review of filteredReviews) {
45
+ const date = new Date(review[0]).toISOString().split("T")[0];
46
+ reviewsByDayMap.set(date, (reviewsByDayMap.get(date) ?? 0) + 1);
47
+ }
48
+ const reviewsByDay = Array.from(reviewsByDayMap.entries())
49
+ .map(([date, count]) => ({ date, count }))
50
+ .sort((a, b) => a.date.localeCompare(b.date));
51
+ await context.reportProgress({ progress: 60, total: 100 });
52
+ const buttonPresses = filteredReviews.map((review) => review[3]);
53
+ this.logger.log("Computing retention metrics...");
54
+ const retention = (0, stats_utils_1.computeRetention)(buttonPresses);
55
+ await context.reportProgress({ progress: 80, total: 100 });
56
+ this.logger.log("Calculating summary statistics...");
57
+ const totalReviews = reviewsByDay.reduce((sum, r) => sum + r.count, 0);
58
+ const daysStudied = reviewsByDay.filter((r) => r.count > 0).length;
59
+ const averagePerDay = reviewsByDay.length > 0 ? totalReviews / reviewsByDay.length : 0;
60
+ const nonZeroDays = reviewsByDay.filter((r) => r.count > 0);
61
+ const maxDay = nonZeroDays.length > 0
62
+ ? nonZeroDays.reduce((max, r) => (r.count > max.count ? r : max))
63
+ : null;
64
+ const minDay = nonZeroDays.length > 0
65
+ ? nonZeroDays.reduce((min, r) => (r.count < min.count ? r : min))
66
+ : null;
67
+ const streak = (0, stats_utils_1.calculateStreak)(reviewsByDay);
68
+ await context.reportProgress({ progress: 90, total: 100 });
69
+ const result = {
70
+ period: {
71
+ start: start_date,
72
+ end: end_date,
73
+ },
74
+ deck: deck,
75
+ reviews_by_day: reviewsByDay,
76
+ summary: {
77
+ total_reviews: totalReviews,
78
+ average_per_day: averagePerDay,
79
+ days_studied: daysStudied,
80
+ max_day: maxDay,
81
+ min_day: minDay,
82
+ streak,
83
+ },
84
+ retention,
85
+ };
86
+ await context.reportProgress({ progress: 100, total: 100 });
87
+ this.logger.log(`Successfully retrieved review statistics: ${totalReviews} total reviews, ` +
88
+ `${daysStudied} days studied, ${(retention.overall * 100).toFixed(1)}% retention, ` +
89
+ `${streak} day streak`);
90
+ return (0, anki_utils_1.createSuccessResponse)(result);
91
+ }
92
+ catch (error) {
93
+ this.logger.error(`Failed to get review statistics`, error);
94
+ return (0, anki_utils_1.createErrorResponse)(error, {
95
+ hint: "Make sure Anki is running and date format is YYYY-MM-DD. Use list_decks to verify deck name if filtering by deck.",
96
+ });
97
+ }
98
+ }
99
+ getTodayISO() {
100
+ const today = new Date();
101
+ return today.toISOString().split("T")[0];
102
+ }
103
+ };
104
+ exports.ReviewStatsTool = ReviewStatsTool;
105
+ __decorate([
106
+ (0, mcp_nest_1.Tool)({
107
+ name: "review_stats",
108
+ description: "Get review history analysis including temporal patterns, retention metrics, and study streak information. " +
109
+ "Use this to analyze learning progress over time, identify review patterns, and track consistency. " +
110
+ "Requires a start date and deck name; end date defaults to today.",
111
+ parameters: zod_1.z
112
+ .object({
113
+ deck: zod_1.z
114
+ .string()
115
+ .describe("Deck name to filter reviews (REQUIRED - AnkiConnect API requires a deck)"),
116
+ start_date: zod_1.z
117
+ .string()
118
+ .regex(/^\d{4}-\d{2}-\d{2}$/, "Must be ISO date format: YYYY-MM-DD")
119
+ .refine((date) => !isNaN(Date.parse(date)), {
120
+ message: "Must be a valid date",
121
+ })
122
+ .describe("Start date for analysis (ISO format: YYYY-MM-DD) - REQUIRED"),
123
+ end_date: zod_1.z
124
+ .string()
125
+ .regex(/^\d{4}-\d{2}-\d{2}$/, "Must be ISO date format: YYYY-MM-DD")
126
+ .refine((date) => !isNaN(Date.parse(date)), {
127
+ message: "Must be a valid date",
128
+ })
129
+ .optional()
130
+ .describe("End date (defaults to today)"),
131
+ })
132
+ .refine((data) => {
133
+ if (!data.end_date)
134
+ return true;
135
+ return new Date(data.start_date) <= new Date(data.end_date);
136
+ }, {
137
+ message: "start_date must be less than or equal to end_date",
138
+ path: ["start_date"],
139
+ }),
140
+ }),
141
+ __metadata("design:type", Function),
142
+ __metadata("design:paramtypes", [Object, Object]),
143
+ __metadata("design:returntype", Promise)
144
+ ], ReviewStatsTool.prototype, "execute", null);
145
+ exports.ReviewStatsTool = ReviewStatsTool = ReviewStatsTool_1 = __decorate([
146
+ (0, common_1.Injectable)(),
147
+ __metadata("design:paramtypes", [anki_connect_client_1.AnkiConnectClient])
148
+ ], ReviewStatsTool);
149
+ //# sourceMappingURL=review-stats.tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review-stats.tool.js","sourceRoot":"","sources":["../../../../../../src/mcp/primitives/essential/tools/review-stats/review-stats.tool.ts"],"names":[],"mappings":";;;;;;;;;;;;;AAAA,2CAAoD;AACpD,8CAAuC;AAEvC,6BAAwB;AACxB,iFAAsE;AACtE,6DAGgC;AAChC,+DAA4E;AAI5E,MAAM,UAAU,GAAG,QAAQ,CAAC;AAMrB,IAAM,eAAe,uBAArB,MAAM,eAAe;IAGG;IAFZ,MAAM,GAAG,IAAI,eAAM,CAAC,iBAAe,CAAC,IAAI,CAAC,CAAC;IAE3D,YAA6B,UAA6B;QAA7B,eAAU,GAAV,UAAU,CAAmB;IAAG,CAAC;IA4CxD,AAAN,KAAK,CAAC,OAAO,CACX,MAIC,EACD,OAAgB;QAEhB,IAAI,CAAC;YACH,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,MAAM,CAAC;YACpC,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YAEvD,IAAI,CAAC,MAAM,CAAC,GAAG,CACb,kCAAkC,UAAU,OAAO,QAAQ,cAAc,IAAI,EAAE,CAChF,CAAC;YACF,MAAM,OAAO,CAAC,cAAc,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;YAI3D,MAAM,cAAc,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,OAAO,EAAE,CAAC;YACtD,MAAM,YAAY,GAAG,IAAI,IAAI,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,GAAG,UAAU,CAAC;YAG/D,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,2CAA2C,IAAI,KAAK,CAAC,CAAC;YAEtE,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,MAAM,CAC1C,aAAa,EACb;gBACE,OAAO,EAAE,cAAc;gBACvB,IAAI,EAAE,IAAI;aACX,CACF,CAAC;YAEF,MAAM,OAAO,CAAC,cAAc,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;YAG3D,MAAM,eAAe,GAAG,OAAO,CAAC,MAAM,CACpC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,YAAY,CACtC,CAAC;YAGF,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,iDAAiD,CAAC,CAAC;YACnE,MAAM,eAAe,GAAG,IAAI,GAAG,EAAkB,CAAC;YAElD,KAAK,MAAM,MAAM,IAAI,eAAe,EAAE,CAAC;gBACrC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC7D,eAAe,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAClE,CAAC;YAGD,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,CAAC;iBACvD,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;iBACzC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;YAEhD,MAAM,OAAO,CAAC,cAAc,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;YAI3D,MAAM,aAAa,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;YAGjE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;YAClD,MAAM,SAAS,GAAG,IAAA,8BAAgB,EAAC,aAAa,CAAC,CAAC;YAElD,MAAM,OAAO,CAAC,cAAc,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;YAG3D,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;YACrD,MAAM,YAAY,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;YACvE,MAAM,WAAW,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC;YACnE,MAAM,aAAa,GACjB,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;YAGnE,MAAM,WAAW,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;YAC5D,MAAM,MAAM,GACV,WAAW,CAAC,MAAM,GAAG,CAAC;gBACpB,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;gBACjE,CAAC,CAAC,IAAI,CAAC;YACX,MAAM,MAAM,GACV,WAAW,CAAC,MAAM,GAAG,CAAC;gBACpB,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;gBACjE,CAAC,CAAC,IAAI,CAAC;YAGX,MAAM,MAAM,GAAG,IAAA,6BAAe,EAAC,YAAY,CAAC,CAAC;YAE7C,MAAM,OAAO,CAAC,cAAc,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;YAE3D,MAAM,MAAM,GAAsB;gBAChC,MAAM,EAAE;oBACN,KAAK,EAAE,UAAU;oBACjB,GAAG,EAAE,QAAQ;iBACd;gBACD,IAAI,EAAE,IAAI;gBACV,cAAc,EAAE,YAAY;gBAC5B,OAAO,EAAE;oBACP,aAAa,EAAE,YAAY;oBAC3B,eAAe,EAAE,aAAa;oBAC9B,YAAY,EAAE,WAAW;oBACzB,OAAO,EAAE,MAAM;oBACf,OAAO,EAAE,MAAM;oBACf,MAAM;iBACP;gBACD,SAAS;aACV,CAAC;YAEF,MAAM,OAAO,CAAC,cAAc,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;YAC5D,IAAI,CAAC,MAAM,CAAC,GAAG,CACb,6CAA6C,YAAY,kBAAkB;gBACzE,GAAG,WAAW,kBAAkB,CAAC,SAAS,CAAC,OAAO,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe;gBACnF,GAAG,MAAM,aAAa,CACzB,CAAC;YAEF,OAAO,IAAA,kCAAqB,EAAC,MAAM,CAAC,CAAC;QACvC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,iCAAiC,EAAE,KAAK,CAAC,CAAC;YAC5D,OAAO,IAAA,gCAAmB,EAAC,KAAK,EAAE;gBAChC,IAAI,EAAE,mHAAmH;aAC1H,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAMO,WAAW;QACjB,MAAM,KAAK,GAAG,IAAI,IAAI,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3C,CAAC;CACF,CAAA;AAlLY,0CAAe;AA+CpB;IA1CL,IAAA,eAAI,EAAC;QACJ,IAAI,EAAE,cAAc;QACpB,WAAW,EACT,4GAA4G;YAC5G,oGAAoG;YACpG,kEAAkE;QACpE,UAAU,EAAE,OAAC;aACV,MAAM,CAAC;YACN,IAAI,EAAE,OAAC;iBACJ,MAAM,EAAE;iBACR,QAAQ,CACP,0EAA0E,CAC3E;YACH,UAAU,EAAE,OAAC;iBACV,MAAM,EAAE;iBACR,KAAK,CAAC,qBAAqB,EAAE,qCAAqC,CAAC;iBACnE,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE;gBAC1C,OAAO,EAAE,sBAAsB;aAChC,CAAC;iBACD,QAAQ,CACP,6DAA6D,CAC9D;YACH,QAAQ,EAAE,OAAC;iBACR,MAAM,EAAE;iBACR,KAAK,CAAC,qBAAqB,EAAE,qCAAqC,CAAC;iBACnE,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE;gBAC1C,OAAO,EAAE,sBAAsB;aAChC,CAAC;iBACD,QAAQ,EAAE;iBACV,QAAQ,CAAC,8BAA8B,CAAC;SAC5C,CAAC;aACD,MAAM,CACL,CAAC,IAAI,EAAE,EAAE;YACP,IAAI,CAAC,IAAI,CAAC,QAAQ;gBAAE,OAAO,IAAI,CAAC;YAChC,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC9D,CAAC,EACD;YACE,OAAO,EAAE,mDAAmD;YAC5D,IAAI,EAAE,CAAC,YAAY,CAAC;SACrB,CACF;KACJ,CAAC;;;;8CA0HD;0BAxKU,eAAe;IAD3B,IAAA,mBAAU,GAAE;qCAI8B,uCAAiB;GAH/C,eAAe,CAkL3B"}
@@ -0,0 +1,47 @@
1
+ import { RetentionMetrics } from "@/mcp/utils/stats.utils";
2
+ export interface ReviewStatsParams {
3
+ deck?: string;
4
+ start_date: string;
5
+ end_date?: string;
6
+ }
7
+ export interface CardReviewsParams {
8
+ startID: number;
9
+ deck?: string;
10
+ }
11
+ export type CardReviewTuple = [
12
+ timestamp: number,
13
+ cardId: number,
14
+ usn: number,
15
+ buttonPressed: number,
16
+ newInterval: number,
17
+ previousInterval: number,
18
+ ease: number,
19
+ timeTaken: number,
20
+ reviewType: number
21
+ ];
22
+ export interface ReviewStatsResult {
23
+ period: {
24
+ start: string;
25
+ end: string;
26
+ };
27
+ deck: string;
28
+ reviews_by_day: Array<{
29
+ date: string;
30
+ count: number;
31
+ }>;
32
+ summary: {
33
+ total_reviews: number;
34
+ average_per_day: number;
35
+ days_studied: number;
36
+ max_day: {
37
+ date: string;
38
+ count: number;
39
+ } | null;
40
+ min_day: {
41
+ date: string;
42
+ count: number;
43
+ } | null;
44
+ streak: number;
45
+ };
46
+ retention: RetentionMetrics;
47
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=review-stats.types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review-stats.types.js","sourceRoot":"","sources":["../../../../../../src/mcp/primitives/essential/tools/review-stats/review-stats.types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,28 @@
1
+ export interface DistributionMetrics {
2
+ mean: number;
3
+ median: number;
4
+ min: number;
5
+ max: number;
6
+ count: number;
7
+ buckets: Record<string, number>;
8
+ }
9
+ export interface BucketConfig {
10
+ boundaries: number[];
11
+ formatLabel?: (lower: number | null, upper: number | null) => string;
12
+ unitSuffix?: string;
13
+ }
14
+ export interface RetentionMetrics {
15
+ overall: number;
16
+ by_rating: {
17
+ again: number;
18
+ hard: number;
19
+ good: number;
20
+ easy: number;
21
+ };
22
+ }
23
+ export declare function computeDistribution(values: number[], config: BucketConfig): DistributionMetrics;
24
+ export declare function computeRetention(buttonPresses: number[]): RetentionMetrics;
25
+ export declare function calculateStreak(reviewsByDay: Array<{
26
+ date: string;
27
+ count: number;
28
+ }>): number;