@ankimcp/anki-mcp-server
Version:
Model Context Protocol server for Anki - enables AI assistants to interact with your Anki flashcards
163 lines • 7.83 kB
JavaScript
;
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
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;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var CreateModelTool_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.CreateModelTool = void 0;
const common_1 = require("@nestjs/common");
const microservices_1 = require("@nestjs/microservices");
const mcp_nest_1 = require("@rekog/mcp-nest");
const zod_1 = require("zod");
const anki_connect_client_1 = require("../../../clients/anki-connect.client.js");
const anki_utils_1 = require("../../../utils/anki.utils.js");
let CreateModelTool = CreateModelTool_1 = class CreateModelTool {
ankiClient;
logger = new common_1.Logger(CreateModelTool_1.name);
constructor(ankiClient) {
this.ankiClient = ankiClient;
}
async createModel({ modelName, inOrderFields, cardTemplates, css, isCloze, }) {
try {
this.logger.log(`Creating model: ${modelName} with ${inOrderFields.length} fields`);
const warnings = [];
const fieldSet = new Set(inOrderFields);
for (const template of cardTemplates) {
const templateContent = `${template.Front} ${template.Back}`;
const fieldRefs = templateContent.match(/\{\{([^}]+)\}\}/g) || [];
for (const ref of fieldRefs) {
const fieldName = ref.slice(2, -2).trim();
if (fieldName === "FrontSide" ||
fieldName === "Tags" ||
fieldName === "Type" ||
fieldName === "Deck" ||
fieldName === "Subdeck" ||
fieldName === "Card" ||
fieldName.startsWith("cloze:")) {
continue;
}
if (!fieldSet.has(fieldName)) {
warnings.push(`Template "${template.Name}" references field "{{${fieldName}}}" which is not in inOrderFields`);
}
}
}
const result = await this.ankiClient.invoke("createModel", {
modelName,
inOrderFields,
cardTemplates,
css,
isCloze: isCloze ?? false,
});
this.logger.log(`Successfully created model: ${modelName}`);
const response = {
success: true,
modelName,
modelId: result.id || null,
fields: inOrderFields,
templateCount: cardTemplates.length,
hasCss: !!css,
isCloze: isCloze || false,
message: `Successfully created model "${modelName}" with ${inOrderFields.length} fields and ${cardTemplates.length} template(s)`,
};
if (warnings.length > 0) {
response.warnings = warnings;
response.message +=
". Note: Some warnings were detected (see warnings field).";
}
return response;
}
catch (error) {
this.logger.error(`Failed to create model ${modelName}`, error);
const errorMessage = error instanceof Error ? error.message : String(error);
if (errorMessage.includes("already exists") ||
errorMessage.includes("duplicate")) {
return (0, anki_utils_1.createErrorResponse)(error, {
modelName,
hint: "A model with this name already exists, and model names are unique. modelNames lists the existing models.",
});
}
return (0, anki_utils_1.createErrorResponse)(error, {
modelName,
hint: "This can happen when Anki is not running or a parameter is invalid",
});
}
}
};
exports.CreateModelTool = CreateModelTool;
__decorate([
(0, mcp_nest_1.Tool)({
name: "createModel",
description: "Create a new note type (model) in Anki with custom fields, card templates, and styling. " +
"Useful for creating specialized models like RTL (Right-to-Left) language models for Hebrew, Arabic, etc. " +
"Each model defines the structure of notes and how cards are generated from them.",
parameters: zod_1.z.object({
modelName: zod_1.z
.string()
.min(1)
.describe('Unique name for the new model (e.g., "Basic RTL", "Advanced Vocabulary")'),
inOrderFields: zod_1.z
.array(zod_1.z.string().min(1))
.min(1)
.describe('Field names in order (e.g., ["Front", "Back"]). At least one field required.'),
cardTemplates: zod_1.z
.array(zod_1.z.object({
Name: zod_1.z.string().min(1).describe('Template name (e.g., "Card 1")'),
Front: zod_1.z
.string()
.min(1)
.describe('Front template HTML with field placeholders (e.g., "{{Front}}")'),
Back: zod_1.z
.string()
.min(1)
.describe('Back template HTML with field placeholders (e.g., "{{FrontSide}}<hr id=answer>{{Back}}")'),
}))
.min(1)
.describe("Card templates (at least one required). Each template generates one card per note."),
css: zod_1.z
.string()
.optional()
.describe('Optional CSS styling for cards. For RTL languages, include "direction: rtl;" in .card class.'),
isCloze: zod_1.z
.boolean()
.optional()
.default(false)
.describe("Create as cloze deletion model (default: false)"),
}),
outputSchema: zod_1.z.object({
success: zod_1.z.boolean(),
modelName: zod_1.z.string(),
modelId: zod_1.z.number().nullable(),
fields: zod_1.z.array(zod_1.z.string()),
templateCount: zod_1.z.number(),
hasCss: zod_1.z.boolean(),
isCloze: zod_1.z.boolean(),
message: zod_1.z.string(),
warnings: zod_1.z.array(zod_1.z.string()).optional(),
}),
annotations: {
title: "Create Note Type",
readOnlyHint: false,
destructiveHint: false,
idempotentHint: false,
openWorldHint: false,
},
}),
__param(0, (0, microservices_1.Payload)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)
], CreateModelTool.prototype, "createModel", null);
exports.CreateModelTool = CreateModelTool = CreateModelTool_1 = __decorate([
(0, mcp_nest_1.McpController)(),
__metadata("design:paramtypes", [anki_connect_client_1.AnkiConnectClient])
], CreateModelTool);
//# sourceMappingURL=create-model.tool.js.map