{
  "markdown": "# AnkiMCP Server (Addon)\n\n<div align=\"center\">\n  <img src=\"./docs/images/ankimcp.png\" alt=\"Anki + MCP Integration\" width=\"600\" />\n\n  <p><strong>Seamlessly integrate <a href=\"https://apps.ankiweb.net\">Anki</a> with AI assistants through the <a href=\"https://modelcontextprotocol.io\">Model Context Protocol</a></strong></p>\n</div>\n\nAn Anki addon that exposes your collection to AI assistants via the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/).\n\n## What is this?\n\nAnkiMCP Server runs a local MCP server inside Anki, allowing AI assistants like Claude to interact with your flashcard collection. This enables AI-powered study sessions, card creation, and collection management.\n\nPart of the [ankimcp.ai](https://ankimcp.ai) project.\n\n## Note on First Run\n\nOn first run, this addon downloads `pydantic_core` (~2MB) from PyPI. This is required because pydantic_core contains platform-specific binaries (Windows/macOS/Linux) that cannot be bundled in a single addon file.\n\nA second native dependency, `rpds` (from `rpds-py`), is handled the same way — but it is almost never downloaded: Anki already ships `rpds` as a transitive dependency of its own `jsonschema`, so the addon just imports it. The download only kicks in on the rare install where that import fails. Both downloads are cached under the addon's `_cache/` directory, so they happen once, not on every launch.\n\n## Features\n\n- **Local HTTP server** - Runs on `http://127.0.0.1:3141/` by default\n- **Remote tunnel** - Access your collection from anywhere via a public HTTPS URL\n- **MCP protocol** - Compatible with any MCP client (Claude Desktop, etc.)\n- **Auto-start** - HTTP server starts automatically when Anki opens\n- **Tunnel-friendly** - Works with Cloudflare Tunnel, ngrok, or the built-in tunnel (exposing the HTTP server this way also requires extending the [allowed hosts/origins](#allowed-hosts-and-origins-dns-rebinding-protection))\n- **DNS-rebinding protection** - The HTTP server validates `Host`/`Origin` headers against a loopback allowlist by default; [extend it](#allowed-hosts-and-origins-dns-rebinding-protection) for tunnel/reverse-proxy exposure\n- **Optional API key** - Require an `Authorization: Bearer` token on the HTTP transport via [`http_api_key`](#api-key-optional-http-auth) (AnkiConnect-style; empty = disabled)\n- **Toolbar indicator** - A `● AnkiMCP` item in the top toolbar shows tunnel connection state at a glance (opt out via `show_toolbar_indicator`)\n- **Diagnostic logging** - Opt-in [`log_to_file`](#diagnostic-file-logging) writes a rotating, secret-redacted log to `user_files/ankimcp.log`, with **Open log folder** / **Copy diagnostics** buttons in settings\n- **Field management** - Add, rename, and reposition note-type fields via the `model_fields` tool (with an opt-in [destructive](#destructive-tools-opt-in) remove)\n- **Bulk card stats** - The read-only `cards_stats` tool returns compact per-card scheduling metrics (type/queue/interval/tags/`dueToday`) for a whole deck including subdecks, FSRS-independent — a lean bulk read for analytics\n- **Cross-platform** - Works on macOS, Windows, and Linux (x64 and ARM)\n\n## Installation\n\n### From AnkiWeb (recommended)\n\n1. Open Anki and go to *Tools → Add-ons → Get Add-ons...*\n2. Enter code: `124672614`\n3. Restart Anki\n\n### From GitHub Releases\n\n1. Download `anki_mcp_server.ankiaddon` from [Releases](https://github.com/ankimcp/anki-mcp-server-addon/releases)\n2. Double-click to install, or use *Tools → Add-ons → Install from file...*\n3. Restart Anki\n\n### NixOS\n\n#### With flakes (recommended)\n\nAdd the flake input and use the pre-built package:\n\n```nix\n# flake.nix\n{\n  inputs.anki-mcp.url = \"github:ankimcp/anki-mcp-server-addon\";\n\n  outputs = { nixpkgs, anki-mcp, ... }: {\n    # Option A: Standalone — Anki with the addon pre-installed\n    environment.systemPackages = [\n      anki-mcp.packages.${system}.default\n    ];\n\n    # Option B: Composable with other addons via overlay\n    nixpkgs.overlays = [ anki-mcp.overlays.default ];\n    environment.systemPackages = [\n      (pkgs.anki.withAddons [ pkgs.ankiAddons.anki-mcp-server ])\n    ];\n  };\n}\n```\n\n#### Without flakes\n\n```nix\n# configuration.nix\n{ pkgs, ... }:\nlet\n  python3 = pkgs.python3;\n\n  ankiMcpPythonDeps = python3.withPackages (ps: with ps; [\n    mcp pydantic pydantic-settings starlette uvicorn anyio httpx websockets\n  ]);\n\n  anki-mcp-server = pkgs.anki-utils.buildAnkiAddon (finalAttrs: {\n    pname = \"anki-mcp-server\";\n    version = \"0.20.0\";\n    src = pkgs.fetchFromGitHub {\n      owner = \"ankimcp\";\n      repo = \"anki-mcp-server-addon\";\n      rev = \"v${finalAttrs.version}\";\n      hash = \"\"; # nix will tell you the correct hash on first build\n    };\n    sourceRoot = \"${finalAttrs.src.name}/anki_mcp_server\";\n  });\n\n  ankiWithMcp = pkgs.anki.withAddons [ anki-mcp-server ];\n\n  ankiWrapped = pkgs.symlinkJoin {\n    name = \"anki-with-mcp\";\n    paths = [ ankiWithMcp ];\n    nativeBuildInputs = [ pkgs.makeWrapper ];\n    postBuild = ''\n      wrapProgram $out/bin/anki \\\n        --prefix PYTHONPATH ':' \"${ankiMcpPythonDeps}/${python3.sitePackages}\"\n    '';\n  };\nin\n{\n  environment.systemPackages = [ ankiWrapped ];\n}\n```\n\n## Usage\n\nThe server starts automatically when you open Anki. Check status via *Tools → AnkiMCP Server Settings...*\n\n### Connect with Claude Desktop\n\nRequires [Node.js](https://nodejs.org/) installed. Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):\n\n```json\n{\n  \"mcpServers\": {\n    \"anki\": {\n      \"command\": \"npx\",\n      \"args\": [\"mcp-remote\", \"http://127.0.0.1:3141\"]\n    }\n  }\n}\n```\n\n> **Note:** Claude Desktop doesn't natively support HTTP servers in its JSON config — `mcp-remote` bridges the connection via stdio.\n\n### Connect with Claude Code\n\n```bash\nclaude mcp add anki --transport http http://127.0.0.1:3141/\n```\n\n### Opencode\n\n```bash\nopencode mcp add anki --url http://127.0.0.1:3141/\n```\n\n### Tunnel (Remote Access)\n\nThe built-in tunnel gives your Anki collection a public HTTPS URL, so AI assistants can reach it from anywhere — no port forwarding or reverse proxy needed. The collection is relayed through a WebSocket tunnel server (`wss://tunnel.ankimcp.ai` by default). Requires an [ankimcp.ai](https://ankimcp.ai) account to log in.\n\n**How to connect:**\n\n1. Open *Tools -> AnkiMCP Server Settings...*\n2. Click **Connect Tunnel**\n3. If not logged in, a login dialog appears — it shows a one-time code; click **Open Browser** and enter that code at the verification URL (OAuth 2.0 device flow)\n4. Once connected, a public tunnel URL is displayed (e.g., `https://tunnel.ankimcp.ai/e3439277-9d1e-47a1-b961-d193a4590da0`)\n5. Use this URL in your AI client instead of `http://127.0.0.1:3141`\n\n**Using with Claude Desktop:**\n\nReplace the localhost URL with your tunnel URL in the Claude Desktop config:\n\n```json\n{\n  \"mcpServers\": {\n    \"anki\": {\n      \"command\": \"npx\",\n      \"args\": [\"mcp-remote\", \"https://tunnel.ankimcp.ai/<your-tunnel-id>\"]\n    }\n  }\n}\n```\n\n**Using with Claude Code:**\n\n```bash\nclaude mcp add anki --transport http https://tunnel.ankimcp.ai/<your-tunnel-id>\n```\n\n**Disconnect vs. Logout:**\n- **Disconnect** closes the tunnel connection. Credentials stay on disk — next Connect reconnects without re-login.\n- **Logout** deletes credentials. Next Connect triggers the login dialog again.\n\n**Tunnel config fields** (for advanced users / self-hosters):\n- `tunnel_server_url` — WebSocket URL of the tunnel relay server (default: `wss://tunnel.ankimcp.ai`)\n- `tunnel_client_id` — OAuth client identifier (default: `ankimcp-cli`)\n\nCredentials are stored in the addon's own `user_files/credentials.json` (preserved across addon updates). They are not shared with the [AnkiMCP CLI](https://github.com/ankimcp/anki-mcp-cli) — the CLI keeps its own credentials under `~/.ankimcp/`, so you log in to the addon and the CLI independently. The on-disk format is identical between the two.\n\n## Configuration\n\nEdit via Anki's *Tools → Add-ons → AnkiMCP Server → Config*:\n\n```json\n{\n  \"http_enabled\": true,\n  \"http_port\": 3141,\n  \"http_host\": \"127.0.0.1\",\n  \"http_path\": \"\",\n  \"http_allowed_hosts\": [],\n  \"http_allowed_origins\": [],\n  \"http_api_key\": \"\",\n  \"cors_origins\": [],\n  \"cors_expose_headers\": [\"mcp-protocol-version\"],\n  \"disabled_tools\": [],\n  \"enabled_destructive_tools\": [],\n  \"max_notes_per_batch\": 100,\n  \"tunnel_server_url\": \"wss://tunnel.ankimcp.ai\",\n  \"tunnel_client_id\": \"ankimcp-cli\",\n  \"media_import_dir\": \"\",\n  \"media_allowed_types\": [],\n  \"media_allowed_hosts\": [],\n  \"show_settings_menu_item\": true,\n  \"show_toolbar_indicator\": true,\n  \"show_sync_tooltip\": true,\n  \"log_to_file\": false\n}\n```\n\n### HTTP Server Toggle\n\nThe `http_enabled` setting controls whether the local HTTP server runs. When set to `false`, the HTTP server won't start — only the tunnel transport is available. Default is `true`.\n\n```json\n{\n  \"http_enabled\": false\n}\n```\n\nThis is useful if you only use the tunnel and don't want a local HTTP server listening.\n\n### Tools Menu Item\n\nThe *AnkiMCP Server Settings…* entry in Anki's *Tools* menu is shown by default. Set `show_settings_menu_item` to `false` to hide it (takes effect after an Anki restart).\n\n```json\n{\n  \"show_settings_menu_item\": false\n}\n```\n\nNote: if you hide the menu item **and** the toolbar indicator, there's no in-app way left to open the settings dialog — you can still edit the config via *Tools → Add-ons → AnkiMCP Server → Config*.\n\n### Toolbar Status Indicator\n\nA persistent `● AnkiMCP` item in Anki's top toolbar shows tunnel connection state (grey = off, amber = connecting, green = connected); clicking it opens the settings dialog. It's shown by default. Set `show_toolbar_indicator` to `false` to hide it (takes effect after an Anki restart).\n\n```json\n{\n  \"show_toolbar_indicator\": false\n}\n```\n\n### Sync Tooltip\n\nWhen an AI client triggers a sync, the addon shows a brief, non-modal tooltip in Anki's UI as the sync starts and finishes (e.g. `AnkiMCP: syncing…`, `AnkiMCP: sync complete`). This is the only visual cue for these otherwise-silent background syncs. It's shown by default. Set `show_sync_tooltip` to `false` to suppress it.\n\n```json\n{\n  \"show_sync_tooltip\": false\n}\n```\n\n### Diagnostic File Logging\n\nSet `log_to_file` to `true` to write a rotating log to `user_files/ankimcp.log` (~1 MB per file, 3 backups). It's **off by default**. When enabled, the addon records a startup diagnostics snapshot (addon/Anki/Qt/Python versions plus the live provenance of shared libraries like `pydantic`, `mcp`, `protobuf`, etc.), which is the key data for diagnosing cross-add-on conflicts. Secrets — the `http_api_key`, OAuth tokens, and any `Bearer` token — are **redacted** before anything is written to disk.\n\n```json\n{\n  \"log_to_file\": true\n}\n```\n\nThe settings dialog (*Tools → AnkiMCP Server Settings…*) has an **Open log folder** button and a **Copy diagnostics** button (the same snapshot, formatted for pasting into a forum post). Takes effect after an Anki restart.\n\n### Disabling Tools\n\nHide specific tools or actions from AI clients to reduce token usage:\n\n```json\n{\n  \"disabled_tools\": [\n    \"sync\",\n    \"card_management:bury\",\n    \"card_management:unbury\"\n  ]\n}\n```\n\n- `\"tool_name\"` — disables the entire tool\n- `\"tool_name:action\"` — disables a specific action within a multi-action tool\n\nDisabled tools are removed from the MCP schema entirely — AI clients never see them. Typos in tool/action names will produce console warnings.\n\n### Destructive Tools (Opt-In)\n\nTools or actions classified as destructive (high-risk operations) are **hidden from AI clients by default**. To expose them, add them to the `enabled_destructive_tools` allow-list:\n\n```json\n{\n  \"enabled_destructive_tools\": [\n    \"some_destructive_tool\",\n    \"some_tool:destructive_action\"\n  ]\n}\n```\n\n- `\"tool_name\"` — opts in an entire destructive tool\n- `\"tool_name:action\"` — opts in a destructive action within a multi-action tool (a whole-tool entry does not implicitly opt in its destructive actions)\n- `disabled_tools` still applies on top — an opted-in tool can still be disabled\n- Entries that don't match anything, or match a non-destructive tool/action, produce console warnings\n\nThis is server-side enforcement: until opted in, destructive tools are absent from the MCP schema, so even a misbehaving client cannot call them. Currently shipped destructive entries: `change_note_type` (whole tool — rewrites every selected note's field layout) and `model_fields:remove` (action — permanently deletes a field and its content on every note of the type).\n\n### Custom Path\n\nSet `http_path` to serve the MCP endpoint under a custom path. Useful when exposing Anki via a tunnel (Cloudflare, ngrok) to avoid a fully open endpoint:\n\n```json\n{\n  \"http_path\": \"my-secret-path\"\n}\n```\n\nThe server will be accessible at `http://localhost:3141/my-secret-path/` instead of the root. Leave empty for default behavior.\n\n> **Note:** A custom path alone is not enough to expose the HTTP server through a tunnel or reverse proxy. You must also populate `http_allowed_hosts`/`http_allowed_origins`, or requests are rejected (`421` for a non-loopback `Host`, `403` for a non-loopback `Origin`) — see [Allowed Hosts and Origins (DNS-Rebinding Protection)](#allowed-hosts-and-origins-dns-rebinding-protection).\n\n## Security\n\nThe local HTTP server accepts requests only from loopback `Host`/`Origin` values by default, so **ordinary local use needs no setup** — localhost clients work out of the box. The sections below cover the available hardening layers (DNS-rebinding allowlist, optional API key, CORS, and media-import validation) for when you expose the server beyond your machine.\n\n**Upgrading from ≤ 0.20.0:** if you reach the server through a tunnel, reverse proxy, or by binding to `0.0.0.0`, requests now arrive with a non-loopback `Host` and are rejected with `421` until you allowlist that host — see [Allowed Hosts and Origins (DNS-Rebinding Protection)](#allowed-hosts-and-origins-dns-rebinding-protection). (Browser clients additionally need their `Origin` allowlisted, otherwise `403`.) Plain localhost setups are unaffected.\n\n### Allowed Hosts and Origins (DNS-Rebinding Protection)\n\nThe HTTP server enables DNS-rebinding protection with a built-in loopback allowlist (copied verbatim from the MCP SDK's own default):\n\n- Hosts: `127.0.0.1:*`, `localhost:*`, `[::1]:*`\n- Origins: `http://127.0.0.1:*`, `http://localhost:*`, `http://[::1]:*`\n\nOrdinary localhost clients therefore work out of the box. Two details of these defaults are worth knowing:\n\n- The `:*` patterns match a **host with a port** (`localhost:3141` ✓). A port-less `Host` header — which browsers and clients send only when the server runs on port 80 — does not match, so a non-default `\"http_port\": 80` needs `\"localhost\"`/`\"127.0.0.1\"` added to `http_allowed_hosts` explicitly.\n- The default origins are **`http://`-only**. An `https://localhost` origin is not covered and must be added to `http_allowed_origins`. (Requests with no `Origin` header at all — the normal case for non-browser MCP clients — always pass.)\n\nIf you expose the HTTP server through a tunnel or reverse proxy (e.g. ngrok, Cloudflare), requests arrive with a non-loopback `Host` (rejected with `421`) — and, for browser clients, a non-loopback `Origin` (rejected with `403`) — unless you extend the allowlist:\n\n```json\n{\n  \"http_allowed_hosts\": [\"myapp.ngrok.io\", \"myapp.ngrok.io:443\"],\n  \"http_allowed_origins\": [\"https://myapp.example\"]\n}\n```\n\n- `http_allowed_hosts` — `Host`-header values **without** a scheme (e.g. `\"myapp.ngrok.io\"` or `\"myapp.ngrok.io:443\"`)\n- `http_allowed_origins` — full origins **with** a scheme (e.g. `\"https://myapp.example\"`)\n\nBoth lists are appended to the built-in loopback defaults (the defaults are not replaced). Changing these requires an Anki restart, consistent with the other `http_*` settings.\n\n> DNS-rebinding vulnerability reported by [avishaigo-commits](https://github.com/avishaigo-commits).\n\n### API Key (Optional HTTP Auth)\n\n`http_api_key` adds an optional shared-secret auth layer on top of the HTTP transport (AnkiConnect-style). It is **empty by default**, which disables the layer and leaves the default behavior unchanged. When set to a non-empty value, **every** HTTP request must send an `Authorization: Bearer <key>` header matching that value, or it is rejected with `403`:\n\n```json\n{\n  \"http_api_key\": \"a-long-random-secret-key\"\n}\n```\n\nConfigure your client to send the header. With Claude Code:\n\n```bash\nclaude mcp add anki --transport http http://127.0.0.1:3141/ --header \"Authorization: Bearer a-long-random-secret-key\"\n```\n\nWith `mcp-remote` (e.g. Claude Desktop):\n\n```json\n{\n  \"mcpServers\": {\n    \"anki\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"mcp-remote\",\n        \"http://127.0.0.1:3141\",\n        \"--header\",\n        \"Authorization: Bearer a-long-random-secret-key\"\n      ]\n    }\n  }\n}\n```\n\nNotes:\n\n- **HTTP-only.** The key applies only to the local HTTP transport. The tunnel does its own OAuth login and is unaffected by `http_api_key`.\n- **Complementary, not a replacement.** It sits alongside DNS-rebinding protection (Allowed Hosts and Origins) — it does not replace it. Both layers apply independently.\n- **Pairs naturally with `http_path` + `http_allowed_hosts`** when exposing the HTTP server through a tunnel or reverse proxy: a secret path obscures the endpoint, the allowlist permits the proxy host, and the API key authenticates each request.\n- Use a long, random key (at least 16 characters). Leading/trailing whitespace is stripped from the presented token, so a configured key with surrounding whitespace will never match.\n- Changing this requires an Anki restart, consistent with the other `http_*` settings.\n\n### CORS Configuration\n\nTo allow browser-based MCP clients (like web-hosted MCP Inspector), add allowed origins:\n\n```json\n{\n  \"cors_origins\": [\"https://inspector.example.com\", \"http://localhost:5173\"]\n}\n```\n\nUse `[\"*\"]` to allow all origins (not recommended for production).\n\n> **Note:** A browser origin allowed via `cors_origins` must **also** be added to `http_allowed_origins`. CORS and the DNS-rebinding allowlist are separate layers: even with CORS configured, a non-loopback `Origin` is rejected with `403` by DNS-rebinding protection unless it is in `http_allowed_origins`.\n\nThe `cors_expose_headers` setting controls which response headers browsers can read. The default (`mcp-protocol-version`) lets browser-based MCP clients negotiate the protocol version. Since v0.16.0 the server runs in stateless mode, so `mcp-session-id` is no longer emitted and no longer needs to be exposed.\n\n### Media Security\n\n> Thanks to **[Hideaki Takahashi](https://github.com/Koukyosyumei)** (Columbia University) for responsibly disclosing the media path traversal vulnerability.\n\nThe `store_media_file` tool validates all inputs to prevent path traversal and SSRF attacks:\n\n- **File paths** are restricted to media files only (images, audio, video) via MIME type checking\n- **URLs** must use `http://` or `https://` and cannot target private/internal networks\n- **Filenames** are sanitized to remove path traversal sequences\n\nOptional hardening via config:\n\n```json\n{\n  \"media_import_dir\": \"/Users/me/anki-media\",\n  \"media_allowed_types\": [\"application/pdf\"],\n  \"media_allowed_hosts\": [\"192.168.1.50\", \"my-nas.local\"]\n}\n```\n\n- `media_import_dir` — restrict file path imports to this directory tree (empty = no restriction)\n- `media_allowed_types` — allow additional MIME types beyond image/audio/video\n- `media_allowed_hosts` — allow specific hosts to bypass private network blocking\n\n## Available Tools\n\n**Upgrading from ≤ 0.27.x:** `get_due_cards` no longer returns `back` unless you pass `include_answer=true`, and `front` is now the rendered question HTML rather than the raw field. Clients that read `back` from this tool need the flag.\n\n### Essential Tools\n\n| Tool | Description |\n|------|-------------|\n| `sync` | Synchronize collection with AnkiWeb (asynchronous job: `sync()` starts a sync, `sync(job_id)` polls its status, `sync(job_id, resolve=...)` resolves a full-sync conflict) |\n| `list_decks` | List all decks in the collection |\n| `create_deck` | Create a new deck |\n| `find_notes` | Search for notes using Anki's search syntax. `include_first_field=true` adds a `noteLabels` array (`{noteId, firstField, truncated, fullLength}`) so you can search and label notes in one call |\n| `notes_info` | Get detailed information about notes. `excerpt_chars` caps every field value at that many characters, marking each with `truncated`/`fullLength` — a cheap way to survey many notes |\n| `add_note` | Add a new note to a deck |\n| `add_notes` | Batch-add up to `max_notes_per_batch` notes (default 100) sharing the same deck and model. Uses Anki's native batch API for atomic undo. Supports partial success — individual failures don't affect others |\n| `card_management` | Manage cards with 9 actions: `reposition` (set learning order), `change_deck` (move between decks), `bury`/`unbury` (hide until tomorrow), `suspend`/`unsuspend` (indefinitely exclude from review), `set_flag` (color flags 0-7), `set_due_date` (reschedule with days DSL), `forget_cards` (reset to new) |\n| `tag_management` | Manage tags with 6 actions: `add_tags`/`remove_tags` (bulk add/remove on notes), `replace_tags` (swap one tag for another), `get_tags` (list all, or scoped to a deck via the optional `deck` param — distinct tags on notes with a card in that deck, subdecks included), `clear_unused_tags` (remove orphans), `batch_tags` (apply multiple add/remove operations in one call, partial success) |\n| `filtered_deck` | Filtered deck lifecycle with 5 actions: `create_or_update` (create or modify a filtered deck from 1-2 search terms — Anki's hard limit), `rebuild` (repopulate), `empty` (return cards to home decks), `delete` (return cards, then remove the deck), `get_info` (read-only inspection of up to 50 deck IDs per call — returns search terms, limit, order, reschedule flag and card count; non-filtered decks come back with `is_filtered=false`, unknown IDs are skipped and counted in `not_found`) |\n| `update_note_fields` | Update fields of existing notes. Two modes: full replace, or patch via `old_str`/`new_str` (find-and-replace within a field; must match exactly once or nothing is written) |\n| `update_notes` | Batch-update fields of multiple notes in one atomic undo step (single backend call). Validates every entry first; supports partial success up to `max_notes_per_batch` |\n| `delete_notes` | Delete notes from the collection |\n| `get_due_cards` | Get next due card for review (supports `skip_images`/`skip_audio` for voice mode). Returns the rendered question only; the answer is omitted unless `include_answer=true` |\n| `cards_stats` | Bulk per-card scheduling stats for a deck (incl. subdecks): type/queue/interval/tags/dueToday, paginated. FSRS-independent, compact payload for analytics |\n| `present_card` | Get card content for review |\n| `rate_card` | Rate a card after review (Again/Hard/Good/Easy) |\n| `model_names` | List available note types |\n| `model_field_names` | Get field names and descriptions for a note type |\n| `model_styling` | Get CSS styling for a note type. `include_latex=true` also returns the LaTeX preamble (`latex_pre`, `latex_post`, `latex_svg`) |\n| `update_model_styling` | Update CSS styling for a note type — full replace, or patch via `old_str`/`new_str` (must match exactly once). Can also write the LaTeX preamble (`latex_pre`/`latex_post`/`latex_svg`) |\n| `model_templates` | Read the Front/Back HTML templates for each card type in a note type |\n| `update_model_templates` | Update Front/Back template HTML — full replace, or patch via `old_str`/`new_str` (must match exactly once). Rejects unrecognized keys (case-sensitive) and unknown template names up front, applying all edits atomically — a failed call leaves the model unchanged |\n| `model_fields` | Manage fields on an existing note type: `add` (optionally at a 0-based index), `rename` (preserves content; card templates are **not** auto-updated), `reposition` (reorder). A `remove` action also exists but is [destructive](#destructive-tools-opt-in) — hidden unless opted in via `enabled_destructive_tools`. `add`, `remove` and `reposition` change field ordinals and force a one-way full sync; a pure `rename` does not. Every result carries `will_force_full_sync`, the collection's actual (sticky, collection-wide) state after the write |\n| `create_model` | Create a new note type |\n| `change_note_type` | Move existing notes to a different note type, remapping fields by name (`{old field: new field or null}`). [Destructive](#destructive-tools-opt-in) — hidden unless opted in via `enabled_destructive_tools`. Two-step flow: `dry_run=true` returns the resolved plan (mapping, dropped fields, cards removed), then the same call with `dry_run=false` **and** `confirm=true` applies it. All notes must share one source note type |\n| `store_media_file` | Store a media file (image/audio) via base64, file path, or URL. File paths are validated against a media-type allowlist; URLs are checked for SSRF |\n| `get_media_files_names` | List media files matching a pattern |\n| `delete_media_file` | Move a media file to Anki's trash (recoverable via Check Media) |\n\n### FSRS Tools\n\n| Tool | Description |\n|------|-------------|\n| `get_fsrs_params` | Get FSRS scheduler parameters for deck presets |\n| `set_fsrs_params` | Update FSRS parameters (weights, desired retention, max interval) |\n| `get_card_memory_state` | Get FSRS memory state (stability, difficulty, retrievability) for cards |\n| `optimize_fsrs_params` | Run FSRS parameter optimization using Anki's built-in optimizer |\n\n### GUI Tools\n\nThese tools interact with Anki's user interface:\n\n| Tool | Description |\n|------|-------------|\n| `gui_browse` | Open the card browser with a search query |\n| `gui_add_cards` | Open the Add Cards dialog |\n| `gui_edit_note` | Open the note editor for a specific note |\n| `gui_current_card` | Get info about the currently displayed card |\n| `gui_show_question` | Show the question side of current card |\n| `gui_show_answer` | Show the answer side of current card |\n| `gui_select_card` | Select a specific card in the reviewer |\n| `gui_deck_browser` | Navigate to deck browser |\n| `gui_undo` | Undo the last operation |\n\n### Resources\n\n| Resource | URI | Description |\n|----------|-----|-------------|\n| `system_info` | `anki://system-info` | Anki version, profile, and scheduler info |\n| `query_syntax` | `anki://query-syntax` | Anki search query syntax reference |\n| `schema` | `anki://schema` | Data model documentation (entities, fields, relationships) |\n| `stats_today` | `anki://stats/today` | Today's study statistics |\n| `stats_forecast` | `anki://stats/forecast` | 30-day review forecast |\n| `stats_collection` | `anki://stats/collection` | Overall collection statistics |\n| `fsrs_config` | `anki://fsrs/config` | FSRS configuration summary and parameters |\n\n### Prompts\n\n| Prompt | Description |\n|--------|-------------|\n| `review_session` | Guided review session workflow. Args: `deck_name` (default `Default`), `card_limit` (default 20), `review_style` — `interactive`, `quick`, or `voice` |\n| `twenty_rules` | Dr. Piotr Woźniak's *Twenty Rules of Formulating Knowledge* (SuperMemo), as card-authoring guidance for the assistant. No arguments |\n\n## Requirements\n\n- **Anki 25.07 or later** (ships Python 3.13)\n- Anki 25.02 and earlier ship Python 3.9, which is **not supported** — the MCP SDK requires Python 3.10+ ([#8](https://github.com/ankimcp/anki-mcp-server-addon/issues/8))\n\n## Architecture\n\nThe addon runs an MCP server in a background thread with two independent transports: local HTTP (FastMCP + uvicorn) and remote tunnel (WebSocket relay with in-memory transport). Both share the same FastMCP server instance. All Anki operations are bridged to the main Qt thread via a queue system, following the same proven pattern as AnkiConnect.\n\nFor details, see [Anki Add-on Development Documentation](https://addon-docs.ankiweb.net/).\n\n## Development\n\n### Running E2E Tests\n\nE2E tests run against a real Anki instance in Docker using [headless-anki](https://github.com/ankimcp/headless-anki).\n\n```bash\n# Install test dependencies\npython -m venv .venv\nsource .venv/bin/activate\npip install -r requirements-dev.txt\n\n# Build the addon\n./package.sh\n\n# Start Anki container\ncd .docker && docker compose up -d && cd ..\n\n# Run tests (waits for server automatically)\npytest tests/e2e/ -v\n\n# Stop container\ncd .docker && docker compose down\n```\n\nOr use the Makefile shortcuts:\n```bash\nmake e2e        # Build, start container, run tests, stop\nmake e2e-up     # Just start container\nmake e2e-test   # Just run tests\nmake e2e-down   # Just stop container\n```\n\n### CI\n\nE2E tests run automatically on push to any branch and on PRs to `main`. See `.github/workflows/e2e.yml`.\n\n## License\n\nAGPL-3.0-or-later\n\n## Links\n\n- [ankimcp.ai](https://ankimcp.ai) - Project homepage\n- [MCP Protocol](https://modelcontextprotocol.io/) - Model Context Protocol specification\n- [Anki Add-on Docs](https://addon-docs.ankiweb.net/) - Official Anki addon development documentation\n",
  "bytes": 29188,
  "sha": "b2ed88b6b4c32e6a907795768fdc8113cd15546bbdb435cc529c855be8756e6a",
  "repo_slug": "ankimcp/anki-mcp-server-addon",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_ai_ankimcp_anki_mcp_server_addon_5d7e32b0/readme"
}