Loading...
Loading...
Integrate Anki spaced repetition flashcards with AI assistants through Model Context Protocol for study sessions, deck management, and card creation
npx skill4agent add aradotso/mcp-skills anki-mcp-server-integrationSkill by ara.so — MCP Skills collection.
.mcpb.mcpbhttp://localhost:8765claude_desktop_config.json{
"mcpServers": {
"anki-mcp": {
"command": "npx",
"args": ["-y", "@ankimcp/anki-mcp-server", "--stdio"],
"env": {
"ANKI_CONNECT_URL": "http://localhost:8765"
}
}
}
}~/Library/Application Support/Claude/claude_desktop_config.json%APPDATA%\Claude\claude_desktop_config.json{
"mcpServers": {
"anki-mcp": {
"command": "npx",
"args": ["-y", "@ankimcp/anki-mcp-server", "--stdio"],
"env": {
"ANKI_CONNECT_URL": "http://localhost:8765"
}
}
}
}~/.cursor/mcp.json# One-time setup
npm install -g @ankimcp/anki-mcp-server
npm install -g ngrok
ngrok config add-authtoken <YOUR_NGROK_TOKEN>
# Start server with public tunnel
ankimcp --ngrok# AnkiConnect URL (default: http://localhost:8765)
ANKI_CONNECT_URL=http://localhost:8765
# Enable read-only mode (no write operations)
READ_ONLY=trueankimcp [options]
--stdio # STDIO mode for MCP clients
--port <port> # HTTP port (default: 3000)
--host <host> # HTTP host (default: 127.0.0.1)
--anki-connect <url> # AnkiConnect URL
--ngrok # Start ngrok tunnel
--read-only # Prevent modifications// User says: "Help me review my Spanish deck"
// AI workflow:
// 1. Sync with AnkiWeb
await use_mcp_tool({
server_name: "anki-mcp",
tool_name: "sync",
arguments: {}
});
// 2. Get due cards from Spanish deck
const dueCards = await use_mcp_tool({
server_name: "anki-mcp",
tool_name: "get_due_cards",
arguments: {
deck: "Spanish"
}
});
// 3. Present first card
const presentation = await use_mcp_tool({
server_name: "anki-mcp",
tool_name: "present_card",
arguments: {
card_id: dueCards.cards[0].cardId
}
});
// AI shows question, user answers, then AI rates:
await use_mcp_tool({
server_name: "anki-mcp",
tool_name: "rate_card",
arguments: {
card_id: dueCards.cards[0].cardId,
ease: 3 // 1=Again, 2=Hard, 3=Good, 4=Easy
}
});// User says: "Create 10 Arabic vocab cards with RTL styling"
// 1. List available note types
const models = await use_mcp_tool({
server_name: "anki-mcp",
tool_name: "modelNames",
arguments: {}
});
// 2. Get fields for Basic note type
const fields = await use_mcp_tool({
server_name: "anki-mcp",
tool_name: "modelFieldNames",
arguments: {
modelName: "Basic"
}
});
// 3. Create notes in batch (up to 100 at once)
const result = await use_mcp_tool({
server_name: "anki-mcp",
tool_name: "addNotes",
arguments: {
notes: [
{
deckName: "Arabic::Vocabulary",
modelName: "Basic",
fields: {
Front: "مرحبا",
Back: "Hello"
},
tags: ["arabic", "greetings"]
},
{
deckName: "Arabic::Vocabulary",
modelName: "Basic",
fields: {
Front: "شكرا",
Back: "Thank you"
},
tags: ["arabic", "greetings"]
}
// ... up to 98 more notes
]
}
});
// Result includes successful IDs and any errors
console.log(result.success); // [note_id1, note_id2, ...]
console.log(result.errors); // Array of error objects if any failed// User says: "Import this image from my Downloads folder into the selected note"
// 1. Get selected note from Anki browser
const selected = await use_mcp_tool({
server_name: "anki-mcp",
tool_name: "guiSelectedNotes",
arguments: {}
});
// 2. Upload media file (auto-detects file path)
const media = await use_mcp_tool({
server_name: "anki-mcp",
tool_name: "storeMediaFile",
arguments: {
filename: "diagram.png",
data: "/Users/username/Downloads/diagram.png" // File path (fastest)
// OR url: "https://example.com/image.jpg" // URL download
// OR data: "base64string..." // Base64 (slowest, avoid)
}
});
// 3. Get note details
const noteInfo = await use_mcp_tool({
server_name: "anki-mcp",
tool_name: "notesInfo",
arguments: {
notes: [selected.result[0]]
}
});
// 4. Update front field with image
await use_mcp_tool({
server_name: "anki-mcp",
tool_name: "updateNoteFields",
arguments: {
note: {
id: selected.result[0],
fields: {
Front: `<img src="${media.result}"> ${noteInfo.result[0].fields.Front.value}`
}
}
}
});// List all decks with statistics
const decks = await use_mcp_tool({
server_name: "anki-mcp",
tool_name: "listDecks",
arguments: {
includeStats: true
}
});
// Get detailed deck statistics
const stats = await use_mcp_tool({
server_name: "anki-mcp",
tool_name: "deckStats",
arguments: {
decks: ["Spanish", "Spanish::Grammar"]
}
});
// Create nested deck (max 2 levels: Parent::Child)
await use_mcp_tool({
server_name: "anki-mcp",
tool_name: "createDeck",
arguments: {
deck: "French::Vocabulary"
}
});
// Move cards to different deck
await use_mcp_tool({
server_name: "anki-mcp",
tool_name: "changeDeck",
arguments: {
cards: [1234567890, 9876543210],
deck: "French::Advanced"
}
});// Search for notes using Anki query syntax
const notes = await use_mcp_tool({
server_name: "anki-mcp",
tool_name: "findNotes",
arguments: {
query: "deck:Spanish tag:verb" // Anki search syntax
}
});
// Get detailed note information
const noteDetails = await use_mcp_tool({
server_name: "anki-mcp",
tool_name: "notesInfo",
arguments: {
notes: notes.result
}
});
// Update note fields (CSS-aware, preserves formatting)
await use_mcp_tool({
server_name: "anki-mcp",
tool_name: "updateNoteFields",
arguments: {
note: {
id: notes.result[0],
fields: {
Front: "¿Cómo estás?",
Back: "How are you? (informal)"
}
}
}
});
// Delete notes and their cards
await use_mcp_tool({
server_name: "anki-mcp",
tool_name: "deleteNotes",
arguments: {
notes: [1234567890, 9876543210]
}
});// Get all existing tags (use first to avoid duplication)
const allTags = await use_mcp_tool({
server_name: "anki-mcp",
tool_name: "getTags",
arguments: {}
});
// Add tags to notes (space-separated)
await use_mcp_tool({
server_name: "anki-mcp",
tool_name: "addTags",
arguments: {
notes: [1234567890, 9876543210],
tags: "important grammar advanced"
}
});
// Remove tags from notes
await use_mcp_tool({
server_name: "anki-mcp",
tool_name: "removeTags",
arguments: {
notes: [1234567890],
tags: "beginner"
}
});
// Rename tag across all notes
await use_mcp_tool({
server_name: "anki-mcp",
tool_name: "replaceTags",
arguments: {
notes: [1234567890, 9876543210],
tag_to_replace: "old-tag",
replace_with_tag: "new-tag"
}
});
// Clean up unused tags from collection
await use_mcp_tool({
server_name: "anki-mcp",
tool_name: "clearUnusedTags",
arguments: {}
});// List media files with pattern filter
const mediaFiles = await use_mcp_tool({
server_name: "anki-mcp",
tool_name: "getMediaFilesNames",
arguments: {
pattern: "*.png"
}
});
// Download media as base64
const mediaData = await use_mcp_tool({
server_name: "anki-mcp",
tool_name: "retrieveMediaFile",
arguments: {
filename: "diagram.png"
}
});
// Upload media (three methods)
// Method 1: File path (FASTEST - recommended)
await use_mcp_tool({
server_name: "anki-mcp",
tool_name: "storeMediaFile",
arguments: {
filename: "photo.jpg",
data: "/Users/username/Pictures/photo.jpg"
}
});
// Method 2: URL (FAST - auto-downloads)
await use_mcp_tool({
server_name: "anki-mcp",
tool_name: "storeMediaFile",
arguments: {
filename: "diagram.png",
url: "https://example.com/diagram.png"
}
});
// Method 3: Base64 (SLOW - avoid if possible)
await use_mcp_tool({
server_name: "anki-mcp",
tool_name: "storeMediaFile",
arguments: {
filename: "audio.mp3",
data: "base64encodedstring..."
}
});
// Delete media file
await use_mcp_tool({
server_name: "anki-mcp",
tool_name: "deleteMediaFile",
arguments: {
filename: "old-diagram.png"
}
});// Always list decks first to avoid duplicates
const decks = await use_mcp_tool({
server_name: "anki-mcp",
tool_name: "listDecks",
arguments: {}
});
if (!decks.result.includes("MyDeck")) {
await use_mcp_tool({
server_name: "anki-mcp",
tool_name: "createDeck",
arguments: { deck: "MyDeck" }
});
}// Fetch existing tags to maintain consistency
const tags = await use_mcp_tool({
server_name: "anki-mcp",
tool_name: "getTags",
arguments: {}
});
// Use existing tags when adding notes
const newTags = tags.result.filter(t => t.includes("spanish"));// Instead of adding notes one by one, batch them
const notes = [];
for (let i = 0; i < 50; i++) {
notes.push({
deckName: "Vocabulary",
modelName: "Basic",
fields: { Front: `Word ${i}`, Back: `Definition ${i}` },
tags: ["batch-import"]
});
}
// Single batch operation
await use_mcp_tool({
server_name: "anki-mcp",
tool_name: "addNotes",
arguments: { notes }
});// Always get note info before updating to preserve content
const info = await use_mcp_tool({
server_name: "anki-mcp",
tool_name: "notesInfo",
arguments: { notes: [noteId] }
});
const currentFields = info.result[0].fields;
// Update only specific fields, preserve others
await use_mcp_tool({
server_name: "anki-mcp",
tool_name: "updateNoteFields",
arguments: {
note: {
id: noteId,
fields: {
Front: currentFields.Front.value, // Keep existing
Back: "Updated back content" // Change this
}
}
}
});findNotes// Examples of valid queries
"deck:Spanish" // All notes in Spanish deck
"tag:verb" // Notes tagged with 'verb'
"deck:Spanish tag:verb" // Spanish deck AND verb tag
"is:due" // Due for review
"added:7" // Added in last 7 days
"Front:*hola*" // Front field contains 'hola'
"deck:Spanish -tag:mastered" // Spanish deck WITHOUT mastered tag# Verify AnkiConnect is installed
# In Anki: Tools → Add-ons → Check for "AnkiConnect"
# Check if Anki is running
curl http://localhost:8765
# Try custom port
export ANKI_CONNECT_URL=http://localhost:8766
ankimcp --stdiowebCorsOriginList// Prefer file paths over base64
// ✅ GOOD
storeMediaFile({ filename: "img.png", data: "/path/to/img.png" })
// ✅ GOOD
storeMediaFile({ filename: "img.png", url: "https://example.com/img.png" })
// ❌ SLOW (avoid unless necessary)
storeMediaFile({ filename: "img.png", data: "base64..." })# Verify environment variable
echo $READ_ONLY
# Or use CLI flag explicitly
ankimcp --stdio --read-only// addNotes supports partial success
const result = await use_mcp_tool({
server_name: "anki-mcp",
tool_name: "addNotes",
arguments: { notes: [...] }
});
// Check which notes succeeded and which failed
result.success.forEach((id, idx) => {
if (id) console.log(`Note ${idx}: Success - ID ${id}`);
});
result.errors.forEach((err, idx) => {
if (err) console.log(`Note ${idx}: Failed - ${err.error}`);
});// Only 2 levels supported: Parent::Child
// ✅ Valid
createDeck({ deck: "Languages::Spanish" })
// ❌ Invalid (too many levels)
createDeck({ deck: "Languages::Spanish::Verbs" })syncaddNotesaddNotegetTagsnotesInfoupdateNoteFields--read-onlyerrorsaddNoteslistDecks