@fredrika/mcp-mochi 1.0.6-beta.1 → 2.0.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 -2
- package/dist/index.js +137 -12
- package/package.json +1 -1
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
|
@@ -112,7 +112,7 @@ declare const ListCardsResponseSchema: z.ZodObject<{
|
|
|
112
112
|
}, z.core.$strip>>;
|
|
113
113
|
}, z.core.$strip>;
|
|
114
114
|
type CreateCardResponse = z.infer<typeof CreateCardResponseSchema>;
|
|
115
|
-
type ListDecksResponse = z.infer<typeof ListDecksResponseSchema
|
|
115
|
+
type ListDecksResponse = z.infer<typeof ListDecksResponseSchema>;
|
|
116
116
|
type ListCardsResponse = z.infer<typeof ListCardsResponseSchema>;
|
|
117
117
|
declare const ListDecksResponseSchema: z.ZodObject<{
|
|
118
118
|
bookmark: z.ZodString;
|
|
@@ -120,6 +120,7 @@ declare const ListDecksResponseSchema: z.ZodObject<{
|
|
|
120
120
|
id: z.ZodString;
|
|
121
121
|
sort: z.ZodNumber;
|
|
122
122
|
name: z.ZodString;
|
|
123
|
+
"template-id": z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
123
124
|
"archived?": z.ZodNullable<z.ZodOptional<z.ZodBoolean>>;
|
|
124
125
|
"trashed?": z.ZodNullable<z.ZodOptional<z.ZodObject<{
|
|
125
126
|
date: z.ZodString;
|
|
@@ -132,7 +133,7 @@ declare const GetDueCardsResponseSchema: z.ZodObject<{
|
|
|
132
133
|
content: z.ZodString;
|
|
133
134
|
name: z.ZodString;
|
|
134
135
|
"deck-id": z.ZodString;
|
|
135
|
-
"new?": z.ZodBoolean
|
|
136
|
+
"new?": z.ZodOptional<z.ZodBoolean>;
|
|
136
137
|
}, z.core.$loose>>;
|
|
137
138
|
}, z.core.$strip>;
|
|
138
139
|
export declare class MochiClient {
|
|
@@ -147,6 +148,7 @@ export declare class MochiClient {
|
|
|
147
148
|
getDueCards(params?: GetDueCardsParams): Promise<z.infer<typeof GetDueCardsResponseSchema>>;
|
|
148
149
|
getTemplate(templateId: string): Promise<z.infer<typeof TemplateSchema>>;
|
|
149
150
|
createCardFromTemplate(request: CreateCardFromTemplateParams): Promise<CreateCardResponse>;
|
|
151
|
+
deleteCard(cardId: string): Promise<void>;
|
|
150
152
|
addAttachment(request: AddAttachmentRequest): Promise<{
|
|
151
153
|
filename: string;
|
|
152
154
|
markdown: string;
|
package/dist/index.js
CHANGED
|
@@ -90,6 +90,9 @@ const ListTemplatesParamsSchema = z.object({
|
|
|
90
90
|
.optional()
|
|
91
91
|
.describe("Pagination bookmark for fetching next page of results"),
|
|
92
92
|
});
|
|
93
|
+
const GetTemplateParamsSchema = z.object({
|
|
94
|
+
templateId: z.string().min(1).describe("ID of the template to fetch"),
|
|
95
|
+
});
|
|
93
96
|
const GetDueCardsParamsSchema = z.object({
|
|
94
97
|
deckId: z
|
|
95
98
|
.string()
|
|
@@ -239,6 +242,11 @@ const DeckSchema = z
|
|
|
239
242
|
id: z.string().describe("Unique identifier for the deck"),
|
|
240
243
|
sort: z.number().describe("Sort order of the deck"),
|
|
241
244
|
name: z.string().describe("Display name of the deck"),
|
|
245
|
+
"template-id": z
|
|
246
|
+
.string()
|
|
247
|
+
.optional()
|
|
248
|
+
.nullable()
|
|
249
|
+
.describe("Template ID associated with this deck, if any"),
|
|
242
250
|
"archived?": z
|
|
243
251
|
.boolean()
|
|
244
252
|
.optional()
|
|
@@ -248,7 +256,7 @@ const DeckSchema = z
|
|
|
248
256
|
.object({ date: z.string() })
|
|
249
257
|
.optional()
|
|
250
258
|
.nullable()
|
|
251
|
-
.describe("
|
|
259
|
+
.describe("Timestamp when the deck was trashed, in ISO 8601 format (matching JavaScript's Date#toJSON)"),
|
|
252
260
|
})
|
|
253
261
|
.strip();
|
|
254
262
|
const ListDecksResponseSchema = z
|
|
@@ -263,7 +271,10 @@ const DueCardSchema = z
|
|
|
263
271
|
content: z.string().describe("Markdown content of the card"),
|
|
264
272
|
name: z.string().describe("Display name of the card"),
|
|
265
273
|
"deck-id": z.string().describe("ID of the deck containing the card"),
|
|
266
|
-
"new?": z
|
|
274
|
+
"new?": z
|
|
275
|
+
.boolean()
|
|
276
|
+
.optional()
|
|
277
|
+
.describe("Whether the card is new (never reviewed)"),
|
|
267
278
|
})
|
|
268
279
|
.passthrough();
|
|
269
280
|
const GetDueCardsResponseSchema = z.object({
|
|
@@ -306,9 +317,13 @@ export class MochiClient {
|
|
|
306
317
|
? ListDecksParamsSchema.parse(params)
|
|
307
318
|
: undefined;
|
|
308
319
|
const response = await this.api.get("/decks", { params: validatedParams });
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
.
|
|
320
|
+
const parsed = ListDecksResponseSchema.parse(response.data);
|
|
321
|
+
return {
|
|
322
|
+
bookmark: parsed.bookmark,
|
|
323
|
+
docs: parsed.docs
|
|
324
|
+
.filter((deck) => !deck["archived?"] && !deck["trashed?"])
|
|
325
|
+
.sort((a, b) => a.sort - b.sort),
|
|
326
|
+
};
|
|
312
327
|
}
|
|
313
328
|
async listCards(params) {
|
|
314
329
|
const validatedParams = params
|
|
@@ -377,6 +392,9 @@ export class MochiClient {
|
|
|
377
392
|
};
|
|
378
393
|
return this.createCard(createRequest);
|
|
379
394
|
}
|
|
395
|
+
async deleteCard(cardId) {
|
|
396
|
+
await this.api.delete(`/cards/${cardId}`);
|
|
397
|
+
}
|
|
380
398
|
async addAttachment(request) {
|
|
381
399
|
// Infer content-type from filename if not provided
|
|
382
400
|
let contentType = request.contentType;
|
|
@@ -426,7 +444,34 @@ const server = new McpServer({
|
|
|
426
444
|
// Schema for update flashcard tool (combines cardId with update fields)
|
|
427
445
|
const UpdateFlashcardToolSchema = z.object({
|
|
428
446
|
cardId: z.string().describe("ID of the card to update"),
|
|
429
|
-
|
|
447
|
+
content: z
|
|
448
|
+
.string()
|
|
449
|
+
.optional()
|
|
450
|
+
.describe("Updated markdown content of the card"),
|
|
451
|
+
deckId: z.string().optional().describe("ID of the deck to move the card to"),
|
|
452
|
+
templateId: z.string().optional().describe("Template ID to use for the card"),
|
|
453
|
+
fields: z
|
|
454
|
+
.record(z.string(), CreateCardFieldSchema)
|
|
455
|
+
.optional()
|
|
456
|
+
.describe("Updated map of field IDs to field values"),
|
|
457
|
+
trashed: z
|
|
458
|
+
.boolean()
|
|
459
|
+
.optional()
|
|
460
|
+
.describe("Set to true to soft-delete (move to trash). This can be undone by setting to false."),
|
|
461
|
+
});
|
|
462
|
+
// Schema for delete flashcard tool
|
|
463
|
+
const DeleteFlashcardToolSchema = z.object({
|
|
464
|
+
cardId: z
|
|
465
|
+
.string()
|
|
466
|
+
.describe("ID of the card to permanently delete. This cannot be undone."),
|
|
467
|
+
});
|
|
468
|
+
// Schema for archive flashcard tool
|
|
469
|
+
const ArchiveFlashcardToolSchema = z.object({
|
|
470
|
+
cardId: z.string().describe("ID of the card to archive"),
|
|
471
|
+
archived: z
|
|
472
|
+
.boolean()
|
|
473
|
+
.default(true)
|
|
474
|
+
.describe("Set to true to archive, false to unarchive"),
|
|
430
475
|
});
|
|
431
476
|
// Output schema for attachment response
|
|
432
477
|
const AddAttachmentResponseSchema = z.object({
|
|
@@ -482,7 +527,7 @@ function formatToolError(error) {
|
|
|
482
527
|
// Note: Using type assertions due to Zod version compatibility between SDK (v4) and project (v3)
|
|
483
528
|
server.registerTool("mochi_create_flashcard", {
|
|
484
529
|
title: "Create flashcard on Mochi",
|
|
485
|
-
description: "Create a new flashcard in Mochi. Look up deckId with mochi_list_decks first.
|
|
530
|
+
description: "Create a new flashcard in Mochi. Look up deckId with mochi_list_decks first. Use mochi_create_card_from_template if the deck has a template-id defined.",
|
|
486
531
|
inputSchema: CreateCardRequestSchema,
|
|
487
532
|
outputSchema: CreateCardResponseSchema,
|
|
488
533
|
annotations: {
|
|
@@ -505,7 +550,7 @@ server.registerTool("mochi_create_flashcard", {
|
|
|
505
550
|
});
|
|
506
551
|
server.registerTool("mochi_create_card_from_template", {
|
|
507
552
|
title: "Create flashcard from template on Mochi",
|
|
508
|
-
description: "Create a flashcard using a template with field names (not IDs).
|
|
553
|
+
description: "Create a flashcard using a template with field names (not IDs). Automatically maps field names to IDs. Get templates with mochi_list_templates, decks with mochi_list_decks.",
|
|
509
554
|
inputSchema: CreateCardFromTemplateSchema,
|
|
510
555
|
outputSchema: CreateCardResponseSchema,
|
|
511
556
|
annotations: {
|
|
@@ -528,7 +573,7 @@ server.registerTool("mochi_create_card_from_template", {
|
|
|
528
573
|
});
|
|
529
574
|
server.registerTool("mochi_update_flashcard", {
|
|
530
575
|
title: "Update flashcard on Mochi",
|
|
531
|
-
description: "Update
|
|
576
|
+
description: "Update an existing flashcard's content, deck, template, or fields. Use mochi_delete_flashcard to delete or mochi_archive_flashcard to archive.",
|
|
532
577
|
inputSchema: UpdateFlashcardToolSchema,
|
|
533
578
|
outputSchema: UpdateCardResponseSchema,
|
|
534
579
|
annotations: {
|
|
@@ -550,6 +595,62 @@ server.registerTool("mochi_update_flashcard", {
|
|
|
550
595
|
return formatToolError(error);
|
|
551
596
|
}
|
|
552
597
|
});
|
|
598
|
+
// Output schema for delete response
|
|
599
|
+
const DeleteFlashcardResponseSchema = z
|
|
600
|
+
.object({
|
|
601
|
+
success: z.boolean().describe("Whether the deletion was successful"),
|
|
602
|
+
cardId: z.string().describe("ID of the deleted card"),
|
|
603
|
+
})
|
|
604
|
+
.strict();
|
|
605
|
+
server.registerTool("mochi_delete_flashcard", {
|
|
606
|
+
title: "Delete flashcard on Mochi",
|
|
607
|
+
description: "Permanently delete a flashcard and its attachments. WARNING: This cannot be undone. For soft deletion, use mochi_update_flashcard with trashed: true.",
|
|
608
|
+
inputSchema: DeleteFlashcardToolSchema,
|
|
609
|
+
outputSchema: DeleteFlashcardResponseSchema,
|
|
610
|
+
annotations: {
|
|
611
|
+
readOnlyHint: false,
|
|
612
|
+
destructiveHint: true,
|
|
613
|
+
idempotentHint: true,
|
|
614
|
+
openWorldHint: true,
|
|
615
|
+
},
|
|
616
|
+
}, async (args) => {
|
|
617
|
+
try {
|
|
618
|
+
await mochiClient.deleteCard(args.cardId);
|
|
619
|
+
const response = { success: true, cardId: args.cardId };
|
|
620
|
+
return {
|
|
621
|
+
content: [{ type: "text", text: JSON.stringify(response, null, 2) }],
|
|
622
|
+
structuredContent: response,
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
catch (error) {
|
|
626
|
+
return formatToolError(error);
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
server.registerTool("mochi_archive_flashcard", {
|
|
630
|
+
title: "Archive flashcard on Mochi",
|
|
631
|
+
description: "Archive or unarchive a flashcard. Archived cards are hidden from review but not deleted.",
|
|
632
|
+
inputSchema: ArchiveFlashcardToolSchema,
|
|
633
|
+
outputSchema: UpdateCardResponseSchema,
|
|
634
|
+
annotations: {
|
|
635
|
+
readOnlyHint: false,
|
|
636
|
+
destructiveHint: false,
|
|
637
|
+
idempotentHint: true,
|
|
638
|
+
openWorldHint: true,
|
|
639
|
+
},
|
|
640
|
+
}, async (args) => {
|
|
641
|
+
try {
|
|
642
|
+
const response = await mochiClient.updateCard(args.cardId, {
|
|
643
|
+
archived: args.archived,
|
|
644
|
+
});
|
|
645
|
+
return {
|
|
646
|
+
content: [{ type: "text", text: JSON.stringify(response, null, 2) }],
|
|
647
|
+
structuredContent: response,
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
catch (error) {
|
|
651
|
+
return formatToolError(error);
|
|
652
|
+
}
|
|
653
|
+
});
|
|
553
654
|
server.registerTool("mochi_add_attachment", {
|
|
554
655
|
title: "Add attachment to flashcard on Mochi",
|
|
555
656
|
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.",
|
|
@@ -612,7 +713,7 @@ server.registerTool("mochi_list_decks", {
|
|
|
612
713
|
const response = await mochiClient.listDecks(args);
|
|
613
714
|
return {
|
|
614
715
|
content: [{ type: "text", text: JSON.stringify(response, null, 2) }],
|
|
615
|
-
structuredContent:
|
|
716
|
+
structuredContent: response,
|
|
616
717
|
};
|
|
617
718
|
}
|
|
618
719
|
catch (error) {
|
|
@@ -642,6 +743,29 @@ server.registerTool("mochi_list_templates", {
|
|
|
642
743
|
return formatToolError(error);
|
|
643
744
|
}
|
|
644
745
|
});
|
|
746
|
+
server.registerTool("mochi_get_template", {
|
|
747
|
+
title: "Get template by ID on Mochi",
|
|
748
|
+
description: "Get a single template by its ID. Use to see template fields and structure.",
|
|
749
|
+
inputSchema: GetTemplateParamsSchema.shape,
|
|
750
|
+
outputSchema: TemplateSchema,
|
|
751
|
+
annotations: {
|
|
752
|
+
readOnlyHint: true,
|
|
753
|
+
destructiveHint: false,
|
|
754
|
+
idempotentHint: true,
|
|
755
|
+
openWorldHint: false,
|
|
756
|
+
},
|
|
757
|
+
}, async (args) => {
|
|
758
|
+
try {
|
|
759
|
+
const response = await mochiClient.getTemplate(args.templateId);
|
|
760
|
+
return {
|
|
761
|
+
content: [{ type: "text", text: JSON.stringify(response, null, 2) }],
|
|
762
|
+
structuredContent: response,
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
catch (error) {
|
|
766
|
+
return formatToolError(error);
|
|
767
|
+
}
|
|
768
|
+
});
|
|
645
769
|
server.registerTool("mochi_get_due_cards", {
|
|
646
770
|
title: "Get due flashcards on Mochi",
|
|
647
771
|
description: "Get flashcards due for review on a specific date (defaults to today).",
|
|
@@ -670,13 +794,13 @@ server.registerResource("decks", "mochi://decks", {
|
|
|
670
794
|
description: "List of all decks in Mochi.",
|
|
671
795
|
mimeType: "application/json",
|
|
672
796
|
}, async () => {
|
|
673
|
-
const
|
|
797
|
+
const response = await mochiClient.listDecks();
|
|
674
798
|
return {
|
|
675
799
|
contents: [
|
|
676
800
|
{
|
|
677
801
|
uri: "mochi://decks",
|
|
678
802
|
mimeType: "application/json",
|
|
679
|
-
text: JSON.stringify(
|
|
803
|
+
text: JSON.stringify(response.docs.map((deck) => ({ id: deck.id, name: deck.name })), null, 2),
|
|
680
804
|
},
|
|
681
805
|
],
|
|
682
806
|
};
|
|
@@ -716,6 +840,7 @@ server.registerPrompt("write-flashcard", {
|
|
|
716
840
|
- Utilize cloze prompts when applicable, like "This is a text with {{hidden}} part. Then don't use '---' separator.".
|
|
717
841
|
- Focus on effective retrieval practice by being concise and clear.
|
|
718
842
|
- Make it just challenging enough to reinforce specific facts.
|
|
843
|
+
- Only use mochi_create_card_from_template if the deck has a template-id defined. Otherwise use mochi_create_flashcard.
|
|
719
844
|
Input: ${input}
|
|
720
845
|
`,
|
|
721
846
|
},
|