@spacholski1225/anki-connect-mcp 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Szymon Pacholski
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,198 @@
1
+ # Anki Connect MCP Server
2
+
3
+ A Model Context Protocol (MCP) server that provides seamless integration with Anki through the AnkiConnect add-on. This server enables AI assistants and other MCP clients to interact with your Anki flashcard collection programmatically.
4
+
5
+ ## What is Anki Connect?
6
+
7
+ [AnkiConnect](https://ankiweb.net/shared/info/2055492159) is a powerful Anki add-on that exposes Anki's functionality through a REST API. It allows external applications to:
8
+
9
+ - Create, modify, and delete flashcards
10
+ - Manage decks and their configurations
11
+ - Control Anki's GUI programmatically
12
+ - Search and retrieve card information
13
+ - Manage tags and note types
14
+ - Perform bulk operations on cards and notes
15
+
16
+ ## What is MCP (Model Context Protocol)?
17
+
18
+ The Model Context Protocol is a standardized way for AI assistants to connect to external data sources and tools. This MCP server acts as a bridge between MCP clients (like Claude Desktop) and AnkiConnect, allowing AI assistants to help you manage your Anki flashcards naturally through conversation.
19
+
20
+ ## Features
21
+
22
+ This MCP server provides comprehensive access to AnkiConnect functionality through the following tools:
23
+
24
+ ### Flashcard Management
25
+ - [`create_flashcard()`](src/index.ts:67) - Create new flashcards with front and back content
26
+ - [`add_note()`](src/tools/notes.ts:22) - Add notes with custom fields and tags
27
+ - [`add_notes()`](src/tools/notes.ts) - Bulk add multiple notes
28
+ - [`update_note_fields()`](src/tools/notes.ts) - Update existing note fields
29
+ - [`find_notes()`](src/tools/notes.ts) - Search for notes using Anki's query syntax
30
+
31
+ ### Deck Management
32
+ - [`deck_names()`](src/tools/decks.ts:19) - Get all deck names
33
+ - [`deck_names_and_ids()`](src/tools/decks.ts:30) - Get deck names with their IDs
34
+ - [`change_deck()`](src/tools/decks.ts) - Move cards between decks
35
+ - [`delete_decks()`](src/tools/decks.ts) - Delete decks and optionally their cards
36
+ - [`get_deck_config()`](src/tools/decks.ts) - Retrieve deck configuration settings
37
+
38
+ ### Card Operations
39
+ - [`find_cards()`](src/tools/cards.ts) - Search for cards
40
+ - [`cards_info()`](src/tools/cards.ts) - Get detailed card information
41
+ - [`suspend()`](src/tools/cards.ts:18) / [`unsuspend()`](src/tools/cards.ts) - Suspend/unsuspend cards
42
+ - [`are_suspended()`](src/tools/cards.ts) / [`are_due()`](src/tools/cards.ts) - Check card status
43
+
44
+ ### GUI Control
45
+ - [`gui_deck_browser()`](src/index.ts:177) - Open deck browser
46
+ - [`gui_deck_review()`](src/index.ts:185) - Start deck review
47
+ - [`gui_browse()`](src/index.ts:196) - Open card browser with search
48
+ - [`gui_add_cards()`](src/index.ts:207) - Open add cards dialog
49
+
50
+ ### Utility Tools
51
+ - [`version()`](src/index.ts:98) - Get AnkiConnect version
52
+ - [`get_tags()`](src/index.ts:114) - Retrieve all tags
53
+ - [`multi()`](src/index.ts:223) - Execute multiple AnkiConnect requests in batch
54
+
55
+ ## Prerequisites
56
+
57
+ ### 1. Install AnkiConnect Add-on
58
+
59
+ 1. Open Anki
60
+ 2. Go to **Tools** → **Add-ons**
61
+ 3. Click **Get Add-ons...**
62
+ 4. Enter the code: `2055492159`
63
+ 5. Click **OK** and restart Anki
64
+
65
+ ### 2. Configure AnkiConnect (Optional)
66
+
67
+ AnkiConnect works out of the box, but you can customize its settings:
68
+
69
+ 1. Go to **Tools** → **Add-ons**
70
+ 2. Select **AnkiConnect** and click **Config**
71
+ 3. Modify settings if needed (default port is 8765)
72
+ 4. Restart Anki after making changes
73
+
74
+ ## Installation
75
+
76
+ 1. Clone this repository:
77
+ ```bash
78
+ git clone https://github.com/spacholski1225/mcp-server.git
79
+ cd mcp-server
80
+ ```
81
+
82
+ 2. Install dependencies:
83
+ ```bash
84
+ npm install
85
+ ```
86
+
87
+ 3. Build the project:
88
+ ```bash
89
+ npm run build
90
+ ```
91
+
92
+ ## MCP Client Configuration
93
+
94
+ ### Claude Desktop
95
+
96
+ To use this MCP server with Claude Desktop, add the following configuration to your MCP settings file:
97
+
98
+ **Location of MCP configuration file:**
99
+ - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
100
+ - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
101
+ - **Linux**: `~/.config/Claude/claude_desktop_config.json`
102
+
103
+ **Configuration:**
104
+ ```json
105
+ {
106
+ "mcpServers": {
107
+ "anki-connect": {
108
+ "command": "node",
109
+ "args": [
110
+ "dist/index.js"
111
+ ],
112
+ "cwd": "/path/to/your/mcp-server",
113
+ "env": {},
114
+ "disabled": false,
115
+ "alwaysAllow": [
116
+ "deck_names",
117
+ "cards_info",
118
+ "deck_names_and_ids",
119
+ "gui_deck_browser"
120
+ ]
121
+ }
122
+ }
123
+ }
124
+ ```
125
+
126
+ **Important Notes:**
127
+ - Replace `/path/to/your/mcp-server` with the actual path to your cloned repository
128
+ - The `alwaysAllow` array contains tools that won't require confirmation each time
129
+ - Make sure Anki is running with AnkiConnect enabled before using the MCP server
130
+
131
+ ### Other MCP Clients
132
+
133
+ For other MCP clients, use the following connection details:
134
+ - **Transport**: stdio
135
+ - **Command**: `node dist/index.js`
136
+ - **Working Directory**: Path to this repository
137
+
138
+ ## Usage Examples
139
+
140
+ Once configured with an MCP client like Claude Desktop, you can interact with your Anki collection naturally:
141
+
142
+ - *"Create a flashcard about the capital of France"*
143
+ - *"Show me all my deck names"*
144
+ - *"Find all cards tagged with 'vocabulary'"*
145
+ - *"Suspend all cards in the 'Difficult Words' deck"*
146
+ - *"Open the deck browser in Anki"*
147
+
148
+ ## Development
149
+
150
+ ### Project Structure
151
+
152
+ ```
153
+ src/
154
+ ├── index.ts # Main MCP server setup and tool definitions
155
+ ├── methods/
156
+ │ └── anki.ts # AnkiConnect API communication
157
+ ├── tools/ # Tool implementations
158
+ │ ├── cards.ts # Card-related operations
159
+ │ ├── decks.ts # Deck management
160
+ │ ├── misc.ts # Utility functions and GUI controls
161
+ │ ├── models.ts # Note type operations
162
+ │ └── notes.ts # Note management
163
+ └── types/ # TypeScript type definitions
164
+ └── anki/ # AnkiConnect API types
165
+ ```
166
+
167
+ ### Available Scripts
168
+
169
+ - `npm run build` - Compile TypeScript to JavaScript
170
+ - `npm start` - Run the compiled MCP server
171
+ - `npm test` - Run tests (not implemented yet)
172
+
173
+ ## Troubleshooting
174
+
175
+ ### Common Issues
176
+
177
+ 1. **Connection refused**: Make sure Anki is running and AnkiConnect is installed
178
+ 2. **Port conflicts**: Check if port 8765 is available or configure AnkiConnect to use a different port
179
+ 3. **Permission errors**: Ensure the MCP server has proper file system permissions
180
+ 4. **Tool not found**: Verify the MCP client configuration and restart the client
181
+
182
+ ### Debug Mode
183
+
184
+ To enable debug logging, check the console output when running the MCP server. All AnkiConnect requests and responses are logged for troubleshooting.
185
+
186
+ ## Contributing
187
+
188
+ Contributions are welcome! Please feel free to submit issues, feature requests, or pull requests.
189
+
190
+ ## License
191
+
192
+ ISC License
193
+
194
+ ## References
195
+
196
+ - [AnkiConnect Documentation](https://foosoft.net/projects/anki-connect/)
197
+ - [Model Context Protocol Specification](https://modelcontextprotocol.io/)
198
+ - [MCP SDK Documentation](https://github.com/modelcontextprotocol/typescript-sdk)
package/dist/index.js ADDED
@@ -0,0 +1,362 @@
1
+ #!/usr/bin/env node
2
+ import { callAnkiConnect } from "./methods/anki.js";
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { z } from "zod";
6
+ import { getVersion, getTags, guiCurrentCard, guiStartCardTimer, guiShowQuestion, guiShowAnswer, guiAnswerCard, guiDeckOverview, guiDeckBrowser, guiDeckReview, guiBrowse, guiAddCards, guiExitAnki, multi, upgrade } from "./tools/misc.js";
7
+ import { deckNames, deckNamesAndIds, getDecks, changeDeck, deleteDecks, getDeckConfig, saveDeckConfig, setDeckConfigId, cloneDeckConfigId, removeDeckConfigId } from "./tools/decks.js";
8
+ import { modelNames, modelNamesAndIds, modelFieldNames, modelFieldsOnTemplates } from "./tools/models.js";
9
+ import { addNote, addNotes, canAddNotes, updateNoteFields, addTags, removeTags, findNotes, notesInfo } from "./tools/notes.js";
10
+ import { suspend, unsuspend, areSuspended, areDue, getIntervals, findCards, cardsToNotes, cardsInfo } from "./tools/cards.js";
11
+ const server = new McpServer({
12
+ name: 'anki-connect mcp',
13
+ version: '0.0.1'
14
+ });
15
+ server.tool('create_flashcard', {
16
+ front: z.string(),
17
+ back: z.string()
18
+ }, async ({ front, back }) => {
19
+ const mcpFlashCard = {
20
+ action: "addNote",
21
+ version: 6,
22
+ params: {
23
+ note: {
24
+ deckName: "Default",
25
+ modelName: "Basic",
26
+ fields: {
27
+ Front: front,
28
+ Back: back
29
+ },
30
+ tags: ["ai"],
31
+ options: {
32
+ allowDuplicate: false
33
+ }
34
+ }
35
+ }
36
+ };
37
+ const responseData = await callAnkiConnect(mcpFlashCard);
38
+ return { content: [{ type: "text", text: JSON.stringify(responseData) }] };
39
+ });
40
+ // Version and basic info tools
41
+ server.tool('version', async () => {
42
+ const response = await getVersion();
43
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
44
+ });
45
+ server.tool('upgrade', async () => {
46
+ const response = await upgrade();
47
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
48
+ });
49
+ server.tool('get_tags', async () => {
50
+ const response = await getTags();
51
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
52
+ });
53
+ // GUI control tools
54
+ server.tool('gui_current_card', async () => {
55
+ const response = await guiCurrentCard();
56
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
57
+ });
58
+ server.tool('gui_start_card_timer', async () => {
59
+ const response = await guiStartCardTimer();
60
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
61
+ });
62
+ server.tool('gui_show_question', async () => {
63
+ const response = await guiShowQuestion();
64
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
65
+ });
66
+ server.tool('gui_show_answer', async () => {
67
+ const response = await guiShowAnswer();
68
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
69
+ });
70
+ server.tool('gui_answer_card', {
71
+ ease: z.number().min(1).max(4).describe('Answer ease: 1=Again, 2=Hard, 3=Good, 4=Easy')
72
+ }, async ({ ease }) => {
73
+ const response = await guiAnswerCard(ease);
74
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
75
+ });
76
+ server.tool('gui_deck_overview', {
77
+ name: z.string().describe('Name of the deck to open')
78
+ }, async ({ name }) => {
79
+ const response = await guiDeckOverview(name);
80
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
81
+ });
82
+ server.tool('gui_deck_browser', async () => {
83
+ const response = await guiDeckBrowser();
84
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
85
+ });
86
+ server.tool('gui_deck_review', {
87
+ name: z.string().describe('Name of the deck to review')
88
+ }, async ({ name }) => {
89
+ const response = await guiDeckReview(name);
90
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
91
+ });
92
+ server.tool('gui_browse', {
93
+ query: z.string().describe('Search query for browsing cards')
94
+ }, async ({ query }) => {
95
+ const response = await guiBrowse(query);
96
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
97
+ });
98
+ server.tool('gui_add_cards', async () => {
99
+ const response = await guiAddCards();
100
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
101
+ });
102
+ server.tool('gui_exit_anki', async () => {
103
+ const response = await guiExitAnki();
104
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
105
+ });
106
+ server.tool('multi', {
107
+ actions: z.array(z.object({
108
+ action: z.string(),
109
+ version: z.number(),
110
+ params: z.record(z.any()).optional()
111
+ })).describe('Array of AnkiConnect requests to execute')
112
+ }, async ({ actions }) => {
113
+ const response = await multi(actions);
114
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
115
+ });
116
+ // Deck management tools
117
+ server.tool('deck_names', async () => {
118
+ const response = await deckNames();
119
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
120
+ });
121
+ server.tool('deck_names_and_ids', async () => {
122
+ const response = await deckNamesAndIds();
123
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
124
+ });
125
+ server.tool('get_decks', {
126
+ cards: z.array(z.number()).describe('Array of card IDs to get deck information for')
127
+ }, async ({ cards }) => {
128
+ const response = await getDecks(cards);
129
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
130
+ });
131
+ server.tool('change_deck', {
132
+ cards: z.array(z.number()).describe('Array of card IDs to move'),
133
+ deck: z.string().describe('Name of the target deck')
134
+ }, async ({ cards, deck }) => {
135
+ const response = await changeDeck(cards, deck);
136
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
137
+ });
138
+ server.tool('delete_decks', {
139
+ decks: z.array(z.string()).describe('Array of deck names to delete'),
140
+ cardsToo: z.boolean().optional().describe('If true, cards within deleted decks will also be deleted; otherwise moved to default deck')
141
+ }, async ({ decks, cardsToo }) => {
142
+ const response = await deleteDecks(decks, cardsToo);
143
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
144
+ });
145
+ server.tool('get_deck_config', {
146
+ deck: z.string().describe('Name of the deck to get configuration for')
147
+ }, async ({ deck }) => {
148
+ const response = await getDeckConfig(deck);
149
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
150
+ });
151
+ server.tool('save_deck_config', {
152
+ config: z.object({
153
+ lapse: z.object({
154
+ leechFails: z.number(),
155
+ delays: z.array(z.number()),
156
+ minInt: z.number(),
157
+ leechAction: z.number(),
158
+ mult: z.number()
159
+ }),
160
+ dyn: z.boolean(),
161
+ autoplay: z.boolean(),
162
+ mod: z.number(),
163
+ id: z.number(),
164
+ maxTaken: z.number(),
165
+ new: z.object({
166
+ bury: z.boolean(),
167
+ order: z.number(),
168
+ initialFactor: z.number(),
169
+ perDay: z.number(),
170
+ delays: z.array(z.number()),
171
+ separate: z.boolean(),
172
+ ints: z.array(z.number())
173
+ }),
174
+ name: z.string(),
175
+ rev: z.object({
176
+ bury: z.boolean(),
177
+ ivlFct: z.number(),
178
+ ease4: z.number(),
179
+ maxIvl: z.number(),
180
+ perDay: z.number(),
181
+ minSpace: z.number(),
182
+ fuzz: z.number()
183
+ }),
184
+ timer: z.number(),
185
+ replayq: z.boolean(),
186
+ usn: z.number()
187
+ }).describe('Deck configuration object to save')
188
+ }, async ({ config }) => {
189
+ const response = await saveDeckConfig(config);
190
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
191
+ });
192
+ server.tool('set_deck_config_id', {
193
+ decks: z.array(z.string()).describe('Array of deck names to change configuration for'),
194
+ configId: z.number().describe('Configuration group ID to assign')
195
+ }, async ({ decks, configId }) => {
196
+ const response = await setDeckConfigId(decks, configId);
197
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
198
+ });
199
+ server.tool('clone_deck_config_id', {
200
+ name: z.string().describe('Name for the new configuration group'),
201
+ cloneFrom: z.number().optional().describe('ID of the configuration group to clone from (optional, defaults to default group)')
202
+ }, async ({ name, cloneFrom }) => {
203
+ const response = await cloneDeckConfigId(name, cloneFrom);
204
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
205
+ });
206
+ server.tool('remove_deck_config_id', {
207
+ configId: z.number().describe('ID of the configuration group to remove')
208
+ }, async ({ configId }) => {
209
+ const response = await removeDeckConfigId(configId);
210
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
211
+ });
212
+ // Model management tools
213
+ server.tool('model_names', async () => {
214
+ const response = await modelNames();
215
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
216
+ });
217
+ server.tool('model_names_and_ids', async () => {
218
+ const response = await modelNamesAndIds();
219
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
220
+ });
221
+ server.tool('model_field_names', {
222
+ modelName: z.string().describe('Name of the model to get field names for')
223
+ }, async ({ modelName }) => {
224
+ const response = await modelFieldNames(modelName);
225
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
226
+ });
227
+ server.tool('model_fields_on_templates', {
228
+ modelName: z.string().describe('Name of the model to get template fields for')
229
+ }, async ({ modelName }) => {
230
+ const response = await modelFieldsOnTemplates(modelName);
231
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
232
+ });
233
+ server.tool('add_note', {
234
+ deckName: z.string().describe('Name of the deck to add the note to'),
235
+ modelName: z.string().describe('Name of the note model/type'),
236
+ fields: z.record(z.string()).describe('Object with field names as keys and field values as strings'),
237
+ tags: z.array(z.string()).optional().describe('Array of tags to add to the note'),
238
+ audio: z.object({
239
+ url: z.string(),
240
+ filename: z.string(),
241
+ skipHash: z.string().optional(),
242
+ fields: z.string()
243
+ }).optional().describe('Optional audio attachment')
244
+ }, async ({ deckName, modelName, fields, tags, audio }) => {
245
+ const note = { deckName, modelName, fields, tags, audio };
246
+ const response = await addNote(note);
247
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
248
+ });
249
+ server.tool('add_notes', {
250
+ notes: z.array(z.object({
251
+ deckName: z.string().describe('Name of the deck to add the note to'),
252
+ modelName: z.string().describe('Name of the note model/type'),
253
+ fields: z.record(z.string()).describe('Object with field names as keys and field values as strings'),
254
+ tags: z.array(z.string()).optional().describe('Array of tags to add to the note'),
255
+ audio: z.object({
256
+ url: z.string(),
257
+ filename: z.string(),
258
+ skipHash: z.string().optional(),
259
+ fields: z.string()
260
+ }).optional().describe('Optional audio attachment')
261
+ })).describe('Array of note objects to create')
262
+ }, async ({ notes }) => {
263
+ const response = await addNotes(notes);
264
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
265
+ });
266
+ server.tool('can_add_notes', {
267
+ notes: z.array(z.object({
268
+ deckName: z.string().describe('Name of the deck to add the note to'),
269
+ modelName: z.string().describe('Name of the note model/type'),
270
+ fields: z.record(z.string()).describe('Object with field names as keys and field values as strings'),
271
+ tags: z.array(z.string()).optional().describe('Array of tags to add to the note')
272
+ })).describe('Array of note objects to check')
273
+ }, async ({ notes }) => {
274
+ const response = await canAddNotes(notes);
275
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
276
+ });
277
+ server.tool('update_note_fields', {
278
+ id: z.number().describe('ID of the note to update'),
279
+ fields: z.record(z.string()).describe('Object with field names as keys and new field values as strings')
280
+ }, async ({ id, fields }) => {
281
+ const noteUpdate = { id, fields };
282
+ const response = await updateNoteFields(noteUpdate);
283
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
284
+ });
285
+ server.tool('add_tags', {
286
+ notes: z.array(z.number()).describe('Array of note IDs to add tags to'),
287
+ tags: z.string().describe('Space-separated string of tags to add')
288
+ }, async ({ notes, tags }) => {
289
+ const response = await addTags(notes, tags);
290
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
291
+ });
292
+ server.tool('remove_tags', {
293
+ notes: z.array(z.number()).describe('Array of note IDs to remove tags from'),
294
+ tags: z.string().describe('Space-separated string of tags to remove')
295
+ }, async ({ notes, tags }) => {
296
+ const response = await removeTags(notes, tags);
297
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
298
+ });
299
+ server.tool('find_notes', {
300
+ query: z.string().describe('Search query string (same syntax as Anki browser)')
301
+ }, async ({ query }) => {
302
+ const response = await findNotes(query);
303
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
304
+ });
305
+ server.tool('notes_info', {
306
+ notes: z.array(z.number()).describe('Array of note IDs to get information for')
307
+ }, async ({ notes }) => {
308
+ const response = await notesInfo(notes);
309
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
310
+ });
311
+ // Card management tools
312
+ server.tool('suspend', {
313
+ cards: z.array(z.number()).describe('Array of card IDs to suspend')
314
+ }, async ({ cards }) => {
315
+ const response = await suspend(cards);
316
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
317
+ });
318
+ server.tool('unsuspend', {
319
+ cards: z.array(z.number()).describe('Array of card IDs to unsuspend')
320
+ }, async ({ cards }) => {
321
+ const response = await unsuspend(cards);
322
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
323
+ });
324
+ server.tool('are_suspended', {
325
+ cards: z.array(z.number()).describe('Array of card IDs to check if suspended')
326
+ }, async ({ cards }) => {
327
+ const response = await areSuspended(cards);
328
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
329
+ });
330
+ server.tool('are_due', {
331
+ cards: z.array(z.number()).describe('Array of card IDs to check if due')
332
+ }, async ({ cards }) => {
333
+ const response = await areDue(cards);
334
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
335
+ });
336
+ server.tool('get_intervals', {
337
+ cards: z.array(z.number()).describe('Array of card IDs to get intervals for'),
338
+ complete: z.boolean().optional().describe('If true, returns all intervals; if false, returns only the most recent interval')
339
+ }, async ({ cards, complete }) => {
340
+ const response = await getIntervals(cards, complete);
341
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
342
+ });
343
+ server.tool('find_cards', {
344
+ query: z.string().describe('Search query string (same syntax as Anki browser)')
345
+ }, async ({ query }) => {
346
+ const response = await findCards(query);
347
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
348
+ });
349
+ server.tool('cards_to_notes', {
350
+ cards: z.array(z.number()).describe('Array of card IDs to convert to note IDs')
351
+ }, async ({ cards }) => {
352
+ const response = await cardsToNotes(cards);
353
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
354
+ });
355
+ server.tool('cards_info', {
356
+ cards: z.array(z.number()).describe('Array of card IDs to get detailed information for')
357
+ }, async ({ cards }) => {
358
+ const response = await cardsInfo(cards);
359
+ return { content: [{ type: 'text', text: JSON.stringify(response) }] };
360
+ });
361
+ const transport = new StdioServerTransport();
362
+ await server.connect(transport);
@@ -0,0 +1,22 @@
1
+ import axios from 'axios';
2
+ const BASE_URL = 'http://localhost:8765';
3
+ const api = axios.create({
4
+ baseURL: BASE_URL,
5
+ timeout: 5000,
6
+ headers: {
7
+ 'Content-Type': 'application/json'
8
+ }
9
+ });
10
+ export async function callAnkiConnect(request) {
11
+ try {
12
+ const response = await api.post('/', request);
13
+ console.log('Request:', request);
14
+ console.log('===RESPONSE===');
15
+ console.log(response.data);
16
+ return response.data;
17
+ }
18
+ catch (error) {
19
+ console.log('Anki Connect Error:', error);
20
+ throw error;
21
+ }
22
+ }