import {
McpServer,
ResourceTemplate,
} from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { AnkiConnector } from "./anki/ankiConnectorClient";
import { z } from "zod";
const server = new McpServer({
name: "anki-connect",
version: "0.1.0",
});
const ankiConnector = new AnkiConnector();
server.tool(
"list-decks",
"Show list of user's Anki decks and their IDs",
{},
{
readOnlyHint: true,
},
async () => {
const decks = await ankiConnector.deckNamesAndIds();
const deckList = Object.keys(decks).map((name) => ({
name,
id: decks[name],
uri: `anki://decks/${name}`,
}));
return {
content: deckList.map((deck) => ({
type: "text",
uri: deck.uri,
text: `Name: ${deck.name}\nID: ${deck.id}`,
})),
};
},
);
server.tool(
"find-cards",
"Find Anki cards in user decks",
{
query: z.string(),
},
{
readOnlyHint: true,
},
async ({ query }) => {
const cards = await ankiConnector.searchCards(query);
const contentDetails = cards.map(
(card) => ({ type: "text", text: JSON.stringify(card) }) as const,
);
return {
content: contentDetails,
};
},
);
server.tool(
"find-notes",
"Find Anki notes in user decks",
{
query: z.string(),
},
{
readOnlyHint: true,
},
async ({ query }) => {
const notes = await ankiConnector.searchNotes(query);
const contentDetails = notes.map(
(note) =>
({
type: "text",
text: JSON.stringify(note),
}) as const,
);
return {
content: contentDetails,
};
},
);
server.tool(
"anki-request",
"Submit an arbitrary request through Anki connect",
{
action: z.string(),
params: z.any(),
},
async ({ action, params }) => {
const response = await ankiConnector.request(action, params);
return {
content: [{ type: "text", text: JSON.stringify(response, null, 2) }],
};
},
);
server.tool(
"model-names-and-ids",
"Get a list of Anki models",
{},
{
readOnlyHint: true,
},
async () => {
const models = await ankiConnector.modelNamesAndIds();
return {
content: [
{
type: "text",
text: JSON.stringify(models, null, 2),
},
],
};
},
);
server.tool(
"add-note",
"Add a new note to Anki. Requires a precise deckName, modelName and additional details to add",
{
note: z.object({
deckName: z.string(),
modelName: z.string(),
fields: z.record(z.string(), z.string()),
tags: z.array(z.string()).optional(),
audio: z.any().optional(),
video: z.any().optional(),
picture: z.any().optional(),
}),
},
async ({ note }) => {
const noteId = await ankiConnector.addNote(note);
return {
content: [
{
type: "text",
text: `Note added with ID: ${noteId}`,
},
],
};
},
);
server.tool(
"update-note-fields",
"Update fields in the Anki Note. Must know the fields defined in the model prior to update",
{
id: z.number(),
fields: z.record(z.string(), z.string()),
},
async ({ id, fields }) => {
const request = { note: { id, fields } };
const response = await ankiConnector.updateNoteFields(request);
return {
content: [
{
type: "text",
text: JSON.stringify(response),
},
],
};
},
);
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Connected to the anki-mcp server");
}
main().catch((err) => {
console.error(err);
});