Releases: ankimcp/anki-mcp-server-addon
Release list
v0.26.0
What's Changed
- feat: event-driven request dispatch replaces 25ms polling QTimer by @anatoly314 in #65
Full Changelog: v0.25.2...v0.26.0
v0.25.2
What's Changed
- feat: hosted_mode for unattended (self-hosted) tunnel connections by @anatoly314 in #64
Full Changelog: v0.25.1...v0.25.2
v0.25.1
What's New
Deck-scoped tag listing with get_tags
The tag_management tool's get_tags action gains an optional deck parameter. Pass a deck name to get just the distinct tags on notes with at least one card in that deck (subdecks included), instead of every tag in the collection. This lets an AI client discover which tags actually apply to a given deck in one lean call — the natural discovery step before suggesting tags — rather than paging through cards_stats and de-duping by hand. Omit the parameter for the original all-collection behavior.
Tags are computed collection-side using the same deck: scoping cards_stats uses, so the two agree: subdecks are included, prefix-similar sibling decks (e.g. Algebra vs Algebra - extra) stay separate, and results come back sorted. An unknown or empty deck returns an empty list rather than erroring. (#63)
Install
- AnkiWeb: Tools → Add-ons → Get Add-ons… → code 124672614
- From this release: download the
.ankiaddonbelow and double-click (or Tools → Add-ons → Install from file).
Full Changelog: v0.25.0...v0.25.1
v0.25.0
What's New
Bulk card scheduling stats with cards_stats
New read-only cards_stats tool returns compact per-card scheduling metrics — type, queue, interval (days), the note's tags, and a computed dueToday flag — for a whole deck (subdecks included), paginated. It's FSRS-independent (works whether or not FSRS is enabled), so a single lean call can replace a find_notes + notes_info + get_card_memory_state sequence when you only need scheduling data. No note fields, no HTML — just the scheduling metrics, for bulk analytics. (#62)
Asynchronous sync
The sync tool moves to a non-blocking job model: sync() starts a sync and returns a job id, sync(job_id) polls its status, and sync(job_id, resolve=...) resolves a full-sync conflict — so a long sync no longer blocks the MCP call. (#62)
Install
- AnkiWeb: Tools → Add-ons → Get Add-ons… → code 124672614
- From this release: download the
.ankiaddonbelow and double-click (or Tools → Add-ons → Install from file).
Full Changelog: v0.24.0...v0.25.0
v0.24.0
What's New
Preview bulk edits with dry_run on update_notes
update_notes gains a dry_run=true option that runs the full per-entry validation pass (field names, note existence, ID validity) and reports which notes would be updated — without writing anything. Returns would_update/would_fail counts plus per-note status: "would_update" entries, mirroring the dry run delete_notes already had. Handy for safely previewing a large bulk edit before committing it.
delete_notes respects max_notes_per_batch
delete_notes no longer caps at a hardcoded 100 notes — it now honors the max_notes_per_batch config option like update_notes, add_notes, and notes_info. Raising that limit in your add-on config now applies to deletes too, and the error hint points at the config option.
(both above: #59 — thanks @klfk for your first contribution!)
Fix: startup crash when another add-on bundles an old packaging
On Anki 26.05+ (Python 3.13), if another add-on (for example AMBOSS) had already loaded an older packaging (≤ 20.9) into Anki's shared Python process, AnkiMCP's first-run pydantic_core download failed with No module named 'distutils' — even though AnkiMCP's own bundled packaging was perfectly fine and just never got used.
AnkiMCP now forces its own vendored packaging when selecting the download wheel, regardless of what other add-ons have loaded, and restores the other add-on's copy afterward (so nothing else breaks). Already-affected installs self-heal on the next launch. (#61)
Docs: connecting via Opencode
Added setup instructions for the Opencode terminal AI agent. (#60 — thanks @Marley-Mulvin-Broome!)
Install
- AnkiWeb: Tools → Add-ons → Get Add-ons… → code 124672614
- From this release: download the
.ankiaddonbelow and double-click (or Tools → Add-ons → Install from file).
New Contributors
- @klfk made their first contribution in #59
- @Marley-Mulvin-Broome made their first contribution in #60
Full Changelog: v0.23.0...v0.24.0
v0.23.0
What's New
Manage note-type fields — new model_fields tool
AI clients can now reshape an existing note type's fields without opening Anki's Fields dialog by hand. The tool exposes three actions:
add— add a new field, optionally at a specific 0-based position (defaults to appended at the end).rename— rename a field, preserving its content.reposition— move a field to a new position.
A fourth action, remove, permanently deletes a field and all of its content. Because that's irreversible bulk data loss, it's treated as destructive: hidden from MCP clients by default and revealed only when the operator opts in via enabled_destructive_tools (e.g. "model_fields:remove").
Operator caveats baked into the tool:
- Every action changes the note-type schema, so each result warns that the next sync will be a one-way full sync — overwriting your collection on AnkiWeb and other devices. Sync your other devices first if they hold unsynced changes.
- Rename does not rewrite card templates: any
{{OldFieldName}}reference renders blank until you fix it withupdate_model_templates. The tool flags this in its response.
Closes #57.
Install
- AnkiWeb: Tools → Add-ons → Get Add-ons… → code 124672614
- From this release: download the
.ankiaddonbelow and double-click (or Tools → Add-ons → Install from file).
Full Changelog: v0.22.0...v0.23.0
v0.22.0
What's New in v0.22.0
🩺 Diagnostic file logging
New opt-in log_to_file setting writes a rotating log to user_files/ankimcp.log (~1 MB × 3 backups) with secrets — the http_api_key, OAuth tokens, and any Bearer token — redacted before anything reaches disk. The settings dialog gains Open log folder and Copy diagnostics buttons. When the add-on can't start (for example a blocked pydantic_core download), you can now capture and share the real cause instead of a cryptic error. Off by default.
🔁 More resilient first-run dependency download
The runtime pydantic_core download is hardened: it retries transient native-library file locks, uses an atomic cache swap so a flaky re-download never destroys a working install, and reports the real failure reason (network, DNS, SSL, …) instead of a generic "not available".
🔎 Clearer dependency-conflict diagnostics
When another add-on has already loaded a shared library, the startup warning now reports the actual version and file path of the conflicting copy — making cross-add-on issues far easier to pin down.
Under the hood
- Packaging: exclude the runtime
_cache/directory from the.ankiaddon, so a local build can never ship a stray platform-specific native binary. - CI: per-tag concurrency guard on the release workflow.
Validated on macOS (arm64), Windows 11 (arm64), Ubuntu/snap (aarch64), and NixOS.
Full Changelog: v0.21.2...v0.22.0
v0.21.2
What's Changed
- fix: ensure rpds at runtime (cross-platform), + cross-platform & Anki-compat CI (#54) by @anatoly314 in #55
Full Changelog: v0.21.1...v0.21.2
v0.21.1
v0.21.1 — Apple Silicon startup crash fix
Fixed
- Wrong
pydantic_corewheel on Apple Silicon (#52). On Macs where Anki runs a universal2 framework Python, the add-on downloaded the x86_64pydantic_corewheel instead of the arm64 one, so the MCP server crashed on startup with:Root cause: architecture was inferred fromImportError: dlopen(..._pydantic_core...darwin.so): mach-o file, but is an incompatible architecture (have 'x86_64', need 'arm64')sysconfig.get_platform(), which a universal2 (fat) build reports asmacosx-…-universal2— a string containing neitherarm64noraarch64, so Apple Silicon was misread as x86_64. Detection now also consultsplatform.machine(), which reflects the architecture the process is actually running as (and stays correct under Rosetta).
Who was affected
- Apple Silicon Macs whose Anki uses a universal2 framework Python. Macs that run a native-arm64 Python (e.g. Anki's newer
uv-based launcher), Intel Macs, Linux, and Windows were never affected. Source / Nix installs never hit this code path.
Upgrading
- Already-broken installs self-heal on the next launch — the stale x86_64 binary fails to load, which transparently triggers a re-download of the correct arm64 wheel. No manual cache wipe needed.
Notes
- Bugfix-only patch release; no API or config changes.
- Added unit regression coverage for wheel selection across universal2 / native-arm64 / Intel / Rosetta / Linux.
Thanks to @ksuenaga — and his AI agent, which filed a remarkably precise report that pinpointed both the root cause and the fix. Filed by one AI agent, fixed by another. 🙂
Full Changelog: v0.21.0...v0.21.1
v0.21.0
v0.21.0 — Security: DNS-rebinding protection + optional API key
Security
- DNS-rebinding protection is now enabled by default on the local HTTP transport (GHSA-j9xx-59ph-wmr6). The server validates the
HostandOriginof every request against a loopback allowlist (127.0.0.1,localhost,[::1]). A forged Host is rejected with421, a forged Origin with403. This closes a vector where a malicious web page could DNS-rebind to your local server and invoke tools. The tunnel (WebSocket) transport is unaffected.
Added
http_allowed_hosts/http_allowed_origins— extend the allowlist to expose the HTTP server through a tunnel or reverse proxy, instead of disabling protection. Host entries are scheme-less (myapp.ngrok.io); origin entries include the scheme (https://myapp.example).http_api_key— optional shared-secret auth (AnkiConnect-style). When set, every HTTP request must sendAuthorization: Bearer <key>(constant-time compared); failures return403. Empty by default (disabled).
Backward compatibility
- Local-only use is unaffected. If you reached the HTTP server under a non-loopback name (bound to
0.0.0.0, or behind ngrok / Cloudflare / a reverse proxy), those requests now return421 Invalid Host headeruntil you add the host tohttp_allowed_hosts(and the origin tohttp_allowed_originsfor browser clients), or runngrok http --host-header=rewrite 3141.
Thanks to @avishaigo-commits for the responsible disclosure.