@fredrika/mcp-mochi 1.0.6-beta.1 → 2.1.0
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/README.md +30 -49
- package/dist/index.d.ts +4 -6
- package/dist/index.js +173 -35
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Mochi MCP Server
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
MCP server for [Mochi](https://mochi.cards) flashcard integration, allowing you to manage your flashcards through the Model Context Protocol.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
@@ -67,51 +67,32 @@ Add the following to your `claude_desktop_config.json`:
|
|
|
67
67
|
|
|
68
68
|
## Available Tools
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
### `mochi_list_flashcards`
|
|
98
|
-
List flashcards (paginated).
|
|
99
|
-
- `deck-id`: (optional) Filter by deck
|
|
100
|
-
- `limit`: (optional) 1-100
|
|
101
|
-
- `bookmark`: (optional) Pagination token
|
|
102
|
-
|
|
103
|
-
### `mochi_list_decks`
|
|
104
|
-
List all decks.
|
|
105
|
-
- `bookmark`: (optional) Pagination token
|
|
106
|
-
|
|
107
|
-
### `mochi_list_templates`
|
|
108
|
-
List all templates with their field definitions.
|
|
109
|
-
- `bookmark`: (optional) Pagination token
|
|
110
|
-
|
|
111
|
-
### `mochi_get_due_cards`
|
|
112
|
-
Get flashcards due for review.
|
|
113
|
-
- `deck-id`: (optional) Filter by deck
|
|
114
|
-
- `date`: (optional) ISO 8601 date (defaults to today)
|
|
70
|
+
| Tool | Description |
|
|
71
|
+
|------|-------------|
|
|
72
|
+
| `mochi_create_flashcard` | Create a new flashcard in Mochi |
|
|
73
|
+
| `mochi_create_card_from_template` | Create a flashcard using a template with field names (auto-maps to IDs) |
|
|
74
|
+
| `mochi_update_flashcard` | Update a flashcard's content, deck, template, or fields. Can also soft-delete with `trashed` property |
|
|
75
|
+
| `mochi_delete_flashcard` | Permanently delete a flashcard and its attachments (cannot be undone) |
|
|
76
|
+
| `mochi_archive_flashcard` | Archive or unarchive a flashcard |
|
|
77
|
+
| `mochi_add_attachment` | Add an attachment (image, audio, etc.) to a card using base64 data |
|
|
78
|
+
| `mochi_list_flashcards` | List flashcards, optionally filtered by deck |
|
|
79
|
+
| `mochi_list_decks` | List all decks |
|
|
80
|
+
| `mochi_list_templates` | List all templates with their field definitions |
|
|
81
|
+
| `mochi_get_template` | Get a single template by ID |
|
|
82
|
+
| `mochi_get_due_cards` | Get flashcards due for review |
|
|
83
|
+
|
|
84
|
+
## Resources
|
|
85
|
+
|
|
86
|
+
| URI | Description |
|
|
87
|
+
|-----|-------------|
|
|
88
|
+
| `mochi://decks` | List of all decks |
|
|
89
|
+
| `mochi://templates` | List of all templates |
|
|
90
|
+
|
|
91
|
+
## Prompts
|
|
92
|
+
|
|
93
|
+
| Prompt | Description |
|
|
94
|
+
|--------|-------------|
|
|
95
|
+
| `write-flashcard` | Generates a well-structured flashcard following best practices (atomic questions, cloze deletions, etc.) |
|
|
115
96
|
|
|
116
97
|
## Examples
|
|
117
98
|
|
|
@@ -122,7 +103,7 @@ Get flashcards due for review.
|
|
|
122
103
|
"tool": "mochi_create_flashcard",
|
|
123
104
|
"params": {
|
|
124
105
|
"content": "What is MCP?\n---\nModel Context Protocol - a protocol for providing context to LLMs",
|
|
125
|
-
"
|
|
106
|
+
"deckId": "<DECK_ID>"
|
|
126
107
|
}
|
|
127
108
|
}
|
|
128
109
|
```
|
|
@@ -133,8 +114,8 @@ Get flashcards due for review.
|
|
|
133
114
|
{
|
|
134
115
|
"tool": "mochi_create_card_from_template",
|
|
135
116
|
"params": {
|
|
136
|
-
"
|
|
137
|
-
"
|
|
117
|
+
"templateId": "<TEMPLATE_ID>",
|
|
118
|
+
"deckId": "<DECK_ID>",
|
|
138
119
|
"fields": {
|
|
139
120
|
"Front": "What is the capital of France?",
|
|
140
121
|
"Back": "Paris"
|
package/dist/index.d.ts
CHANGED
|
@@ -5,10 +5,6 @@ declare const CreateCardRequestSchema: z.ZodObject<{
|
|
|
5
5
|
deckId: z.ZodString;
|
|
6
6
|
templateId: z.ZodDefault<z.ZodNullable<z.ZodOptional<z.ZodString>>>;
|
|
7
7
|
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
8
|
-
fields: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
9
|
-
id: z.ZodString;
|
|
10
|
-
value: z.ZodString;
|
|
11
|
-
}, z.core.$strip>>>;
|
|
12
8
|
}, z.core.$strip>;
|
|
13
9
|
declare const UpdateCardRequestSchema: z.ZodObject<{
|
|
14
10
|
content: z.ZodOptional<z.ZodString>;
|
|
@@ -112,7 +108,7 @@ declare const ListCardsResponseSchema: z.ZodObject<{
|
|
|
112
108
|
}, z.core.$strip>>;
|
|
113
109
|
}, z.core.$strip>;
|
|
114
110
|
type CreateCardResponse = z.infer<typeof CreateCardResponseSchema>;
|
|
115
|
-
type ListDecksResponse = z.infer<typeof ListDecksResponseSchema
|
|
111
|
+
type ListDecksResponse = z.infer<typeof ListDecksResponseSchema>;
|
|
116
112
|
type ListCardsResponse = z.infer<typeof ListCardsResponseSchema>;
|
|
117
113
|
declare const ListDecksResponseSchema: z.ZodObject<{
|
|
118
114
|
bookmark: z.ZodString;
|
|
@@ -120,6 +116,7 @@ declare const ListDecksResponseSchema: z.ZodObject<{
|
|
|
120
116
|
id: z.ZodString;
|
|
121
117
|
sort: z.ZodNumber;
|
|
122
118
|
name: z.ZodString;
|
|
119
|
+
"template-id": z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
123
120
|
"archived?": z.ZodNullable<z.ZodOptional<z.ZodBoolean>>;
|
|
124
121
|
"trashed?": z.ZodNullable<z.ZodOptional<z.ZodObject<{
|
|
125
122
|
date: z.ZodString;
|
|
@@ -132,7 +129,7 @@ declare const GetDueCardsResponseSchema: z.ZodObject<{
|
|
|
132
129
|
content: z.ZodString;
|
|
133
130
|
name: z.ZodString;
|
|
134
131
|
"deck-id": z.ZodString;
|
|
135
|
-
"new?": z.ZodBoolean
|
|
132
|
+
"new?": z.ZodOptional<z.ZodBoolean>;
|
|
136
133
|
}, z.core.$loose>>;
|
|
137
134
|
}, z.core.$strip>;
|
|
138
135
|
export declare class MochiClient {
|
|
@@ -147,6 +144,7 @@ export declare class MochiClient {
|
|
|
147
144
|
getDueCards(params?: GetDueCardsParams): Promise<z.infer<typeof GetDueCardsResponseSchema>>;
|
|
148
145
|
getTemplate(templateId: string): Promise<z.infer<typeof TemplateSchema>>;
|
|
149
146
|
createCardFromTemplate(request: CreateCardFromTemplateParams): Promise<CreateCardResponse>;
|
|
147
|
+
deleteCard(cardId: string): Promise<void>;
|
|
150
148
|
addAttachment(request: AddAttachmentRequest): Promise<{
|
|
151
149
|
filename: string;
|
|
152
150
|
markdown: string;
|
package/dist/index.js
CHANGED
|
@@ -34,7 +34,7 @@ const CreateCardRequestSchema = z.object({
|
|
|
34
34
|
content: z
|
|
35
35
|
.string()
|
|
36
36
|
.min(1)
|
|
37
|
-
.describe("Markdown content of the card. Separate
|
|
37
|
+
.describe("Markdown content of the card. Separate the question and answer with a horizontal rule surrounded by newlines: '\\n---\\n'"),
|
|
38
38
|
deckId: z.string().min(1).describe("ID of the deck to create the card in"),
|
|
39
39
|
templateId: z
|
|
40
40
|
.string()
|
|
@@ -46,10 +46,6 @@ const CreateCardRequestSchema = z.object({
|
|
|
46
46
|
.array(z.string())
|
|
47
47
|
.optional()
|
|
48
48
|
.describe("Optional array of tags to add to the card"),
|
|
49
|
-
fields: z
|
|
50
|
-
.record(z.string(), CreateCardFieldSchema)
|
|
51
|
-
.optional()
|
|
52
|
-
.describe("Map of field IDs to field values. Required only when using a template"),
|
|
53
49
|
});
|
|
54
50
|
const UpdateCardRequestSchema = z.object({
|
|
55
51
|
content: z
|
|
@@ -90,6 +86,9 @@ const ListTemplatesParamsSchema = z.object({
|
|
|
90
86
|
.optional()
|
|
91
87
|
.describe("Pagination bookmark for fetching next page of results"),
|
|
92
88
|
});
|
|
89
|
+
const GetTemplateParamsSchema = z.object({
|
|
90
|
+
templateId: z.string().min(1).describe("ID of the template to fetch"),
|
|
91
|
+
});
|
|
93
92
|
const GetDueCardsParamsSchema = z.object({
|
|
94
93
|
deckId: z
|
|
95
94
|
.string()
|
|
@@ -104,14 +103,14 @@ const CreateCardFromTemplateSchema = z.object({
|
|
|
104
103
|
templateId: z
|
|
105
104
|
.string()
|
|
106
105
|
.min(1)
|
|
107
|
-
.describe("ID of the template to use. Get this from
|
|
106
|
+
.describe("ID of the template to use. Get this from list_templates."),
|
|
108
107
|
deckId: z
|
|
109
108
|
.string()
|
|
110
109
|
.min(1)
|
|
111
|
-
.describe("ID of the deck to create the card in. Get this from
|
|
110
|
+
.describe("ID of the deck to create the card in. Get this from list_decks."),
|
|
112
111
|
fields: z
|
|
113
112
|
.record(z.string(), z.string())
|
|
114
|
-
.describe('Map of field NAMES (not IDs) to values. E.g., { "Word": "serendipity" }
|
|
113
|
+
.describe('Map of field NAMES (not IDs) to values. E.g., { "Word": "serendipity" }'),
|
|
115
114
|
tags: z
|
|
116
115
|
.array(z.string())
|
|
117
116
|
.optional()
|
|
@@ -217,7 +216,7 @@ const CardSchema = z
|
|
|
217
216
|
.describe("Array of tags associated with the card"),
|
|
218
217
|
content: z
|
|
219
218
|
.string()
|
|
220
|
-
.describe('Markdown content of the card. Separate
|
|
219
|
+
.describe('Markdown content of the card. Separate the question and answer with "---"'),
|
|
221
220
|
name: z.string().describe("Display name of the card"),
|
|
222
221
|
"deck-id": z.string().describe("ID of the deck containing the card"),
|
|
223
222
|
fields: z
|
|
@@ -239,6 +238,11 @@ const DeckSchema = z
|
|
|
239
238
|
id: z.string().describe("Unique identifier for the deck"),
|
|
240
239
|
sort: z.number().describe("Sort order of the deck"),
|
|
241
240
|
name: z.string().describe("Display name of the deck"),
|
|
241
|
+
"template-id": z
|
|
242
|
+
.string()
|
|
243
|
+
.optional()
|
|
244
|
+
.nullable()
|
|
245
|
+
.describe("Template ID associated with this deck, if any"),
|
|
242
246
|
"archived?": z
|
|
243
247
|
.boolean()
|
|
244
248
|
.optional()
|
|
@@ -248,7 +252,7 @@ const DeckSchema = z
|
|
|
248
252
|
.object({ date: z.string() })
|
|
249
253
|
.optional()
|
|
250
254
|
.nullable()
|
|
251
|
-
.describe("
|
|
255
|
+
.describe("Timestamp when the deck was trashed, in ISO 8601 format (matching JavaScript's Date#toJSON)"),
|
|
252
256
|
})
|
|
253
257
|
.strip();
|
|
254
258
|
const ListDecksResponseSchema = z
|
|
@@ -263,21 +267,24 @@ const DueCardSchema = z
|
|
|
263
267
|
content: z.string().describe("Markdown content of the card"),
|
|
264
268
|
name: z.string().describe("Display name of the card"),
|
|
265
269
|
"deck-id": z.string().describe("ID of the deck containing the card"),
|
|
266
|
-
"new?": z
|
|
270
|
+
"new?": z
|
|
271
|
+
.boolean()
|
|
272
|
+
.optional()
|
|
273
|
+
.describe("Whether the card is new (never reviewed)"),
|
|
267
274
|
})
|
|
268
275
|
.passthrough();
|
|
269
276
|
const GetDueCardsResponseSchema = z.object({
|
|
270
277
|
cards: z.array(DueCardSchema).describe("Array of cards due for review"),
|
|
271
278
|
});
|
|
272
279
|
function getApiKey() {
|
|
273
|
-
const apiKey = process.env.
|
|
280
|
+
const apiKey = process.env.API_KEY;
|
|
274
281
|
if (!apiKey) {
|
|
275
|
-
console.error("
|
|
282
|
+
console.error("API_KEY environment variable is not set");
|
|
276
283
|
process.exit(1);
|
|
277
284
|
}
|
|
278
285
|
return apiKey;
|
|
279
286
|
}
|
|
280
|
-
const
|
|
287
|
+
const API_KEY = getApiKey();
|
|
281
288
|
export class MochiClient {
|
|
282
289
|
api;
|
|
283
290
|
token;
|
|
@@ -290,6 +297,24 @@ export class MochiClient {
|
|
|
290
297
|
"Content-Type": "application/json",
|
|
291
298
|
},
|
|
292
299
|
});
|
|
300
|
+
// Add response interceptor for error handling
|
|
301
|
+
this.api.interceptors.response.use((response) => response, (error) => {
|
|
302
|
+
if (axios.isAxiosError(error) && error.response) {
|
|
303
|
+
const { status, data } = error.response;
|
|
304
|
+
// Mochi API returns errors as arrays or objects
|
|
305
|
+
if (data && (Array.isArray(data) || typeof data === "object")) {
|
|
306
|
+
throw new MochiError(data, status);
|
|
307
|
+
}
|
|
308
|
+
// Fallback for string error messages
|
|
309
|
+
if (typeof data === "string" && data.length > 0) {
|
|
310
|
+
throw new MochiError([data], status);
|
|
311
|
+
}
|
|
312
|
+
// Generic error with status
|
|
313
|
+
throw new MochiError([`Request failed with status ${status}`], status);
|
|
314
|
+
}
|
|
315
|
+
// Re-throw non-axios errors
|
|
316
|
+
throw error;
|
|
317
|
+
});
|
|
293
318
|
}
|
|
294
319
|
async createCard(request) {
|
|
295
320
|
const mochiRequest = toMochiCreateCardRequest(request);
|
|
@@ -306,9 +331,13 @@ export class MochiClient {
|
|
|
306
331
|
? ListDecksParamsSchema.parse(params)
|
|
307
332
|
: undefined;
|
|
308
333
|
const response = await this.api.get("/decks", { params: validatedParams });
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
.
|
|
334
|
+
const parsed = ListDecksResponseSchema.parse(response.data);
|
|
335
|
+
return {
|
|
336
|
+
bookmark: parsed.bookmark,
|
|
337
|
+
docs: parsed.docs
|
|
338
|
+
.filter((deck) => !deck["archived?"] && !deck["trashed?"])
|
|
339
|
+
.sort((a, b) => a.sort - b.sort),
|
|
340
|
+
};
|
|
312
341
|
}
|
|
313
342
|
async listCards(params) {
|
|
314
343
|
const validatedParams = params
|
|
@@ -372,11 +401,13 @@ export class MochiClient {
|
|
|
372
401
|
content,
|
|
373
402
|
deckId: request.deckId,
|
|
374
403
|
templateId: request.templateId,
|
|
375
|
-
fields,
|
|
376
404
|
tags: request.tags,
|
|
377
405
|
};
|
|
378
406
|
return this.createCard(createRequest);
|
|
379
407
|
}
|
|
408
|
+
async deleteCard(cardId) {
|
|
409
|
+
await this.api.delete(`/cards/${cardId}`);
|
|
410
|
+
}
|
|
380
411
|
async addAttachment(request) {
|
|
381
412
|
// Infer content-type from filename if not provided
|
|
382
413
|
let contentType = request.contentType;
|
|
@@ -426,7 +457,34 @@ const server = new McpServer({
|
|
|
426
457
|
// Schema for update flashcard tool (combines cardId with update fields)
|
|
427
458
|
const UpdateFlashcardToolSchema = z.object({
|
|
428
459
|
cardId: z.string().describe("ID of the card to update"),
|
|
429
|
-
|
|
460
|
+
content: z
|
|
461
|
+
.string()
|
|
462
|
+
.optional()
|
|
463
|
+
.describe("Updated markdown content of the card"),
|
|
464
|
+
deckId: z.string().optional().describe("ID of the deck to move the card to"),
|
|
465
|
+
templateId: z.string().optional().describe("Template ID to use for the card"),
|
|
466
|
+
fields: z
|
|
467
|
+
.record(z.string(), CreateCardFieldSchema)
|
|
468
|
+
.optional()
|
|
469
|
+
.describe("Updated map of field IDs to field values"),
|
|
470
|
+
trashed: z
|
|
471
|
+
.boolean()
|
|
472
|
+
.optional()
|
|
473
|
+
.describe("Set to true to soft-delete (move to trash). This can be undone by setting to false."),
|
|
474
|
+
});
|
|
475
|
+
// Schema for delete flashcard tool
|
|
476
|
+
const DeleteFlashcardToolSchema = z.object({
|
|
477
|
+
cardId: z
|
|
478
|
+
.string()
|
|
479
|
+
.describe("ID of the card to permanently delete. This cannot be undone."),
|
|
480
|
+
});
|
|
481
|
+
// Schema for archive flashcard tool
|
|
482
|
+
const ArchiveFlashcardToolSchema = z.object({
|
|
483
|
+
cardId: z.string().describe("ID of the card to archive"),
|
|
484
|
+
archived: z
|
|
485
|
+
.boolean()
|
|
486
|
+
.default(true)
|
|
487
|
+
.describe("Set to true to archive, false to unarchive"),
|
|
430
488
|
});
|
|
431
489
|
// Output schema for attachment response
|
|
432
490
|
const AddAttachmentResponseSchema = z.object({
|
|
@@ -436,7 +494,7 @@ const AddAttachmentResponseSchema = z.object({
|
|
|
436
494
|
.describe("Markdown reference to use in card content, e.g. "),
|
|
437
495
|
});
|
|
438
496
|
// Create Mochi client
|
|
439
|
-
const mochiClient = new MochiClient(
|
|
497
|
+
const mochiClient = new MochiClient(API_KEY);
|
|
440
498
|
// Helper to format errors for tool responses
|
|
441
499
|
function formatToolError(error) {
|
|
442
500
|
if (error instanceof z.ZodError) {
|
|
@@ -480,9 +538,9 @@ function formatToolError(error) {
|
|
|
480
538
|
}
|
|
481
539
|
// Register tools
|
|
482
540
|
// Note: Using type assertions due to Zod version compatibility between SDK (v4) and project (v3)
|
|
483
|
-
server.registerTool("
|
|
541
|
+
server.registerTool("create_flashcard", {
|
|
484
542
|
title: "Create flashcard on Mochi",
|
|
485
|
-
description: "Create a new flashcard in Mochi. Look up deckId with
|
|
543
|
+
description: "Create a new flashcard in Mochi. Look up deckId with list_decks first. Use create_card_from_template if the deck has a template-id defined.",
|
|
486
544
|
inputSchema: CreateCardRequestSchema,
|
|
487
545
|
outputSchema: CreateCardResponseSchema,
|
|
488
546
|
annotations: {
|
|
@@ -503,9 +561,9 @@ server.registerTool("mochi_create_flashcard", {
|
|
|
503
561
|
return formatToolError(error);
|
|
504
562
|
}
|
|
505
563
|
});
|
|
506
|
-
server.registerTool("
|
|
564
|
+
server.registerTool("create_card_from_template", {
|
|
507
565
|
title: "Create flashcard from template on Mochi",
|
|
508
|
-
description: "Create a flashcard using a template with field names (not IDs).
|
|
566
|
+
description: "Create a flashcard using a template with field names (not IDs). Automatically maps field names to IDs. Get templates with list_templates, decks with list_decks.",
|
|
509
567
|
inputSchema: CreateCardFromTemplateSchema,
|
|
510
568
|
outputSchema: CreateCardResponseSchema,
|
|
511
569
|
annotations: {
|
|
@@ -526,9 +584,9 @@ server.registerTool("mochi_create_card_from_template", {
|
|
|
526
584
|
return formatToolError(error);
|
|
527
585
|
}
|
|
528
586
|
});
|
|
529
|
-
server.registerTool("
|
|
587
|
+
server.registerTool("update_flashcard", {
|
|
530
588
|
title: "Update flashcard on Mochi",
|
|
531
|
-
description: "Update
|
|
589
|
+
description: "Update an existing flashcard's content, deck, template, or fields. Use delete_flashcard to delete or archive_flashcard to archive.",
|
|
532
590
|
inputSchema: UpdateFlashcardToolSchema,
|
|
533
591
|
outputSchema: UpdateCardResponseSchema,
|
|
534
592
|
annotations: {
|
|
@@ -550,7 +608,63 @@ server.registerTool("mochi_update_flashcard", {
|
|
|
550
608
|
return formatToolError(error);
|
|
551
609
|
}
|
|
552
610
|
});
|
|
553
|
-
|
|
611
|
+
// Output schema for delete response
|
|
612
|
+
const DeleteFlashcardResponseSchema = z
|
|
613
|
+
.object({
|
|
614
|
+
success: z.boolean().describe("Whether the deletion was successful"),
|
|
615
|
+
cardId: z.string().describe("ID of the deleted card"),
|
|
616
|
+
})
|
|
617
|
+
.strict();
|
|
618
|
+
server.registerTool("delete_flashcard", {
|
|
619
|
+
title: "Delete flashcard on Mochi",
|
|
620
|
+
description: "Permanently delete a flashcard and its attachments. WARNING: This cannot be undone. For soft deletion, use update_flashcard with trashed: true.",
|
|
621
|
+
inputSchema: DeleteFlashcardToolSchema,
|
|
622
|
+
outputSchema: DeleteFlashcardResponseSchema,
|
|
623
|
+
annotations: {
|
|
624
|
+
readOnlyHint: false,
|
|
625
|
+
destructiveHint: true,
|
|
626
|
+
idempotentHint: true,
|
|
627
|
+
openWorldHint: true,
|
|
628
|
+
},
|
|
629
|
+
}, async (args) => {
|
|
630
|
+
try {
|
|
631
|
+
await mochiClient.deleteCard(args.cardId);
|
|
632
|
+
const response = { success: true, cardId: args.cardId };
|
|
633
|
+
return {
|
|
634
|
+
content: [{ type: "text", text: JSON.stringify(response, null, 2) }],
|
|
635
|
+
structuredContent: response,
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
catch (error) {
|
|
639
|
+
return formatToolError(error);
|
|
640
|
+
}
|
|
641
|
+
});
|
|
642
|
+
server.registerTool("archive_flashcard", {
|
|
643
|
+
title: "Archive flashcard on Mochi",
|
|
644
|
+
description: "Archive or unarchive a flashcard. Archived cards are hidden from review but not deleted.",
|
|
645
|
+
inputSchema: ArchiveFlashcardToolSchema,
|
|
646
|
+
outputSchema: UpdateCardResponseSchema,
|
|
647
|
+
annotations: {
|
|
648
|
+
readOnlyHint: false,
|
|
649
|
+
destructiveHint: false,
|
|
650
|
+
idempotentHint: true,
|
|
651
|
+
openWorldHint: true,
|
|
652
|
+
},
|
|
653
|
+
}, async (args) => {
|
|
654
|
+
try {
|
|
655
|
+
const response = await mochiClient.updateCard(args.cardId, {
|
|
656
|
+
archived: args.archived,
|
|
657
|
+
});
|
|
658
|
+
return {
|
|
659
|
+
content: [{ type: "text", text: JSON.stringify(response, null, 2) }],
|
|
660
|
+
structuredContent: response,
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
catch (error) {
|
|
664
|
+
return formatToolError(error);
|
|
665
|
+
}
|
|
666
|
+
});
|
|
667
|
+
server.registerTool("add_attachment", {
|
|
554
668
|
title: "Add attachment to flashcard on Mochi",
|
|
555
669
|
description: "Add an attachment (image, audio, etc.) to a card using base64 data. For URL-based images, just use markdown directly in card content instead.",
|
|
556
670
|
inputSchema: AddAttachmentSchema,
|
|
@@ -573,7 +687,7 @@ server.registerTool("mochi_add_attachment", {
|
|
|
573
687
|
return formatToolError(error);
|
|
574
688
|
}
|
|
575
689
|
});
|
|
576
|
-
server.registerTool("
|
|
690
|
+
server.registerTool("list_flashcards", {
|
|
577
691
|
title: "List flashcards on Mochi",
|
|
578
692
|
description: "List flashcards, optionally filtered by deck. Returns paginated results.",
|
|
579
693
|
inputSchema: ListCardsParamsSchema.shape,
|
|
@@ -596,7 +710,7 @@ server.registerTool("mochi_list_flashcards", {
|
|
|
596
710
|
return formatToolError(error);
|
|
597
711
|
}
|
|
598
712
|
});
|
|
599
|
-
server.registerTool("
|
|
713
|
+
server.registerTool("list_decks", {
|
|
600
714
|
title: "List decks on Mochi",
|
|
601
715
|
description: "List all decks. Use to get deckId for other operations.",
|
|
602
716
|
inputSchema: ListDecksParamsSchema.shape,
|
|
@@ -612,16 +726,16 @@ server.registerTool("mochi_list_decks", {
|
|
|
612
726
|
const response = await mochiClient.listDecks(args);
|
|
613
727
|
return {
|
|
614
728
|
content: [{ type: "text", text: JSON.stringify(response, null, 2) }],
|
|
615
|
-
structuredContent:
|
|
729
|
+
structuredContent: response,
|
|
616
730
|
};
|
|
617
731
|
}
|
|
618
732
|
catch (error) {
|
|
619
733
|
return formatToolError(error);
|
|
620
734
|
}
|
|
621
735
|
});
|
|
622
|
-
server.registerTool("
|
|
736
|
+
server.registerTool("list_templates", {
|
|
623
737
|
title: "List templates on Mochi",
|
|
624
|
-
description: "List all templates. Use with
|
|
738
|
+
description: "List all templates. Use with create_card_from_template for easy template-based card creation.",
|
|
625
739
|
inputSchema: ListTemplatesParamsSchema.shape,
|
|
626
740
|
outputSchema: ListTemplatesResponseSchema,
|
|
627
741
|
annotations: {
|
|
@@ -642,7 +756,30 @@ server.registerTool("mochi_list_templates", {
|
|
|
642
756
|
return formatToolError(error);
|
|
643
757
|
}
|
|
644
758
|
});
|
|
645
|
-
server.registerTool("
|
|
759
|
+
server.registerTool("get_template", {
|
|
760
|
+
title: "Get template by ID on Mochi",
|
|
761
|
+
description: "Get a single template by its ID. Use to see template fields and structure.",
|
|
762
|
+
inputSchema: GetTemplateParamsSchema.shape,
|
|
763
|
+
outputSchema: TemplateSchema,
|
|
764
|
+
annotations: {
|
|
765
|
+
readOnlyHint: true,
|
|
766
|
+
destructiveHint: false,
|
|
767
|
+
idempotentHint: true,
|
|
768
|
+
openWorldHint: false,
|
|
769
|
+
},
|
|
770
|
+
}, async (args) => {
|
|
771
|
+
try {
|
|
772
|
+
const response = await mochiClient.getTemplate(args.templateId);
|
|
773
|
+
return {
|
|
774
|
+
content: [{ type: "text", text: JSON.stringify(response, null, 2) }],
|
|
775
|
+
structuredContent: response,
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
catch (error) {
|
|
779
|
+
return formatToolError(error);
|
|
780
|
+
}
|
|
781
|
+
});
|
|
782
|
+
server.registerTool("get_due_cards", {
|
|
646
783
|
title: "Get due flashcards on Mochi",
|
|
647
784
|
description: "Get flashcards due for review on a specific date (defaults to today).",
|
|
648
785
|
inputSchema: GetDueCardsParamsSchema.shape,
|
|
@@ -670,13 +807,13 @@ server.registerResource("decks", "mochi://decks", {
|
|
|
670
807
|
description: "List of all decks in Mochi.",
|
|
671
808
|
mimeType: "application/json",
|
|
672
809
|
}, async () => {
|
|
673
|
-
const
|
|
810
|
+
const response = await mochiClient.listDecks();
|
|
674
811
|
return {
|
|
675
812
|
contents: [
|
|
676
813
|
{
|
|
677
814
|
uri: "mochi://decks",
|
|
678
815
|
mimeType: "application/json",
|
|
679
|
-
text: JSON.stringify(
|
|
816
|
+
text: JSON.stringify(response.docs.map((deck) => ({ id: deck.id, name: deck.name })), null, 2),
|
|
680
817
|
},
|
|
681
818
|
],
|
|
682
819
|
};
|
|
@@ -716,6 +853,7 @@ server.registerPrompt("write-flashcard", {
|
|
|
716
853
|
- Utilize cloze prompts when applicable, like "This is a text with {{hidden}} part. Then don't use '---' separator.".
|
|
717
854
|
- Focus on effective retrieval practice by being concise and clear.
|
|
718
855
|
- Make it just challenging enough to reinforce specific facts.
|
|
856
|
+
- Only use create_card_from_template if the deck has a template-id defined. Otherwise use create_flashcard.
|
|
719
857
|
Input: ${input}
|
|
720
858
|
`,
|
|
721
859
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fredrika/mcp-mochi",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "MCP server for Mochi flashcard integration",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -17,7 +17,9 @@
|
|
|
17
17
|
"dev": "ts-node-esm src/index.ts",
|
|
18
18
|
"dev:inspect": "npx @modelcontextprotocol/inspector ts-node-esm src/index.ts",
|
|
19
19
|
"test": "jest",
|
|
20
|
-
"prepublishOnly": "npm run build"
|
|
20
|
+
"prepublishOnly": "npm run build",
|
|
21
|
+
"version:minor": "npm version minor",
|
|
22
|
+
"release:minor": "npm run version:minor && npm publish"
|
|
21
23
|
},
|
|
22
24
|
"keywords": [
|
|
23
25
|
"mcp",
|
|
@@ -28,6 +30,9 @@
|
|
|
28
30
|
],
|
|
29
31
|
"author": "",
|
|
30
32
|
"license": "MIT",
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
31
36
|
"dependencies": {
|
|
32
37
|
"@modelcontextprotocol/sdk": "^1.25.2",
|
|
33
38
|
"axios": "^1.6.7",
|