CLAUDE.md · ankimcp/anki-mcp-server
CLAUDE.md from ankimcp/anki-mcp-server: 319 lines covering Project Overview, Quick Reference, Architecture, Adding New Tools and more.
Template by ankimcp
Install
Works with Claude Code. Read the repository before you give any tool access to your data.
curl -fsSL https://raw.githubusercontent.com/ankimcp/anki-mcp-server/main/CLAUDE.md -o CLAUDE.md
Read it and adapt it to your project before use.
Get the weekly diff
The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.
About
Use cases: documentation
Reviews
Write a reviewNo reviews yet. Installed it? Reviews come from verified installers only. How reviews work
What's in it
A CLAUDE.md file: project memory Claude Code loads at the start of every session.
- For
- Claude Code
- Length
- 319 lines
- File
CLAUDE.md
Sections
- Project Overview
- Quick Reference
- Architecture
- Adding New Tools
- Testing
- Git Hooks (Husky)
- Release Process
- MCPB Bundle Notes
- Environment
First lines (shown under the repo's MIT license)
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
MCP server enabling AI assistants to interact with Anki via AnkiConnect. Built with NestJS and `@rekog/mcp-nest`.
- **Package**: `@ankimcp/anki-mcp-server` (npm)
- **License**: MIT
- **Status**: Beta (0.x.x) - breaking changes allowed
- **User-facing docs**: `README.md` covers installation, client setup (Claude Desktop/MCPB, HTTP, tunnel), and the full tool catalog — consult it for anything user-facing rather than reconstructing it here.
## Quick Reference
```bash
# Build & Run
npm run build # Build → dist/ (all three entry points)
npm run start:dev:stdio # STDIO mode with watch
npm run start:dev:http # HTTP mode with watch
npm run start:dev:tunnel # Tunnel mode with watch + debug
# Testing
npm test # All tests
npm test -- path/to/file.spec.ts # Single test file
npm run test:tools # Tool unit tests only
npm run test:workflows # Multi-tool workflow scenarios (mocked)
npm run test:cov # With coverage (70% threshold)
npm run e2e:full:local # One-shot E2E: up → test → down
npm run smoke:pack # Pack + install from tarball (no lockfile), verify initialize/tools-list
# Quality
npm run lint && npm run type-check # Pre-push checks (also runs via Husky)
# Debugging
npm run inspector:stdio # MCP Inspector UI for testing tools
npm run inspector:stdio:debug # With debugger on port 9229
npm run inspector:http # Inspector against HTTP transport
```
## Architecture
### Entry Points
Three entry points compiled in a single build:
| Mode | Entry | Use Case | Logging |
|------|-------|----------|---------|
| STDIO | `dist/main-stdio.js` | Claude Desktop, MCP clients | stderr |
| HTTP | `dist/main-http.js` | Web-based AI (ChatGPT, claude.ai) | stdout |
| Tunnel | `dist/main-tunnel.js` | Remote access via WebSocket tunnel | stderr |
### Core Files
```
src/
├── main-stdio.ts # STDIO bootstrap: NestFactory.createMicroservice() with the MCP strategy
├── main-http.ts # HTTP bootstrap: NestFactory.create() + guards + connectMicroservice()
├── main-tunnel.ts # Tunnel bootstrap: auth commands + WebSocket tunnel
├── app.module.ts # Root module with forStdio()/forHttp()/forTunnel() factories
├── bootstrap.ts # Shared logger setup (pino → NestJS LoggerService) + createMcpStrategy()
├── version.ts # Package version constant (read from package.json at build)
├── cli/ # CLI layer (Commander parsing + user-facing output)
│ ├── args.ts # Commander entrypoint: option parsing, subcommand dispatch
│ ├── cli-output.ts # User-facing print helpers (cli.success/error/info/box) — separate from args.ts so non-CLI code can use it without pulling in Commander
│ ├── spinner.ts # Terminal spinner for long-running CLI operations
│ └── index.ts # Barrel re-export for the cli module
├── app-config.service.ts # IAnkiConfig implementation (reads from validated AppConfig)
├── config/ # Zod-validated config system (schema, factory, APP_CONFIG token)
├── services/ngrok.service.ts # Optional ngrok subprocess for HTTP-mode public tunneling
├── http/guards/ # HTTP-only guards: origin-validation + host-validation (DNS-rebinding protection)
├── http/mcp-http.factory.ts # StreamableHttpTransport + the Nest controller that owns the MCP route
├── tunnel/ # Tunnel mode: WebSocket client, OAuth device flow, credentials
│ ├── tunnel.client.ts # WebSocket client for tunnel server
│ ├── tunnel.transport.ts # McpTransport for tunnel mode: owns the bound MCP server + handleRequest()
│ ├── in-memory.transport.ts # SDK Transport that pairs a relayed request with the server's response
│ ├── tunnel.protocol.ts # WS message type definitions (request/response/ping/error)
│ ├── device-flow.service.ts # OAuth device flow authentication
│ ├── credentials.service.ts # Persistent credential storage
│ └── commands/ # CLI command handlers (login, logout, tunnel)
└── mcp/
├── clients/anki-connect.client.ts # HTTP client using ky (retries, error handling, read-only guard)
├── config/anki-config.interface.ts # ANKI_CONFIG injection token + IAnkiConfig interface
├── types/anki.types.ts # Shared Anki types (cards, notes, ratings)
├── utils/ # Shared utilities (anki.utils, date.utils, markdown.utils, stats.utils, media-validation.utils, card-states.utils, deck-hierarchy.utils, card-validation.utils, card-suspension.utils)
├── primitives/essential/ # Core tools, prompts, resources
└── primitives/gui/ # GUI-specific tools (require user approval)
```
### Module System
```
AppModule.forStdio()/forHttp()/forTunnel()
→ MCP_STRATEGY provider # the McpStrategy the entry point built
→ McpPrimitivesAnkiEssentialModule.forRoot()
→ McpPrimitivesAnkiGuiModule.forRoot()
```
Each entry point builds exactly one `McpStrategy` (`new McpStrategy({ name, version, icons, instructions, transports })` — see `createMcpStrategy()` in `bootstrap.ts`) and hands the same instance to both `AppModule.forX()` (as `MCP_STRATEGY`) and the NestJS microservice connection: `createMicroservice()` for STDIO/tunnel, `connectMicroservice()` + `startAllMicroservices()` for HTTP. Transports are instances, not enum values — `new StdioTransport()`, `new StreamableHttpTransport()`, `new TunnelTransport()`.
All tools/prompts/resources are `@McpController()` classes listed in each primitive module's `controllers` array (see `ESSENTIAL_MCP_TOOLS` and `GUI_MCP_TOOLS`). NestJS scans every module's controllers for the `@MessagePattern` handlers that `@Tool`/`@Prompt`/`@Resource` compile to, so `AppModule` does not re-list them.
**HTTP mode** does not let the transport self-mount its route. `createMcpHttpServer()` (`src/http/mcp-http.factory.ts`) builds the MCP endpoint as a controller via the `McpHttpControllerFor(transport)` mixin, so requests run through the Nest pipeline and the Origin/Host `APP_GUARD`s apply. A self-mounted route would register straight on the HTTP adapter and bypass them.
**Tunnel mode** uses `TunnelTransport` (`src/tunnel/tunnel.transport.ts`): it gets a fully-wired server from `ctx.createBoundServer(session)`, connects it to an `InMemoryTransport` channel, and exposes `handleRequest(body)`. `TunnelClient` relays request bodies from the tunnel server over WebSocket into that method. Bootstrap lives in `src/tunnel/commands/tunnel.command.ts`. The bound server is created with `era: "legacy"`, so tunnel mode serves only the 2025 protocol revision — STDIO and HTTP also negotiate the 2026-07-28 revision.
### Key Patterns
**Tool response format**: Success paths return raw objects matching the tool's `outputSchema`. The mcp-nest handler validates and wraps them automatically. Error paths use `createErrorResponse(error, context)` from `anki.utils.ts` which returns `CallToolResult` with `isError: true` and bypasses outputSchema validation. Two distinct `success: false` shapes exist: with `error` + `isError: true` (from `createErrorResponse`) the call failed; without them (see `add-notes.tool.ts`, `suspend`/`unsuspend`) the call ran and the payload describes a partial outcome — routing those through `createErrorResponse` would drop `structuredContent`.
**Action helper pattern**: The former aggregate tools (`deckActions`, `tagActions`, `mediaActions`) were split into single-purpose tools (`list-decks.tool.ts`, `deck-stats.tool.ts`, `create-deck.tool.ts`, `change-deck.tool.ts`, `store-media-file.tool.ts`, `replace-tags.tool.ts`, …). Their directories now hold only `actions/*.action.ts` — pure functions taking `(params, ankiClient)` that the split tools import. The tool class stays thin: log, call the helper, wrap failures in `createErrorResponse`.
**Read-only mode**: `AnkiConnectClient` enforces read-only mode by checking actions against a `WRITE_ACTIONS` set before sending requests. Throws `ReadOnlyModeError`. The set covers content, deck, tag, media and note-type writes, the manual rescheduling actions (`forgetCards`, `setDueDate`), `guiUndo`, and the Add/Edit dialogs (`guiAddCards`, `guiEditNote`). Still allowed: `answerCards` (rating during review), `suspend`/`unsuspend`, `sync`, and the GUI navigation actions. `createErrorResponse` replaces the caller's hint with a read-only hint for a `ReadOnlyModeError`, so tools need no special handling.
**Config system**: Two injection tokens:
- `APP_CONFIG` — validated `AppConfig` object (Zod schema in `src/config/config.schema.ts`). Provided as `useValue` after parsing env + CLI overrides.
- `ANKI_CONFIG` — AnkiConnect-specific config interface. Provided via `useClass: AppConfigService` in each module's `forRoot()`. Modules can swap the config provider for testing.
**Environment Configuration**: Config `process.env.*` reads go through `buildConfigInput()` in `src/config/config.factory.ts`. CLI args override env vars in memory (no `process.env` mutation). Services inject `AppConfigService` for type-safe access. Two deliberate exceptions sit outside the config system: `media-validation.utils.ts` (`MEDIA_*`) and `system-info.resource.ts` read `process.env` directly.
### Upstream AnkiConnect Quirks
These are upstream behaviors that shape tool design — surface them in tool descriptions, stated as facts about the tool (see "Description style" below), so the AI can avoid them:
- **`updateNoteFields` silently fails** if the target note is open in Anki's Browse window. The request returns 200 but fields don't persist. The tool description states this.
- **Model CSS is per-note-type, not per-note.** Use `modelStyling` to fetch CSS for a model; `notesInfo` tells you which model each note uses. `updateNoteFields` should preserve inline styles.
- **`sync` relies on the desktop app being logged into AnkiWeb.** There's no API path to authenticate — surface a helpful error hint.
- **`deleteNotes` permanently removes the notes and cascades to all their cards.** Anki's undo history (Edit > Undo, or `guiUndo`, which reverts the newest step) can restore them until that history is cleared, for example by a change Anki does not record for undo such as `updateNoteFields`; after that only a backup restores them. The tool requires explicit `confirmDeletion: true`.
### Build & Tooling Notes
- **NestJS CLI** builds the project (`nest build`). Asset copying is configured in `nest-cli.json` — all `**/*.md` files in `src/` are copied to `dist/`. This matters for prompt templates that reference markdown files.
- **`prebuild` hook** runs `scripts/generate-icon.mjs` before every `npm run build` (via npm's `pre*` lifecycle) — that's why an icon-generation step fires on build. It's expected, not a stray command.
- **ESLint flat config** (`eslint.config.mjs`) — not legacy `.eslintrc`. Uses `typescript-eslint` + Prettier integration.
- **TypeScript**: `strict: true`, target ES2023, `nodenext` module resolution. Path aliases are resolved by both `tsconfig.json` and Jest's `moduleNameMapper`.
- **Zod 4** (`^4.4.3`) — not Zod 3. Some patterns like `z.preprocess` in `config.schema.ts` are Zod 3 holdovers that still work but may need migration.
### Path Aliases
- `@/*` → `src/*`
- `@test/*` → `test/*`
### Key Dependencies
- **Zod v4** (`zod@^4.x`) — NOT v3. Zod 4 has different APIs (e.g., `z.interface()`, changed error handling). Don't use v3 patterns.
- **`@rekog/mcp-nest` v2** (`^2.0.0`) — NOT v1. No `McpModule.forRoot()`: the server is a NestJS microservice `CustomTransportStrategy` (`McpStrategy`), capability classes are `@McpController()`s, and handler methods take `@Payload()`.
- **`@modelcontextprotocol/{core,node,server}` v2** — the MCP SDK. The old single `@modelcontextprotocol/sdk` package is gone. Don't bump without testing MCP protocol compatibility.
- **`@nestjs/microservices`** — required by mcp-nest v2 (`CustomStrategy`, `@Payload()`), not optional.
- **TypeScript** — `strict: true`, `module: "nodenext"`, target `ES2023`. Path aliases (`@/`, `@test/`) handle most imports.
- **ESLint** — Flat config (`eslint.config.mjs`), not legacy `.eslintrc`.
### Logging Guidelines
**Two types of output - don't mix them:**
1. **CLI Output** (user-facing, clean, no timestamps):
```typescript
import { cli } from '@/cli/cli-output';Signals
- Stars
- 531
- Forks
- 38
- Open issues
- 1
- Contributors
- 6
- Last commit (this folder)
- Oct 4, 2026 (24 days ago)
- Last release
- Oct 4, 2026 (24 days ago)
From GitHub (fetched Oct 24, 2026).
Details
- License
- MIT
- Repository
- github.com/ankimcp/anki-mcp-server
- First seen
- Oct 24, 2026
- Last checked
- Oct 24, 2026
- Source
- GitHubgithub-graphql
Cite this page
CLAUDE.md · ankimcp/anki-mcp-server. Agent Mart. https://agentmart.dev/templates/ankimcp-anki-mcp-server-claude-md (last updated 2026-10-24)