UNPKG

@ankimcp/anki-mcp-server

Version:

Model Context Protocol server for Anki - enables AI assistants to interact with your Anki flashcards

156 lines (147 loc) • 6.97 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.checkForUpdates = checkForUpdates; exports.parseOptionalUrl = parseOptionalUrl; exports.parseCliArgs = parseCliArgs; exports.displayStartupBanner = displayStartupBanner; const commander_1 = require("commander"); const fs_1 = require("fs"); const path_1 = require("path"); const update_notifier_1 = __importDefault(require("update-notifier")); const cli_output_1 = require("./cli-output"); const version_1 = require("../version"); function getPackageJson() { try { return JSON.parse((0, fs_1.readFileSync)((0, path_1.join)(__dirname, "../../package.json"), "utf-8")); } catch { return { version: "0.0.0", name: "ankimcp" }; } } function checkForUpdates() { (0, update_notifier_1.default)({ pkg: getPackageJson() }).notify(); } function parseAnkiConnectArg(value) { if (value.trim() === "") { throw new commander_1.InvalidArgumentError("AnkiConnect URL cannot be empty. Omit --anki-connect/-a to use the default (http://localhost:8765) or the ANKI_CONNECT_URL env var."); } return value; } function parseOptionalUrl(raw, flag, cli) { if (raw === false || raw === true) { return undefined; } let parsed; try { parsed = new URL(raw); } catch { cli.error(`Invalid ${flag} URL: ${raw === "" ? "(empty string)" : raw}. Expected a ws:// or wss:// URL.`); process.exit(1); } if (parsed.protocol !== "ws:" && parsed.protocol !== "wss:") { cli.error(`Invalid ${flag} URL protocol: ${parsed.protocol}. Expected ws:// or wss://.`); process.exit(1); } return raw; } function parseCliArgs() { const program = new commander_1.Command(); program .name("ankimcp") .description("AnkiMCP Server - Model Context Protocol server for Anki") .version((0, version_1.getVersion)()) .option("--stdio", "Run in STDIO mode (for MCP clients like Cursor, Cline, Zed)") .option("-p, --port <number>", "Port to listen on (HTTP mode; default: 3000, or PORT env var)") .option("-h, --host <address>", "Host to bind to (HTTP mode; default: 127.0.0.1, or HOST env var)") .option("-a, --anki-connect <url>", "AnkiConnect URL (default: http://localhost:8765, or ANKI_CONNECT_URL env var)", parseAnkiConnectArg) .option("--ngrok", "Start ngrok tunnel (requires global ngrok installation)") .option("--read-only", "Run in read-only mode (blocks content changes and rescheduling; rating, suspend and sync still work)") .option("--login [url]", "Authenticate with tunnel service (also triggered automatically by --tunnel when needed)") .option("--logout", "Clear tunnel credentials") .option("--tunnel [url]", "Connect to tunnel server (auto-launches browser login if not authenticated; default URL: wss://tunnel.ankimcp.ai)") .option("-d, --debug", "Enable debug logging (shows stack traces)") .addHelpText("after", ` Transport Modes: HTTP Mode (default): For web-based AI assistants (ChatGPT, Claude.ai) STDIO Mode: For desktop MCP clients (Cursor, Cline, Zed) Examples - HTTP Mode: $ ankimcp # Use defaults $ ankimcp --port 8080 # Custom port $ ankimcp --host 0.0.0.0 --port 3000 # Listen on all interfaces $ ankimcp --anki-connect http://localhost:8765 Examples - HTTP Mode with Ngrok: $ ankimcp --ngrok # Start with ngrok tunnel $ ankimcp --port 8080 --ngrok # Custom port + ngrok $ ankimcp --host 0.0.0.0 --ngrok # Public host + ngrok Examples - STDIO Mode: $ ankimcp --stdio # For use with npx in MCP clients # MCP client configuration (Cursor, Cline, Zed, etc.): { "mcpServers": { "anki-mcp": { "command": "npx", "args": ["-y", "ankimcp", "--stdio"] } } } Examples - Read-Only Mode: $ ankimcp --read-only # HTTP mode, read-only $ ankimcp --stdio --read-only # STDIO mode, read-only Ngrok Setup (one-time): 1. Install: npm install -g ngrok 2. Get auth token from: https://dashboard.ngrok.com/get-started/your-authtoken 3. Setup: ngrok config add-authtoken <your-token> 4. Run: ankimcp --ngrok Tunnel Mode: $ ankimcp --tunnel # Connect to wss://tunnel.ankimcp.ai # (auto-launches browser login if needed) $ ankimcp --tunnel wss://tunnel.ankimcp.ai/tunnel # Production tunnel (explicit) $ ankimcp --login # Pre-authenticate (optional) $ ankimcp --login wss://custom.server.com/tunnel # Login to custom server $ ankimcp --logout # Clear saved credentials `); program.parse(); const options = program.opts(); return { port: options.port !== undefined ? parseInt(options.port, 10) : undefined, host: options.host, ankiConnect: options.ankiConnect, ngrok: options.ngrok || false, readOnly: options.readOnly || false, login: options.login ?? false, logout: options.logout || false, tunnel: options.tunnel ?? false, debug: options.debug || false, }; } function displayStartupBanner(cli, options, ngrokUrl) { const readOnlyWarning = options.readOnly ? "\n\n** READ-ONLY MODE ENABLED **\nContent modifications (addNote, deleteNotes, createDeck, etc.), rescheduling (forgetCards, setDueDate), guiUndo, guiAddCards and guiEditNote are blocked.\nReview operations (sync, answerCards, suspend) remain available." : ""; cli.info(` ${(0, cli_output_1.formatBanner)("HTTP")}${readOnlyWarning} šŸš€ Server running on: http://${options.host}:${options.port} šŸ”Œ AnkiConnect URL: ${options.ankiConnect}${ngrokUrl ? `\n🌐 Ngrok tunnel: ${ngrokUrl}` : ""}${options.readOnly ? "\nšŸ”’ Mode: Read-only" : ""} Configuration: • Port: ${options.port} (override: --port 8080) • Host: ${options.host} (override: --host 0.0.0.0) • AnkiConnect: ${options.ankiConnect} (override: --anki-connect http://localhost:8765)${options.readOnly ? "\n • Read-only: Yes (content changes and rescheduling blocked)" : ""}${ngrokUrl ? `\n • Ngrok tunnel: ${ngrokUrl}\n • Ngrok dashboard: http://localhost:4040` : ""} ${!ngrokUrl ? ` Want remote access? • Tunnel (recommended): ankimcp --tunnel • ngrok also supported — run 'ankimcp --help' or see the docs ` : ` Share this URL with your AI assistant: ${ngrokUrl} `} Run 'ankimcp --help' for more options. `); } //# sourceMappingURL=args.js.map