An MCP server for AI-powered Anki interaction using a codemode architecture — the AI writes TypeScript code that executes against an injected anki API.
This server implements the codemode pattern pioneered by Cloudflare: instead of exposing dozens of discrete MCP tools, we provide a TypeScript API that the AI writes code against.
"LLMs are better at writing code to call APIs than at calling tools directly"
LLMs have been trained on billions of lines of real code, but relatively few synthetic tool-calling examples. Writing await anki.notes.add({...}) is natural — it's just TypeScript.
| Traditional | Codemode |
|---|---|
| 40+ discrete tools | 4 scoped tools |
| Fixed parameters | Full TypeScript flexibility |
| One operation per call | Compose multiple operations |
| Tool explosion as features grow | API grows, tools stay stable |
Benefits:
- Composable — Chain operations without round-trips through the LLM
- Efficient — Results flow through code, not back through the model for each step
- Familiar — Standard patterns the AI already knows from training data
| Tool | Purpose | Key APIs |
|---|---|---|
anki-create |
Add new content | decks.create, notes.add, notes.addMany |
anki-manage |
Work with existing | notes.find, notes.update, notes.delete, cards.*, tags.* |
anki-review |
Study sessions | gui.*, sync, prompts.reviewSession |
anki-design |
Customize templates | models.create, models.styling, media.*, stats.* |
- Anki desktop app (must be running)
- AnkiConnect plugin installed in Anki
- Bun runtime
git clone https://github.com/youruser/anki-mcp-codemode
cd anki-mcp-codemode
bun installAdd to your MCP configuration:
{
"mcpServers": {
"anki": {
"command": "bun",
"args": ["run", "/absolute/path/to/anki-mcp-codemode/src/index.ts"],
"env": {
"ANKI_CONNECT_URL": "http://localhost:8765"
}
}
}
}| Variable | Default | Description |
|---|---|---|
ANKI_TOOLS |
all |
Tools to enable: all or comma-separated (create,manage) |
ANKI_CONNECT_URL |
http://localhost:8765 |
AnkiConnect URL |
ANKI_CONNECT_API_KEY |
— | API key (if configured in AnkiConnect) |
ANKI_CONNECT_TIMEOUT |
10000 |
Request timeout in ms |
MCP_TRANSPORT |
stdio |
stdio or http (Streamable HTTP) |
MCP_HTTP_PORT |
8766 |
Port for HTTP transport |
MCP_HTTP_HOST |
127.0.0.1 |
Bind address; non-localhost requires MCP_HTTP_TOKEN |
MCP_HTTP_TOKEN |
— | Static bearer token required on every HTTP request |
Run the server over Streamable HTTP instead of stdio:
MCP_TRANSPORT=http MCP_HTTP_TOKEN=your-secret bun run src/index.ts
# → http://127.0.0.1:8766/mcp (health check at /health)Connect from Claude Code:
claude mcp add --transport http anki http://your-host:8766/mcp \
--header "Authorization: Bearer your-secret"Notes for remote deployment:
- The server needs a reachable AnkiConnect, which means a running Anki. On a
headless VPS, run Anki Desktop with
QT_QPA_PLATFORM=offscreenand the AnkiConnect addon bound to a reachable address (community Docker images:ankimcp/headless-anki,ThisIsntTheWay/headless-anki). - The HTTP transport runs stateless (fresh session per request, JSON responses), which suits a single-user server behind a VPN/Tailscale.
- Executed code runs in-process with no sandbox — treat the bearer token as full control over the machine, not just the Anki collection, and keep the port off the public internet.
Only enable the tools you need:
{
"env": {
"ANKI_TOOLS": "create,review"
}
}// List available decks and models
const decks = await anki.decks.list();
const models = await anki.models.list();
console.log({ decks, models });
// Create a deck and add cards
await anki.decks.create("Spanish Vocabulary");
const noteId = await anki.notes.add({
deckName: "Spanish Vocabulary",
modelName: "Basic",
fields: { Front: "Hola", Back: "Hello" },
tags: ["greeting", "beginner"]
});
console.log({ created: noteId });// Add multiple cards efficiently
const vocab = [
{ front: "perro", back: "dog" },
{ front: "gato", back: "cat" },
{ front: "pájaro", back: "bird" },
];
const noteIds = await anki.notes.addMany(
vocab.map(v => ({
deckName: "Spanish Vocabulary",
modelName: "Basic",
fields: { Front: v.front, Back: v.back }
}))
);
// null marks a note AnkiConnect refused, usually a duplicate
console.log({ added: noteIds.filter(id => id !== null).length });// Get the twenty rules prompt for effective card creation
const rules = anki.prompts.twentyRules();
console.log(rules);See docs/API.md for the complete API documentation for each tool.
bun install # Install dependencies
bun run src/index.ts # Run server
bun test # Run tests
bunx tsc --noEmit # Type check- MCP client sends TypeScript code to one of the 4 tools
- Server executes the code with an injected
ankiobject (in-process — trusted-client model, not a security sandbox) - The
ankiobject provides type-safe methods that call AnkiConnect console.log()output is captured and returned to the AI
┌─────────────┐ TypeScript ┌─────────────┐ HTTP ┌─────────────┐
│ AI Agent │ ──── code ────────▶ │ MCP Server │ ────────────▶ │ AnkiConnect │
│ (Claude) │ ◀─── console.log ── │ (Bun) │ ◀──────────── │ (Anki) │
└─────────────┘ └─────────────┘ └─────────────┘
This project was inspired by and builds upon concepts from @anthropics/anki-mcp-server (formerly anki-mcp-http). While the implementation is a complete rewrite using a different architecture (Bun + codemode vs NestJS + discrete tools), credit goes to the original authors for pioneering Anki + MCP integration.
MIT — Use freely, attribution appreciated.
- Anki® is a registered trademark of Ankitects Pty Ltd. This is an unofficial third-party tool.
- Model Context Protocol (MCP) is an open standard by Anthropic.