@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # Mochi MCP Server
2
2
 
3
- This MCP server provides integration with the Mochi flashcard system, allowing you to manage your flashcards through the Model Context Protocol.
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
- ### `mochi_create_flashcard`
71
- Create a new flashcard in Mochi.
72
- - `content`: Markdown content (separate front/back with `---`)
73
- - `deck-id`: ID of the deck (use `mochi_list_decks` to find)
74
- - `template-id`: (optional) Template to use
75
- - `fields`: (optional) Map of field IDs to values (required with template)
76
- - `manual-tags`: (optional) Array of tags
77
-
78
- ### `mochi_create_card_from_template`
79
- Create a flashcard using a template with field **names** (not IDs). The MCP automatically maps names to IDs.
80
- - `template-id`: Template ID (use `mochi_list_templates` to find)
81
- - `deck-id`: Deck ID
82
- - `fields`: Map of field names to values (e.g., `{"Front": "Question?", "Back": "Answer"}`)
83
- - `manual-tags`: (optional) Array of tags
84
-
85
- ### `mochi_update_flashcard`
86
- Update or delete a flashcard. Set `trashed?` to `true` to delete.
87
- - `card-id`: ID of the card to update
88
- - Any updatable card fields
89
-
90
- ### `mochi_add_attachment`
91
- Add an attachment (image, audio, etc.) to a card using base64 data.
92
- - `card-id`: ID of the card
93
- - `data`: Base64-encoded file data
94
- - `filename`: Filename with extension (e.g., `image.png`)
95
- - `content-type`: (optional) MIME type (inferred from filename if omitted)
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
- "deck-id": "<DECK_ID>"
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
- "template-id": "<TEMPLATE_ID>",
137
- "deck-id": "<DECK_ID>",
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>["docs"];
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 front and back using a horizontal rule (---) or use brackets for {{cloze deletion}}."),
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 mochi_list_templates."),
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 mochi_list_decks."),
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" } or { "Front": "Question?", "Back": "Answer" }'),
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 front and back of card with "---"'),
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("Whether the deck is trashed"),
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.boolean().describe("Whether the card is new (never reviewed)"),
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.MOCHI_API_KEY;
280
+ const apiKey = process.env.API_KEY;
274
281
  if (!apiKey) {
275
- console.error("MOCHI_API_KEY environment variable is not set");
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 MOCHI_API_KEY = getApiKey();
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
- return ListDecksResponseSchema.parse(response.data)
310
- .docs.filter((deck) => !deck["archived?"] && !deck["trashed?"])
311
- .sort((a, b) => a.sort - b.sort);
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
- ...UpdateCardRequestSchema.shape,
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. ![](@media/filename)"),
437
495
  });
438
496
  // Create Mochi client
439
- const mochiClient = new MochiClient(MOCHI_API_KEY);
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("mochi_create_flashcard", {
541
+ server.registerTool("create_flashcard", {
484
542
  title: "Create flashcard on Mochi",
485
- description: "Create a new flashcard in Mochi. Look up deckId with mochi_list_decks first. For template-based cards, prefer mochi_create_card_from_template.",
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("mochi_create_card_from_template", {
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). Preferred way to create template-based cards. Automatically maps field names to IDs. Get templates with mochi_list_templates, decks with mochi_list_decks.",
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("mochi_update_flashcard", {
587
+ server.registerTool("update_flashcard", {
530
588
  title: "Update flashcard on Mochi",
531
- description: "Update or delete an existing flashcard. Set trashed to true to delete.",
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
- server.registerTool("mochi_add_attachment", {
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("mochi_list_flashcards", {
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("mochi_list_decks", {
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: { bookmark: "", docs: response },
729
+ structuredContent: response,
616
730
  };
617
731
  }
618
732
  catch (error) {
619
733
  return formatToolError(error);
620
734
  }
621
735
  });
622
- server.registerTool("mochi_list_templates", {
736
+ server.registerTool("list_templates", {
623
737
  title: "List templates on Mochi",
624
- description: "List all templates. Use with mochi_create_card_from_template for easy template-based card creation.",
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("mochi_get_due_cards", {
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 decks = await mochiClient.listDecks();
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(decks.map((deck) => ({ id: deck.id, name: deck.name })), null, 2),
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.6-beta.1",
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",