A Python MCP server to integrate with Anki via the AnkiConnect plugin using FastMCP with HTTP transport
This server demonstrates basic competence with clear tool naming and consistent use of structured response types. However, it has significant gaps in parameter descriptions, schema completeness, and error handling guidance. 9 of 13 tools lack proper parameter descriptions, and error responses are generic without recovery guidance. Output schemas are present but not documented in tool descriptions. The server uses Pydantic models correctly but doesn't expose schema validation in parameter constraints (enums, ranges, patterns). For comparison, the rubric baseline shows 94% of A+ tools have complete docstrings and 100% have documented return types, this server achieves ~70% coverage.
Create a new deck in Anki
Add a new note to Anki with optional media attachments. Media files can be specified with a url, path, or base64-encoded data. The media will be appended to the fields specified in the media object's fields list.
Delete a note by note_id.
Get the full path to Anki's collection.media folder.
Get a model, including field and template definitions, from Anki
Get info for a note by note_id.
Get the names of all decks from Anki
9 tools lack meaningful descriptions: list_notes_in_deck, add_deck, get_media_dir_path, sync_collection, delete_note, and others have descriptions under 50 characters or are generic. Descriptions must be 10-1024 chars and explain WHAT the tool does, WHEN to use it, and ANY SIDE EFFECTS. For example, delete_note should say 'Permanently delete a note from Anki. This operation cannot be undone.' and sync_collection should explain that it requires AnkiWeb login and network connectivity.
Parameter descriptions are missing or incomplete. Example: add_note's 'picture', 'audio', 'video' parameters have descriptions of the array structure but do NOT explain how the 'fields' list inside each media object maps fields to the attachment. The 'skip_hash' field in media objects is not documented at all. LLMs cannot infer field mapping semantics.
Inferred effective spec: <=2025-11-25.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-22 | D | 56 | <=2025-11-25 | v2 |
| 2026-03-09 | D | 56 | - | v1 |
Get the names of all note models from Anki
List all notes in a specified deck
Move a note's cards into the specified deck. Notes in Anki are associated with one or more cards; decks are assigned per-card. This tool moves all cards for the note into the target deck.
Store a media file in Anki's media folder. Provide one of data (base64), path, or url. Returns the filename as stored in Anki.
Sync the Anki collection with AnkiWeb
Update fields and tags for a note by note_id. Fields can be provided as a dictionary or a JSON string. Tags: only updates if a non-empty list is provided.
Error handling is generic. All tools return ErrorResponse(error=str(e), operation='...'), a raw exception message that gives the LLM no recovery guidance. Per the rubric, error responses must tell the LLM what to do next: 'Note not found. Try list_notes_in_deck() first.' Instead, the LLM receives 'Exception: note not found' with no actionable next step. This violates the recovery-guide pattern.
No parameter validation constraints are exposed. The 'fields' parameter in add_note and update_note accepts any dict[str, str] but doesn't document: (1) which fields are required vs optional, (2) field name format rules, (3) field value length limits. This forces LLMs to call get_model first to discover valid field names, a wasteful two-call pattern. add_note description should say 'Fields must match the model's field names; call get_model(model_name) first to see required fields.'
Output schemas are not documented in tool descriptions. LLMs need to know what fields to expect in responses to plan downstream calls. Example: add_note returns NoteCreated(note_id=int) but the description does not state 'Returns the new note_id as an integer.' delete_note returns NoteDeleted(note_id=int), again, undocumented. This violates the response-shaper pattern and wastes tokens as LLMs experiment with field names.
Destructive operations (delete_note) lack a confirmation or dry-run mechanism. The tool accepts a note_id and immediately deletes it. Per the rubric, irreversible operations should support a dry-run or confirmation step to prevent agent mistakes. Consider adding a 'confirm=true' parameter or a separate 'prepare_delete_note' that returns a summary before executing.
No timeout handling documented. store_media_file can download from a URL but doesn't specify a timeout. If the URL is slow or unreachable, the tool blocks indefinitely. Per the rubric, tools calling external services must set explicit timeouts and return a timeout error with retry guidance.
Vague parameter names reduce clarity. Example: update_note's 'fields' parameter accepts dict[str, str] | str | None with conflicting semantics. The description says 'Fields can be provided as a dictionary or a JSON string' but doesn't clarify WHEN to use which format or what happens if both formats are invalid JSON. This forces LLMs to guess.