@ankimcp/anki-mcp-server
Version:
Model Context Protocol server for Anki - enables AI assistants to interact with your Anki flashcards
151 lines • 4.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.READ_ONLY_HINT = void 0;
exports.cleanHtml = cleanHtml;
exports.extractRenderedCardContent = extractRenderedCardContent;
exports.getCardType = getCardType;
exports.getNoteType = getNoteType;
exports.getRatingDescription = getRatingDescription;
exports.createSuccessResponse = createSuccessResponse;
exports.createErrorResponse = createErrorResponse;
exports.formatInterval = formatInterval;
exports.parseDeckStats = parseDeckStats;
const anki_types_1 = require("../types/anki.types");
const anki_connect_client_1 = require("../clients/anki-connect.client");
exports.READ_ONLY_HINT = "The server is running in read-only mode, which blocks this operation. " +
"Read-only mode is set by the READ_ONLY environment variable or the --read-only flag.";
const ANSWER_SEPARATOR_REGEX = /<hr\b[^>]*\bid=["']?answer["']?[^>]*>/i;
function cleanHtml(html) {
if (!html) {
return "";
}
return (html
.replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, "")
.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, "")
.replace(/<br\s*\/?>/gi, "\n")
.replace(/<\/(?:div|p)>/gi, "\n")
.replace(/<[^>]*>/g, "")
.replace(/ /g, " ")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, '"')
.replace(/'/g, "'")
.replace(/&/g, "&")
.replace(/[ \t]+/g, " ")
.replace(/ *\n */g, "\n")
.replace(/\n{2,}/g, "\n")
.trim());
}
function extractRenderedCardContent(card) {
const question = card.question ?? "";
const answer = card.answer ?? "";
const separatorMatch = answer.match(ANSWER_SEPARATOR_REGEX);
const backHtml = separatorMatch && separatorMatch.index !== undefined
? answer.slice(separatorMatch.index + separatorMatch[0].length)
: answer;
return {
front: cleanHtml(question),
back: cleanHtml(backHtml),
};
}
function getCardType(type) {
switch (type) {
case anki_types_1.CardType.New:
return "new";
case anki_types_1.CardType.Learning:
return "learning";
case anki_types_1.CardType.Review:
return "review";
case anki_types_1.CardType.Relearning:
return "relearning";
default:
return "unknown";
}
}
function getNoteType(modelName) {
const lowerName = modelName.toLowerCase();
if (lowerName.includes("basic")) {
if (lowerName.includes("reverse")) {
return "Basic (and reversed card)";
}
return "Basic";
}
if (lowerName.includes("cloze")) {
return "Cloze";
}
return "Custom";
}
function getRatingDescription(rating) {
switch (rating) {
case anki_types_1.CardRating.Again:
return "Again (failed to recall)";
case anki_types_1.CardRating.Hard:
return "Hard (recalled with difficulty)";
case anki_types_1.CardRating.Good:
return "Good (recalled with some effort)";
case anki_types_1.CardRating.Easy:
return "Easy (recalled instantly)";
default:
return "Unknown";
}
}
function createSuccessResponse(data) {
return {
content: [
{
type: "text",
text: JSON.stringify(data, null, 2),
},
],
};
}
function createErrorResponse(error, context) {
const errorData = {
success: false,
error: error instanceof Error ? error.message : "Unknown error occurred",
};
if (error instanceof anki_connect_client_1.AnkiConnectError) {
if (error.action) {
errorData.action = error.action;
}
}
if (context) {
Object.assign(errorData, context);
}
if (error instanceof anki_connect_client_1.ReadOnlyModeError) {
errorData.hint = exports.READ_ONLY_HINT;
}
return {
content: [
{
type: "text",
text: JSON.stringify(errorData, null, 2),
},
],
isError: true,
};
}
function formatInterval(days) {
if (days < 1) {
const hours = Math.round(days * 24);
return `${hours} hour${hours !== 1 ? "s" : ""}`;
}
if (days < 30) {
return `${Math.round(days)} day${days !== 1 ? "s" : ""}`;
}
if (days < 365) {
const months = Math.round(days / 30);
return `${months} month${months !== 1 ? "s" : ""}`;
}
const years = Math.round((days / 365) * 10) / 10;
return `${years} year${years !== 1 ? "s" : ""}`;
}
function parseDeckStats(stats) {
return {
new_count: stats.new_count || 0,
learn_count: stats.lrn_count || 0,
review_count: stats.rev_count || 0,
total_cards: stats.total || 0,
};
}
//# sourceMappingURL=anki.utils.js.map