AnkiConnect Server
Headless AnkiConnect-compatible REST API server with AnkiWeb sync support and MCP server integration.
❓ Problem & Solution
Problem: Traditional AnkiConnect requires the Anki desktop app running, works only on localhost, and can't be deployed to servers.
Solution: This headless server provides direct collection access without Anki desktop, supports remote deployment, and includes automatic AnkiWeb sync.
✨ Features
- Server Deployment - Run on VPS, Raspberry Pi, or cloud without GUI
- AI Integration - MCP server for AI assistant access to your cards
- Full AnkiConnect API Compatibility - Version 6 API with all standard actions
- Headless Operation - No Qt/GUI required, perfect for servers and containers
- AnkiWeb Sync - Automatic synchronization with AnkiWeb (optional)
- MCP Server - Model Context Protocol integration for AI assistants
📋 Table of Contents
⚙️ Configuration
Set environment variables before running the server:
| Variable | Required | Default | Description |
|---|---|---|---|
ANKICONNECT_COLLECTION_PATH |
No | temp path | Path to your .anki2 collection file |
ANKICONNECT_PORT |
No | 8765 |
Server port |
ANKICONNECT_BIND |
No | 127.0.0.1 |
Bind address (use 0.0.0.0 for external access) |
ANKICONNECT_ANKIWEB_USER |
No | - | AnkiWeb username (required for sync) |
ANKICONNECT_ANKIWEB_PASS |
No | - | AnkiWeb password (required for sync) |
ANKICONNECT_ANKIWEB_URL |
No | - | Custom sync server URL (optional) |
Example .env File
# Required: Path to your Anki collection
ANKICONNECT_COLLECTION_PATH=/path/to/collection.anki2
# Optional: Server configuration
ANKICONNECT_PORT=8765
ANKICONNECT_BIND=127.0.0.1
# Optional: AnkiWeb sync credentials
ANKICONNECT_ANKIWEB_USER=your@email.com
ANKICONNECT_ANKIWEB_PASS=your_password
# Optional: Custom sync server
ANKICONNECT_ANKIWEB_URL=https://your-sync-server.com
🚀 Usage
Quick Start with uvx (No Installation)
# Run the API server
ANKICONNECT_COLLECTION_PATH=/path/to/collection.anki2 \
ANKICONNECT_ANKIWEB_USER=your@email.com \
ANKICONNECT_ANKIWEB_PASS=your_password \
uvx anki-connect-server api
# Run the MCP server
ANKICONNECT_COLLECTION_PATH=/path/to/collection.anki2 \
ANKICONNECT_ANKIWEB_USER=your@email.com \
ANKICONNECT_ANKIWEB_PASS=your_password \
uvx anki-connect-server mcp
📚 API Reference
The server exposes a single POST endpoint at /api that accepts AnkiConnect-style JSON requests.
Request Format
{
"action": "actionName",
"version": 6,
"params": {
"param1": "value1",
"param2": "value2"
}
}
Response Format
{
"result": <action_result>,
"error": null
}
On error:
{
"result": null,
"error": "Error message"
}
Examples
Get Deck Names
curl -X POST http://localhost:8765/api \
-H "Content-Type: application/json" \
-d '{"action": "deckNames", "version": 6}'
Create a Deck
curl -X POST http://localhost:8765/api \
-H "Content-Type: application/json" \
-d '{"action": "createDeck", "version": 6, "params": {"deck": "My New Deck"}}'
Add a Note
curl -X POST http://localhost:8765/api \
-H "Content-Type: application/json" \
-d '{
"action": "addNote",
"version": 6,
"params": {
"note": {
"deckName": "Default",
"modelName": "Basic",
"fields": {
"Front": "Hello",
"Back": "World"
},
"tags": ["api"]
}
}
}'
Store Media from a URL (no base64 needed)
storeMediaFile accepts base64 data, a url to download, or a server-side path.
When several are given, data takes precedence over url, which takes precedence over path.
# Download audio from a dictionary on the server side (~100-byte request)
curl -X POST http://localhost:8765/api \
-H "Content-Type: application/json" \
-d '{
"action": "storeMediaFile",
"version": 6,
"params": {
"filename": "sky_us.ogg",
"url": "https://commons.wikimedia.org/wiki/Special:FilePath/En-us-sky.ogg"
}
}'
# Copy a file already visible to the server (e.g. a mounted volume)
curl -X POST http://localhost:8765/api \
-H "Content-Type: application/json" \
-d '{
"action": "storeMediaFile",
"version": 6,
"params": {"filename": "sky.mp3", "path": "/data/media-in/sky.mp3"}
}'
Then reference the stored file in a note field with [sound:sky_us.ogg].
Sync with AnkiWeb
curl -X POST http://localhost:8765/api \
-H "Content-Type: application/json" \
-d '{"action": "sync", "version": 6}'
Batch Multiple Actions
curl -X POST http://localhost:8765/api \
-H "Content-Type: application/json" \
-d '{
"action": "multi",
"version": 6,
"params": {
"actions": [
{"action": "deckNames", "params": {}},
{"action": "modelNames", "params": {}}
]
}
}'
Supported Actions
Misc
version- Get API versionsync- Sync with AnkiWebsyncStatus- Get sync status (returns the sync requirement;server/statusare included only when the sync backend provides them)syncMedia- Sync media only (bidirectional; runs independently of collection sync)apiReflect- Introspect the supported actions (scopes: ["actions"])reloadCollection- No-op on a headless server (anki's reset is deprecated and does nothing)requestPermission- Always granted (headless; no GUI prompt or CORS allowlist)multi- Execute multiple actions (each entry replies per its ownversion, like upstream: failures always{"result": null, "error": "..."}; successes are the bare value for version ≤ 4, or{"result": ..., "error": null}for version ≥ 5)importPackage- Import.apkgfileexportPackage- Export deck to.apkg
Decks
deckNames- Get all deck namesdeckNamesAndIds- Get deck names with IDsdeckNameFromId- Get a deck name from its IDcreateDeck- Create a new deckdeleteDecks- Delete deckschangeDeck- Move cards to different deckgetDecks- Get decks for cardsgetDeckConfig- Get deck configurationsaveDeckConfig- Save deck configurationsetDeckConfigId- Assign config to deckscloneDeckConfigId- Clone deck configremoveDeckConfigId- Remove deck config
Models
modelNames- Get all model namesmodelNamesAndIds- Get model names with IDsmodelNameFromId- Get a model name from its IDmodelFieldNames- Get field names for modelmodelFieldsOnTemplates- Get fields used in templatescreateModel- Create a new note model (returns the created model, like upstream)modelTemplates- Get card templatesmodelStyling- Get CSS stylingupdateModelTemplates- Update templatesupdateModelStyling- Update CSSmodelFieldAdd- Add a field to a model (optionally positioned at an index)modelFieldRemove- Remove a field from a model (destroys that field's data in all notes)modelFieldRename- Rename a model field (anki 26.8 also rewrites the renamed field's references in templates on save)modelFieldReposition- Move a field to a new positionmodelFieldSetFont- Set a field's fontmodelFieldSetFontSize- Set a field's font sizemodelFieldSetDescription- Set a field's descriptionmodelFieldDescriptions- Get field descriptionsmodelFieldFonts- Get per-field font and sizemodelTemplateAdd- Add or update a card templatemodelTemplateRemove- Remove a card template (a model must keep at least one)modelTemplateRename- Rename a card templatemodelTemplateReposition- Move a card template to a new positionfindAndReplaceInModels- Find and replace text in templates/CSS across models (empty modelName = all models; replacements that would leave a dangling field reference are refused with a validation error — unlike upstream, broken templates are never persisted)
Notes
addNote- Add a single noteaddNotes- Add multiple notescanAddNote- Check if a single note can be addedcanAddNotes- Check if notes can be addedcanAddNotesWithErrorDetail- Check whether notes can be added, with per-note error detailsupdateNoteFields- Update note fieldsupdateNote- Update note fields and/or tags in one callfindNotes- Search for notesnotesInfo- Get note detailsdeleteNotes- Delete notesremoveEmptyNotes- Remove unused note types (upstream parity: despite the name, removes note types with zero notes)addTags- Add tags to notesremoveTags- Remove tags from notesgetTags- Get all tagsreplaceTags- Replace a tag with another across the given notesreplaceTagsInAllNotes- Replace a tag with another across all notesclearUnusedTags- Remove tags not used by any notegetNoteTags- Get a single note's tagsupdateNoteTags- Replace all tags of a note (accepts a tag string or list; a string becomes one tag, but anki re-splits tags on whitespace when the note loads — prefer the list form)
Cards
findCards- Search for cardscardsToNotes- Get note IDs from card IDscardsInfo- Get card details (upstream's full shape:factoris the ease factor x10;easeis kept as a legacy alias)cardsModTime- Get card modification timestampssuspend- Suspend cardsunsuspend- Unsuspend cardsareSuspended- Check if cards are suspendedsuspended- Check if a single card is suspendedareDue- Check if cards are duegetIntervals- Get card intervalsanswerCards- Answer cards with a given ease (1=Again, 2=Hard, 3=Good, 4=Easy); returns a per-card success list; an invalid ease rejects the whole batch at validation time (upstream applies earlier answers first)forgetCards- Reset cards back to new, clearing all scheduling staterelearnCards- Move cards into the relearning queuesetEaseFactors- Set card ease factors (10x the ease percentage, e.g. 3100 = 310%)getEaseFactors- Get card ease factors (10x the ease percentage)setSpecificValueOfCard- Raw/dangerous: set arbitrary card fields directly (critical fields are refused unlesswarning_checkis true)
Stats
cardReviews- Get review log entries for a deck since a given review ID (unknown deck names are created, as upstream does)getReviewsOfCards- Get full review history per cardgetLatestReviewID- Get the newest review ID for a deck (deck required; unknown names are created, as upstream does)getNumCardsReviewedToday- Count reviews made todaygetNumCardsReviewedByDay- Count reviews grouped by dayinsertReviews- Raw review-log insertion (developer utility, rows of 9 integers)getDeckStats- Deck due counts by deck name (keys are deck IDs; due counts aggregate child decks,total_in_deckcounts direct cards per upstream,total_including_childrenis the aggregated total;nameis the deck's own name per anki's due tree)getCollectionStatsHTML- Full stats report as HTML
Media
getMediaDirPath- Get media directory pathgetMediaFilesNames- List media file names matching a glob pattern (patterns are confined to the media directory; absolute paths and..are rejected)storeMediaFile- Store a media file (base64data,url, or server-sidepath)retrieveMediaFile- Retrieve a media filedeleteMediaFile- Delete a media file
Not supported (headless server)
These upstream AnkiConnect actions require the Anki desktop GUI or profile manager and are intentionally not implemented:
getProfiles,loadProfile- multi-profile support is an Anki desktop concept; this server serves a single collection viaANKICONNECT_COLLECTION_PATHguiAddCards,guiAnswerCard,guiBrowse,guiCheckDatabase,guiCurrentCard,guiDeckBrowser,guiDeckOverview,guiDeckReview,guiEditNote,guiExitAnki,guiImportFile,guiReviewActive,guiSelectedNotes,guiShowAnswer,guiShowQuestion,guiStartCardTimer,guiUndo- allgui*actions open or drive Anki desktop windows
🤖 MCP Server
The server includes a Model Context Protocol (MCP) integration for AI assistants.
Starting the MCP Server
ANKICONNECT_COLLECTION_PATH=/path/to/collection.anki2 \
ANKICONNECT_ANKIWEB_USER=your@email.com \
ANKICONNECT_ANKIWEB_PASS=your_password \
uvx anki-connect-server mcp
Transports: stdio vs http
The MCP server supports two transports via --transport:
| Transport | Flag | Behavior |
|---|---|---|
stdio (default) |
--transport stdio |
One process per client connection. The MCP client (e.g. Claude Desktop, Hermes) spawns a new process for each session. |
http |
--transport http |
A single long-lived server holds the collection and serves all clients over HTTP. |
# stdio (default)
uvx anki-connect-server mcp
# http
uvx anki-connect-server mcp --transport http
⚠️ Concurrent MCP processes and the "already open" error
The Anki collection is a single-writer SQLite database: only one process can hold the lock at a time. The default stdio transport spawns a new process per client connection, so if your MCP client (Hermes, Claude Desktop, etc.) starts a new process before the previous one has exited, you will see:
anki.errors.DBError: Anki already open, or media currently syncing.
This is a SQLite lock contention error, not a media-sync issue — despite what the message says.
Workarounds:
-
Use HTTP transport (recommended for multi-client setups):
uvx anki-connect-server mcp --transport http # Server runs on http://127.0.0.1:8765/mcp
Configure your MCP client to connect to the HTTP endpoint instead of spawning a stdio process. Only one process holds the collection and serves all connections.
-
Leave
ANKICONNECT_COLLECTION_PATHunset. When not configured, each process gets a random temp collection (anki-connect-server-<random>.anki2), so concurrent processes don't contend for the same file. Note: each process will have its own empty collection — set the path explicitly if you need a shared collection. -
Ensure only one process is running. If you stick with stdio, make sure your MCP client kills the previous process before starting a new one.
Available MCP Tools
get_deck_names- Get all deck namesget_deck_names_and_ids- Get decks with IDscreate_deck- Create a new deckdelete_decks- Delete decksget_model_names- Get all model namesget_model_field_names- Get fields for a modelcreate_model- Create a note model (fields + card templates)model_add_field/model_remove_field/model_rename_field/model_reposition_field- Manage model fieldsupdate_note_fields- Update fields of an existing noteadd_note- Add a new notefind_notes- Search for notesget_notes_info- Get note detailsdelete_notes- Delete notesfind_cards- Search for cardsget_cards_info- Get card detailssuspend_cards/unsuspend_cards- Manage card suspensionare_suspended/are_due- Check card statusget_card_intervals- Get card intervalsget_all_tags- Get all tagsadd_tags/remove_tags- Manage tagsget_media_dir_path- Get media directorystore_media_file/retrieve_media_file/delete_media_file- Media operations (store_media_fileaccepts base64data,url, or server-sidepath)update_note/update_note_fields- Edit note fields and/or tagschange_deck- Move cards between deckscards_to_notes- Convert card IDs to note IDsget_deck_config- Get deck configurationget_model_templates/get_model_styling- Model customizationupdate_model_templates/update_model_styling- Edit model templates/CSSget_api_version- Get API versionimport_package/export_package- Import/export deckssync/sync_media/get_sync_status- Sync operations
Adding to Claude Desktop
Option A — stdio (default, one process per session):
Add to your claude_desktop_config.json:
{
"mcpServers": {
"anki-connect-server": {
"command": "uvx",
"args": ["anki-connect-server", "mcp"],
"env": {
"ANKICONNECT_COLLECTION_PATH": "/path/to/collection.anki2",
"ANKICONNECT_ANKIWEB_USER": "your@email.com",
"ANKICONNECT_ANKIWEB_PASS": "your_password"
}
}
}
}
Option B — http (single long-lived server, avoids lock contention):
First start the HTTP server (e.g. as a systemd service or background process):
uvx anki-connect-server mcp --transport http
Then point your MCP client at http://127.0.0.1:8765/mcp.
🐳 Docker
Using Pre-built Image
docker run -d \
-p 8765:8765 \
-v /path/to/collection.anki2:/data/collection.anki2 \
-e ANKICONNECT_COLLECTION_PATH=/data/collection.anki2 \
-e ANKICONNECT_ANKIWEB_USER=your@email.com \
-e ANKICONNECT_ANKIWEB_PASS=your_password \
--name anki-connect-server \
glechic/anki-connect-server:latest
Docker Compose
version: '3.8'
services:
anki-connect-server:
image: glechic/anki-connect-server:latest
ports:
- "8765:8765"
volumes:
- ./collection.anki2:/data/collection.anki2
environment:
- ANKICONNECT_COLLECTION_PATH=/data/collection.anki2
- ANKICONNECT_ANKIWEB_USER=${ANKIWEB_USER}
- ANKICONNECT_ANKIWEB_PASS=${ANKIWEB_PASS}
restart: unless-stopped
Building from Source
docker build -t anki-connect-server .
Metadata
Release files for anki-connect-server 0.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| anki_connect_server-0.5.0.tar.gz | 75.5 kB | Without provenance | Details for anki_connect_server-0.5.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| anki_connect_server-0.5.0-py3-none-any.whl | Python 3 | none | any | Details for anki_connect_server-0.5.0-py3-none-any.whl |
Total release size: 118.3 kB
Release files / anki_connect_server-0.5.0.tar.gz
| Download URL | anki_connect_server-0.5.0.tar.gz |
|---|---|
| Size | 75.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
38ebda9ae1270425af570a07997071799df7693986a4d6b234bb7b9e9a761149
|
|
BLAKE2b-256 checksum How to use checksums |
1438f10c68ce8ad90db3cda2b332d026db45d21ae8b81b918225a70d9c2bb48a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.7.3
|
Release files / anki_connect_server-0.5.0-py3-none-any.whl
| Download URL | anki_connect_server-0.5.0-py3-none-any.whl |
|---|---|
| Size | 42.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7d4f29228bbc0ed8cce1e079d1b3c14a96ab255184d1c91d0e4947f53da0c629
|
|
BLAKE2b-256 checksum How to use checksums |
3a49f8b78007969ed102746e617c1ba9364ed89c0ed27a86c0d37f70eee183a4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.7.3
|
Security for anki-connect-server 0.5.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.