@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
JavaScript
;
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