"""Anki MCP server — manage Anki flashcards via AnkiConnect.""" import os import sys # sys.path injection for `mcp dev` compatibility _root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) if _root not in sys.path: sys.path.insert(0, _root) from dotenv import load_dotenv load_dotenv(os.path.join(_root, ".env")) from mcp.server.fastmcp import FastMCP from src import anki mcp = FastMCP( name="anki", instructions=( "Anki flashcard manager via AnkiConnect. " "Add cards to decks, review due cards, manage categories. " "Requires Anki to be running with the AnkiConnect add-on (code: 2055492159)." ), ) @mcp.tool() def list_decks() -> list[str]: """List all Anki deck names.""" return anki.list_decks() @mcp.tool() def create_deck(deck_name: str) -> dict: """Create a new Anki deck (or return existing deck ID). Args: deck_name: Deck name. Use '::' separator for nested decks (e.g. 'Korean::Vocabulary'). """ deck_id = anki.create_deck(deck_name) return {"deck_name": deck_name, "deck_id": deck_id} @mcp.tool() def add_card( deck_name: str, front: str, back: str, tags: list[str] = [], ) -> dict: """Add a Basic flashcard to a deck. Args: deck_name: Target deck name (must already exist or be created first). front: Front (question) side of the card. back: Back (answer) side of the card. tags: Optional list of tags to attach to the card. """ note_id = anki.add_note(deck_name, front, back, tags) return {"note_id": note_id, "deck_name": deck_name, "front": front, "back": back} @mcp.tool() def get_due_cards(deck_name: str = "", limit: int = 20) -> list[dict]: """Get today's due cards for review. Args: deck_name: Filter by deck name. Leave empty to get due cards from all decks. limit: Maximum number of cards to return (default 20). """ return anki.find_due_cards(deck_name, limit) @mcp.tool() def update_card(note_id: int, front: str = "", back: str = "") -> dict: """Edit an existing card's Front and/or Back content. Args: note_id: The note ID to update (obtained from add_card or get_due_cards). front: New front text. Leave empty to keep existing. back: New back text. Leave empty to keep existing. """ anki.update_note_fields(note_id, front, back) return {"note_id": note_id, "updated": {"front": bool(front), "back": bool(back)}} @mcp.tool() def update_deck(deck_name: str, new_name: str) -> dict: """Rename an existing deck. Args: deck_name: Current deck name. new_name: New deck name. """ anki.rename_deck(deck_name, new_name) return {"old_name": deck_name, "new_name": new_name} def main() -> None: mcp.run() if __name__ == "__main__": main()