UNPKG

@ankimcp/anki-mcp-server

Version:

Model Context Protocol server for Anki - enables AI assistants to interact with your Anki flashcards

224 lines • 10.5 kB
"use strict"; 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 AddNoteTool_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.AddNoteTool = 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 AddNoteTool = AddNoteTool_1 = class AddNoteTool { ankiClient; logger = new common_1.Logger(AddNoteTool_1.name); constructor(ankiClient) { this.ankiClient = ankiClient; } async addNote({ deckName, modelName, fields, tags, allowDuplicate, duplicateScope, duplicateScopeOptions, }) { try { this.logger.log(`Adding note to deck "${deckName}" with model "${modelName}"`); const fieldNames = await this.ankiClient.invoke("modelFieldNames", { modelName: modelName, }); if (!fieldNames || fieldNames.length === 0) { return (0, anki_utils_1.createErrorResponse)(new Error(`Model "${modelName}" not found or has no fields`), { modelName, hint: "modelNames lists the available models", }); } const sortField = fieldNames[0]; const sortFieldValue = fields[sortField]; if (!sortFieldValue || sortFieldValue.trim() === "") { return (0, anki_utils_1.createErrorResponse)(new Error(`The first field "${sortField}" cannot be empty. Anki requires the sort field to have content.`), { modelName, sortField, providedFields: Object.keys(fields), hint: `The first field "${sortField}" is the sort field and must contain non-empty content.`, }); } const noteParams = { deckName: deckName, modelName: modelName, fields: fields, }; if (tags && tags.length > 0) { noteParams.tags = tags; } const options = {}; let hasOptions = false; if (allowDuplicate !== undefined) { options.allowDuplicate = allowDuplicate; hasOptions = true; } if (duplicateScope !== undefined) { options.duplicateScope = duplicateScope; hasOptions = true; } if (duplicateScopeOptions !== undefined) { options.duplicateScopeOptions = duplicateScopeOptions; hasOptions = true; } if (hasOptions) { noteParams.options = options; } const noteId = await this.ankiClient.invoke("addNote", { note: noteParams, }); if (!noteId) { this.logger.warn("Note creation failed - possibly a duplicate"); return (0, anki_utils_1.createErrorResponse)(new Error("Failed to create note - it may be a duplicate"), { deckName, modelName, hint: allowDuplicate ? "The note could not be created: AnkiConnect returned no note ID." : "This note may duplicate an existing one; with `allowDuplicate: true` the duplicate is created anyway.", }); } this.logger.log(`Successfully created note with ID: ${noteId}`); const fieldCount = Object.keys(fields).length; const tagCount = tags ? tags.length : 0; const effectiveScope = allowDuplicate ? "none" : duplicateScope || "default"; return { success: true, noteId: noteId, deckName: deckName, modelName: modelName, message: `Successfully created note in deck "${deckName}"`, details: { fieldsAdded: fieldCount, tagsAdded: tagCount, duplicateCheckScope: effectiveScope, }, }; } catch (error) { this.logger.error("Failed to add note", error); if (error instanceof Error) { const lowerMessage = error.message.toLowerCase(); if (lowerMessage.includes("duplicate")) { return (0, anki_utils_1.createErrorResponse)(error, { deckName, modelName, hint: "This note duplicates an existing one. With `allowDuplicate: true` the duplicate is created anyway.", }); } if (error.message.includes("model")) { return (0, anki_utils_1.createErrorResponse)(error, { deckName, modelName, hint: "Model not found. modelNames lists the available models.", }); } if (error.message.includes("deck")) { return (0, anki_utils_1.createErrorResponse)(error, { deckName, modelName, hint: "Deck not found. listDecks lists the available decks; createDeck creates a new one.", }); } if (error.message.includes("field")) { return (0, anki_utils_1.createErrorResponse)(error, { deckName, modelName, providedFields: Object.keys(fields), hint: "Field mismatch. modelFieldNames lists the fields this model requires.", }); } } return (0, anki_utils_1.createErrorResponse)(error, { deckName, modelName, hint: "This can happen when Anki is not running or the deck/model names are wrong", }); } } }; exports.AddNoteTool = AddNoteTool; __decorate([ (0, mcp_nest_1.Tool)({ name: "addNote", description: "Add a SINGLE note to Anki. The addNotes batch tool creates several notes in one call — AnkiConnect processes requests one at a time, so repeated (especially parallel) addNote calls are slower. modelNames lists the available note types and modelFieldNames their fields. Returns the note ID on success. The note and its cards are added to the user's collection and appear in their reviews.", parameters: zod_1.z.object({ deckName: zod_1.z.string().min(1).describe("The deck to add the note to"), modelName: zod_1.z .string() .min(1) .describe('The note type/model to use (e.g., "Basic", "Cloze")'), fields: zod_1.z .record(zod_1.z.string(), zod_1.z.string()) .describe('Field values as key-value pairs (e.g., {"Front": "question", "Back": "answer"})'), tags: zod_1.z .array(zod_1.z.string()) .optional() .describe("Optional tags to add to the note"), allowDuplicate: zod_1.z .boolean() .optional() .default(false) .describe("Whether to allow adding duplicate notes"), duplicateScope: zod_1.z .enum(["deck", "collection"]) .optional() .describe("Scope for duplicate checking"), duplicateScopeOptions: zod_1.z .object({ deckName: zod_1.z .string() .optional() .describe("Specific deck to check for duplicates"), checkChildren: zod_1.z .boolean() .optional() .default(false) .describe("Check child decks for duplicates"), checkAllModels: zod_1.z .boolean() .optional() .default(false) .describe("Check across all note types"), }) .optional() .describe("Advanced duplicate checking options"), }), outputSchema: zod_1.z.object({ success: zod_1.z.boolean(), noteId: zod_1.z.number(), deckName: zod_1.z.string(), modelName: zod_1.z.string(), message: zod_1.z.string(), details: zod_1.z.object({ fieldsAdded: zod_1.z.number(), tagsAdded: zod_1.z.number(), duplicateCheckScope: zod_1.z.enum(["deck", "collection", "default", "none"]), }), }), annotations: { title: "Add Note", 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) ], AddNoteTool.prototype, "addNote", null); exports.AddNoteTool = AddNoteTool = AddNoteTool_1 = __decorate([ (0, mcp_nest_1.McpController)(), __metadata("design:paramtypes", [anki_connect_client_1.AnkiConnectClient]) ], AddNoteTool); //# sourceMappingURL=add-note.tool.js.map