@fredrika/mcp-mochi 1.0.6-beta.0 → 1.0.6-beta.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/dist/index.d.ts +41 -391
- package/dist/index.js +345 -529
- package/package.json +3 -4
- package/dist/common/version.d.ts +0 -1
- package/dist/common/version.js +0 -3
package/dist/index.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
3
2
|
import axios from "axios";
|
|
4
3
|
import FormData from "form-data";
|
|
5
|
-
import {
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
6
|
import dotenv from "dotenv";
|
|
8
7
|
import { z } from "zod";
|
|
9
|
-
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
10
8
|
dotenv.config();
|
|
11
9
|
/**
|
|
12
10
|
* Custom error class for Mochi API errors
|
|
@@ -37,14 +35,14 @@ const CreateCardRequestSchema = z.object({
|
|
|
37
35
|
.string()
|
|
38
36
|
.min(1)
|
|
39
37
|
.describe("Markdown content of the card. Separate front and back using a horizontal rule (---) or use brackets for {{cloze deletion}}."),
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
deckId: z.string().min(1).describe("ID of the deck to create the card in"),
|
|
39
|
+
templateId: z
|
|
42
40
|
.string()
|
|
43
41
|
.optional()
|
|
44
42
|
.nullable()
|
|
45
43
|
.default(null)
|
|
46
44
|
.describe("Optional template ID to use for the card. Defaults to null if not set."),
|
|
47
|
-
|
|
45
|
+
tags: z
|
|
48
46
|
.array(z.string())
|
|
49
47
|
.optional()
|
|
50
48
|
.describe("Optional array of tags to add to the card"),
|
|
@@ -58,16 +56,10 @@ const UpdateCardRequestSchema = z.object({
|
|
|
58
56
|
.string()
|
|
59
57
|
.optional()
|
|
60
58
|
.describe("Updated markdown content of the card"),
|
|
61
|
-
"deck
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
"template-id": z
|
|
66
|
-
.string()
|
|
67
|
-
.optional()
|
|
68
|
-
.describe("Template ID to use for the card"),
|
|
69
|
-
"archived?": z.boolean().optional().describe("Whether the card is archived"),
|
|
70
|
-
"trashed?": z.string().optional().describe("Whether the card is trashed"),
|
|
59
|
+
deckId: z.string().optional().describe("ID of the deck to move the card to"),
|
|
60
|
+
templateId: z.string().optional().describe("Template ID to use for the card"),
|
|
61
|
+
archived: z.boolean().optional().describe("Whether the card is archived"),
|
|
62
|
+
trashed: z.boolean().optional().describe("Whether the card is trashed"),
|
|
71
63
|
fields: z
|
|
72
64
|
.record(z.string(), CreateCardFieldSchema)
|
|
73
65
|
.optional()
|
|
@@ -80,7 +72,7 @@ const ListDecksParamsSchema = z.object({
|
|
|
80
72
|
.describe("Pagination bookmark for fetching next page of results"),
|
|
81
73
|
});
|
|
82
74
|
const ListCardsParamsSchema = z.object({
|
|
83
|
-
|
|
75
|
+
deckId: z.string().optional().describe("Get cards from deck ID"),
|
|
84
76
|
limit: z
|
|
85
77
|
.number()
|
|
86
78
|
.min(1)
|
|
@@ -99,7 +91,7 @@ const ListTemplatesParamsSchema = z.object({
|
|
|
99
91
|
.describe("Pagination bookmark for fetching next page of results"),
|
|
100
92
|
});
|
|
101
93
|
const GetDueCardsParamsSchema = z.object({
|
|
102
|
-
|
|
94
|
+
deckId: z
|
|
103
95
|
.string()
|
|
104
96
|
.optional()
|
|
105
97
|
.describe("Optional deck ID to filter due cards by a specific deck"),
|
|
@@ -109,35 +101,71 @@ const GetDueCardsParamsSchema = z.object({
|
|
|
109
101
|
.describe("Optional ISO 8601 date to get cards due on that date. Defaults to today."),
|
|
110
102
|
});
|
|
111
103
|
const CreateCardFromTemplateSchema = z.object({
|
|
112
|
-
|
|
104
|
+
templateId: z
|
|
113
105
|
.string()
|
|
114
106
|
.min(1)
|
|
115
107
|
.describe("ID of the template to use. Get this from mochi_list_templates."),
|
|
116
|
-
|
|
108
|
+
deckId: z
|
|
117
109
|
.string()
|
|
118
110
|
.min(1)
|
|
119
111
|
.describe("ID of the deck to create the card in. Get this from mochi_list_decks."),
|
|
120
112
|
fields: z
|
|
121
113
|
.record(z.string(), z.string())
|
|
122
114
|
.describe('Map of field NAMES (not IDs) to values. E.g., { "Word": "serendipity" } or { "Front": "Question?", "Back": "Answer" }'),
|
|
123
|
-
|
|
115
|
+
tags: z
|
|
124
116
|
.array(z.string())
|
|
125
117
|
.optional()
|
|
126
118
|
.describe("Optional array of tags to add to the card"),
|
|
127
119
|
});
|
|
128
120
|
// Schema for adding attachments
|
|
129
121
|
const AddAttachmentSchema = z.object({
|
|
130
|
-
|
|
122
|
+
cardId: z.string().min(1).describe("ID of the card to attach the file to"),
|
|
131
123
|
data: z.string().min(1).describe("Base64-encoded file data"),
|
|
132
124
|
filename: z
|
|
133
125
|
.string()
|
|
134
126
|
.min(1)
|
|
135
127
|
.describe("Filename with extension (e.g., 'image.png', 'audio.mp3')"),
|
|
136
|
-
|
|
128
|
+
contentType: z
|
|
137
129
|
.string()
|
|
138
130
|
.optional()
|
|
139
131
|
.describe("MIME type of the file (e.g., 'image/png'). Can be inferred from filename if not provided."),
|
|
140
132
|
});
|
|
133
|
+
// Helper to transform camelCase params to hyphenated format for Mochi API
|
|
134
|
+
function toMochiCreateCardRequest(params) {
|
|
135
|
+
return {
|
|
136
|
+
content: params.content,
|
|
137
|
+
"deck-id": params.deckId,
|
|
138
|
+
"template-id": params.templateId,
|
|
139
|
+
"manual-tags": params.tags,
|
|
140
|
+
fields: params.fields,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function toMochiUpdateCardRequest(params) {
|
|
144
|
+
const result = {};
|
|
145
|
+
if (params.content !== undefined)
|
|
146
|
+
result.content = params.content;
|
|
147
|
+
if (params.deckId !== undefined)
|
|
148
|
+
result["deck-id"] = params.deckId;
|
|
149
|
+
if (params.templateId !== undefined)
|
|
150
|
+
result["template-id"] = params.templateId;
|
|
151
|
+
if (params.archived !== undefined)
|
|
152
|
+
result["archived?"] = params.archived;
|
|
153
|
+
if (params.trashed !== undefined)
|
|
154
|
+
result["trashed?"] = params.trashed;
|
|
155
|
+
if (params.fields !== undefined)
|
|
156
|
+
result.fields = params.fields;
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
function toMochiListCardsParams(params) {
|
|
160
|
+
const result = {};
|
|
161
|
+
if (params.deckId !== undefined)
|
|
162
|
+
result["deck-id"] = params.deckId;
|
|
163
|
+
if (params.limit !== undefined)
|
|
164
|
+
result.limit = params.limit;
|
|
165
|
+
if (params.bookmark !== undefined)
|
|
166
|
+
result.bookmark = params.bookmark;
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
141
169
|
const TemplateFieldSchema = z.object({
|
|
142
170
|
id: z.string().describe("Unique identifier for the template field"),
|
|
143
171
|
name: z.string().describe("Display name of the field"),
|
|
@@ -193,12 +221,13 @@ const CardSchema = z
|
|
|
193
221
|
name: z.string().describe("Display name of the card"),
|
|
194
222
|
"deck-id": z.string().describe("ID of the deck containing the card"),
|
|
195
223
|
fields: z
|
|
196
|
-
.record(z.unknown())
|
|
224
|
+
.record(z.string(), z.unknown())
|
|
197
225
|
.optional()
|
|
198
226
|
.describe("Map of field IDs to field values. Need to match the field IDs in the template"),
|
|
199
227
|
})
|
|
200
228
|
.strip();
|
|
201
229
|
const CreateCardResponseSchema = CardSchema.strip();
|
|
230
|
+
const UpdateCardResponseSchema = CardSchema.strip();
|
|
202
231
|
const ListCardsResponseSchema = z
|
|
203
232
|
.object({
|
|
204
233
|
bookmark: z.string().describe("Pagination bookmark for fetching next page"),
|
|
@@ -263,11 +292,13 @@ export class MochiClient {
|
|
|
263
292
|
});
|
|
264
293
|
}
|
|
265
294
|
async createCard(request) {
|
|
266
|
-
const
|
|
295
|
+
const mochiRequest = toMochiCreateCardRequest(request);
|
|
296
|
+
const response = await this.api.post("/cards", mochiRequest);
|
|
267
297
|
return CreateCardResponseSchema.parse(response.data);
|
|
268
298
|
}
|
|
269
299
|
async updateCard(cardId, request) {
|
|
270
|
-
const
|
|
300
|
+
const mochiRequest = toMochiUpdateCardRequest(request);
|
|
301
|
+
const response = await this.api.post(`/cards/${cardId}`, mochiRequest);
|
|
271
302
|
return CreateCardResponseSchema.parse(response.data);
|
|
272
303
|
}
|
|
273
304
|
async listDecks(params) {
|
|
@@ -275,13 +306,18 @@ export class MochiClient {
|
|
|
275
306
|
? ListDecksParamsSchema.parse(params)
|
|
276
307
|
: undefined;
|
|
277
308
|
const response = await this.api.get("/decks", { params: validatedParams });
|
|
278
|
-
return ListDecksResponseSchema.parse(response.data)
|
|
309
|
+
return ListDecksResponseSchema.parse(response.data)
|
|
310
|
+
.docs.filter((deck) => !deck["archived?"] && !deck["trashed?"])
|
|
311
|
+
.sort((a, b) => a.sort - b.sort);
|
|
279
312
|
}
|
|
280
313
|
async listCards(params) {
|
|
281
314
|
const validatedParams = params
|
|
282
315
|
? ListCardsParamsSchema.parse(params)
|
|
283
316
|
: undefined;
|
|
284
|
-
const
|
|
317
|
+
const mochiParams = validatedParams
|
|
318
|
+
? toMochiListCardsParams(validatedParams)
|
|
319
|
+
: undefined;
|
|
320
|
+
const response = await this.api.get("/cards", { params: mochiParams });
|
|
285
321
|
return ListCardsResponseSchema.parse(response.data);
|
|
286
322
|
}
|
|
287
323
|
async listTemplates(params) {
|
|
@@ -297,7 +333,7 @@ export class MochiClient {
|
|
|
297
333
|
const validatedParams = params
|
|
298
334
|
? GetDueCardsParamsSchema.parse(params)
|
|
299
335
|
: undefined;
|
|
300
|
-
const deckId = validatedParams?.
|
|
336
|
+
const deckId = validatedParams?.deckId;
|
|
301
337
|
const endpoint = deckId ? `/due/${deckId}` : "/due";
|
|
302
338
|
const queryParams = validatedParams?.date
|
|
303
339
|
? { date: validatedParams.date }
|
|
@@ -311,7 +347,7 @@ export class MochiClient {
|
|
|
311
347
|
}
|
|
312
348
|
async createCardFromTemplate(request) {
|
|
313
349
|
// Fetch the template to get field definitions
|
|
314
|
-
const template = await this.getTemplate(request
|
|
350
|
+
const template = await this.getTemplate(request.templateId);
|
|
315
351
|
// Map field names to IDs
|
|
316
352
|
const fieldNameToId = {};
|
|
317
353
|
for (const [fieldId, field] of Object.entries(template.fields)) {
|
|
@@ -334,16 +370,16 @@ export class MochiClient {
|
|
|
334
370
|
const content = fieldValues.join("\n---\n");
|
|
335
371
|
const createRequest = {
|
|
336
372
|
content,
|
|
337
|
-
|
|
338
|
-
|
|
373
|
+
deckId: request.deckId,
|
|
374
|
+
templateId: request.templateId,
|
|
339
375
|
fields,
|
|
340
|
-
|
|
376
|
+
tags: request.tags,
|
|
341
377
|
};
|
|
342
378
|
return this.createCard(createRequest);
|
|
343
379
|
}
|
|
344
380
|
async addAttachment(request) {
|
|
345
381
|
// Infer content-type from filename if not provided
|
|
346
|
-
let contentType = request
|
|
382
|
+
let contentType = request.contentType;
|
|
347
383
|
if (!contentType) {
|
|
348
384
|
const ext = request.filename.split(".").pop()?.toLowerCase();
|
|
349
385
|
const mimeTypes = {
|
|
@@ -370,7 +406,7 @@ export class MochiClient {
|
|
|
370
406
|
contentType,
|
|
371
407
|
});
|
|
372
408
|
// Upload attachment
|
|
373
|
-
await this.api.post(`/cards/${request
|
|
409
|
+
await this.api.post(`/cards/${request.cardId}/attachments/${encodeURIComponent(request.filename)}`, formData, {
|
|
374
410
|
headers: {
|
|
375
411
|
...formData.getHeaders(),
|
|
376
412
|
Authorization: `Basic ${Buffer.from(`${this.token}:`).toString("base64")}`,
|
|
@@ -383,529 +419,309 @@ export class MochiClient {
|
|
|
383
419
|
}
|
|
384
420
|
}
|
|
385
421
|
// Server setup
|
|
386
|
-
const server = new
|
|
422
|
+
const server = new McpServer({
|
|
387
423
|
name: "mcp-server/mochi",
|
|
388
424
|
version: "1.0.3",
|
|
389
|
-
}, {
|
|
390
|
-
capabilities: {
|
|
391
|
-
tools: {},
|
|
392
|
-
resources: {},
|
|
393
|
-
prompts: {},
|
|
394
|
-
},
|
|
395
425
|
});
|
|
396
|
-
//
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
### content (required)
|
|
409
|
-
The markdown content of the card. Separate front and back using a horizontal rule (---).
|
|
410
|
-
|
|
411
|
-
### template-id (optional)
|
|
412
|
-
When using a template, the field ids MUST match the template ones. If not using a template, omit this field. Consider using mochi_create_card_from_template instead for easier template-based card creation.
|
|
413
|
-
|
|
414
|
-
### fields (optional)
|
|
415
|
-
A map of field IDs (keyword) to field values. Only required when using a template. The field IDs must correspond to the fields defined on the template.
|
|
416
|
-
|
|
417
|
-
## Example without template
|
|
418
|
-
{
|
|
419
|
-
"content": "What is the capital of France?\n---\nParis",
|
|
420
|
-
"deck-id": "btmZUXWM"
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
## Example with template
|
|
424
|
-
{
|
|
425
|
-
"content": "New card from API. ",
|
|
426
|
-
"deck-id": "btmZUXWM",
|
|
427
|
-
"template-id": "8BtaEAXe",
|
|
428
|
-
"fields": {
|
|
429
|
-
"name": {
|
|
430
|
-
"id": "name",
|
|
431
|
-
"value": "Hello,"
|
|
432
|
-
},
|
|
433
|
-
"JNEnw1e7": {
|
|
434
|
-
"id": "JNEnw1e7",
|
|
435
|
-
"value": "World!"
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
## Properties of good flashcards:
|
|
441
|
-
- **focused:** A question or answer involving too much detail will dull your concentration and stimulate incomplete retrievals, leaving some bulbs unlit.
|
|
442
|
-
- **precise** about what they're asking for. Vague questions will elicit vague answers, which won't reliably light the bulbs you're targeting.
|
|
443
|
-
- **consistent** answers, lighting the same bulbs each time you perform the task.
|
|
444
|
-
- **tractable**: Write prompts which you can almost always answer correctly. This often means breaking the task down, or adding cues
|
|
445
|
-
- **effortful**: You shouldn't be able to trivially infer the answer.
|
|
446
|
-
`,
|
|
447
|
-
inputSchema: zodToJsonSchema(CreateCardRequestSchema),
|
|
448
|
-
annotations: {
|
|
449
|
-
title: "Create flashcard on Mochi",
|
|
450
|
-
readOnlyHint: false,
|
|
451
|
-
destructiveHint: false,
|
|
452
|
-
idempotentHint: false,
|
|
453
|
-
openWorldHint: true,
|
|
454
|
-
},
|
|
455
|
-
},
|
|
456
|
-
{
|
|
457
|
-
name: "mochi_create_card_from_template",
|
|
458
|
-
description: `Create a flashcard using a template with field names (not IDs). This is the preferred way to create template-based cards.
|
|
459
|
-
|
|
460
|
-
The MCP automatically:
|
|
461
|
-
1. Fetches the template to get field definitions
|
|
462
|
-
2. Maps provided field names to their IDs
|
|
463
|
-
3. Builds the fields object in the format Mochi expects
|
|
464
|
-
|
|
465
|
-
## Parameters
|
|
466
|
-
|
|
467
|
-
### template-id (required)
|
|
468
|
-
The ID of the template to use. Get available templates with mochi_list_templates.
|
|
469
|
-
|
|
470
|
-
### deck-id (required)
|
|
471
|
-
The ID of the deck to create the card in. Get available decks with mochi_list_decks.
|
|
472
|
-
|
|
473
|
-
### fields (required)
|
|
474
|
-
A map of field **names** (not IDs) to their values. The MCP will map names to IDs automatically.
|
|
475
|
-
|
|
476
|
-
### manual-tags (optional)
|
|
477
|
-
Array of tags to add to the card.
|
|
478
|
-
|
|
479
|
-
## Example: "Word" template (single field)
|
|
480
|
-
{
|
|
481
|
-
"template-id": "mzROLUuD",
|
|
482
|
-
"deck-id": "HGOW9dWP",
|
|
483
|
-
"fields": {
|
|
484
|
-
"Word": "serendipity"
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
## Example: "Basic Flashcard" template (front/back)
|
|
489
|
-
{
|
|
490
|
-
"template-id": "Jyv52qHg",
|
|
491
|
-
"deck-id": "jJAIs2ZZ",
|
|
492
|
-
"fields": {
|
|
493
|
-
"Front": "What is the capital of France?",
|
|
494
|
-
"Back": "Paris"
|
|
495
|
-
}
|
|
496
|
-
}`,
|
|
497
|
-
inputSchema: zodToJsonSchema(CreateCardFromTemplateSchema),
|
|
498
|
-
annotations: {
|
|
499
|
-
title: "Create flashcard from template on Mochi",
|
|
500
|
-
readOnlyHint: false,
|
|
501
|
-
destructiveHint: false,
|
|
502
|
-
idempotentHint: false,
|
|
503
|
-
openWorldHint: true,
|
|
504
|
-
},
|
|
505
|
-
},
|
|
506
|
-
{
|
|
507
|
-
name: "mochi_update_flashcard",
|
|
508
|
-
description: `Update or delete an existing flashcard in Mochi. To delete set trashed to true.`,
|
|
509
|
-
inputSchema: zodToJsonSchema(z.object({
|
|
510
|
-
"card-id": z.string(),
|
|
511
|
-
...UpdateCardRequestSchema.shape,
|
|
512
|
-
})),
|
|
513
|
-
annotations: {
|
|
514
|
-
title: "Update flashcard on Mochi",
|
|
515
|
-
readOnlyHint: false,
|
|
516
|
-
destructiveHint: false,
|
|
517
|
-
idempotentHint: false,
|
|
518
|
-
openWorldHint: true,
|
|
519
|
-
},
|
|
520
|
-
},
|
|
521
|
-
{
|
|
522
|
-
name: "mochi_add_attachment",
|
|
523
|
-
description: `Add an attachment (image, audio, etc.) to a card using base64 data.
|
|
524
|
-
|
|
525
|
-
Use this when you have base64-encoded file data (e.g., from images inserted in chat).
|
|
526
|
-
|
|
527
|
-
## Parameters
|
|
528
|
-
|
|
529
|
-
### card-id (required)
|
|
530
|
-
The ID of the card to attach the file to.
|
|
531
|
-
|
|
532
|
-
### data (required)
|
|
533
|
-
Base64-encoded file data.
|
|
534
|
-
|
|
535
|
-
### filename (required)
|
|
536
|
-
Filename with extension (e.g., "image.png", "audio.mp3").
|
|
537
|
-
|
|
538
|
-
### content-type (optional)
|
|
539
|
-
MIME type (e.g., "image/png"). Can be inferred from filename.
|
|
540
|
-
|
|
541
|
-
## Returns
|
|
542
|
-
The markdown reference to use in card content: \`\`
|
|
543
|
-
|
|
544
|
-
## Note
|
|
545
|
-
For URL-based images, just use markdown directly in card content: \`\` - no attachment upload needed.`,
|
|
546
|
-
inputSchema: zodToJsonSchema(AddAttachmentSchema),
|
|
547
|
-
annotations: {
|
|
548
|
-
title: "Add attachment to flashcard on Mochi",
|
|
549
|
-
readOnlyHint: false,
|
|
550
|
-
destructiveHint: false,
|
|
551
|
-
idempotentHint: false,
|
|
552
|
-
openWorldHint: true,
|
|
553
|
-
},
|
|
554
|
-
},
|
|
555
|
-
{
|
|
556
|
-
name: "mochi_list_flashcards",
|
|
557
|
-
description: "List flashcards in pages of 10 cards per page",
|
|
558
|
-
inputSchema: zodToJsonSchema(ListCardsParamsSchema),
|
|
559
|
-
annotations: {
|
|
560
|
-
title: "List flashcards on Mochi",
|
|
561
|
-
readOnlyHint: true,
|
|
562
|
-
destructiveHint: false,
|
|
563
|
-
idempotentHint: true,
|
|
564
|
-
openWorldHint: false,
|
|
565
|
-
},
|
|
566
|
-
},
|
|
567
|
-
{
|
|
568
|
-
name: "mochi_list_decks",
|
|
569
|
-
description: "List all decks",
|
|
570
|
-
inputSchema: zodToJsonSchema(ListDecksParamsSchema),
|
|
571
|
-
annotations: {
|
|
572
|
-
title: "List decks on Mochi",
|
|
573
|
-
readOnlyHint: true,
|
|
574
|
-
destructiveHint: false,
|
|
575
|
-
idempotentHint: true,
|
|
576
|
-
openWorldHint: false,
|
|
577
|
-
},
|
|
578
|
-
},
|
|
579
|
-
{
|
|
580
|
-
name: "mochi_list_templates",
|
|
581
|
-
description: `List all templates. Templates can be used to create cards with pre-defined fields.
|
|
582
|
-
|
|
583
|
-
Use mochi_create_card_from_template for easy template-based card creation with field names instead of IDs.
|
|
584
|
-
|
|
585
|
-
Example response:
|
|
586
|
-
{
|
|
587
|
-
"bookmark": "g1AAAABAeJzLYWBgYMpgSmHgKy5JLCrJTq2MT8lPzkzJBYpzVBn4JgaaVZiC5Dlg8igyWQAxwRHd",
|
|
588
|
-
"docs": [
|
|
589
|
-
{
|
|
590
|
-
"id": "YDELNZSu",
|
|
591
|
-
"name": "Simple flashcard",
|
|
592
|
-
"content": "# << Front >>\n---\n<< Back >>",
|
|
593
|
-
"pos": "s",
|
|
594
|
-
"fields": {
|
|
595
|
-
"name": {
|
|
596
|
-
"id": "name",
|
|
597
|
-
"name": "Front",
|
|
598
|
-
"pos": "a"
|
|
599
|
-
},
|
|
600
|
-
"Ysrde7Lj": {
|
|
601
|
-
"id": "Ysrde7Lj",
|
|
602
|
-
"name": "Back",
|
|
603
|
-
"pos": "m",
|
|
604
|
-
"options": {
|
|
605
|
-
"multi-line?": true
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
},
|
|
610
|
-
...
|
|
611
|
-
]
|
|
612
|
-
}`,
|
|
613
|
-
inputSchema: zodToJsonSchema(ListTemplatesParamsSchema),
|
|
614
|
-
annotations: {
|
|
615
|
-
title: "List templates on Mochi",
|
|
616
|
-
readOnlyHint: true,
|
|
617
|
-
destructiveHint: false,
|
|
618
|
-
idempotentHint: true,
|
|
619
|
-
openWorldHint: false,
|
|
620
|
-
},
|
|
621
|
-
},
|
|
622
|
-
{
|
|
623
|
-
name: "mochi_get_due_cards",
|
|
624
|
-
description: `Get flashcards that are due for review. Returns cards scheduled for review on the specified date (defaults to today).
|
|
625
|
-
|
|
626
|
-
## Parameters
|
|
627
|
-
|
|
628
|
-
### deck-id (optional)
|
|
629
|
-
Filter to only show due cards from a specific deck.
|
|
630
|
-
|
|
631
|
-
### date (optional)
|
|
632
|
-
ISO 8601 date string (e.g., "2026-01-11T00:00:00.000Z") to get cards due on that date. Defaults to today.`,
|
|
633
|
-
inputSchema: zodToJsonSchema(GetDueCardsParamsSchema),
|
|
634
|
-
annotations: {
|
|
635
|
-
title: "Get due flashcards on Mochi",
|
|
636
|
-
readOnlyHint: true,
|
|
637
|
-
destructiveHint: false,
|
|
638
|
-
idempotentHint: true,
|
|
639
|
-
openWorldHint: false,
|
|
640
|
-
},
|
|
641
|
-
},
|
|
642
|
-
],
|
|
643
|
-
}));
|
|
426
|
+
// Schema for update flashcard tool (combines cardId with update fields)
|
|
427
|
+
const UpdateFlashcardToolSchema = z.object({
|
|
428
|
+
cardId: z.string().describe("ID of the card to update"),
|
|
429
|
+
...UpdateCardRequestSchema.shape,
|
|
430
|
+
});
|
|
431
|
+
// Output schema for attachment response
|
|
432
|
+
const AddAttachmentResponseSchema = z.object({
|
|
433
|
+
filename: z.string().describe("The filename of the uploaded attachment"),
|
|
434
|
+
markdown: z
|
|
435
|
+
.string()
|
|
436
|
+
.describe("Markdown reference to use in card content, e.g. "),
|
|
437
|
+
});
|
|
644
438
|
// Create Mochi client
|
|
645
439
|
const mochiClient = new MochiClient(MOCHI_API_KEY);
|
|
646
|
-
//
|
|
647
|
-
|
|
440
|
+
// Helper to format errors for tool responses
|
|
441
|
+
function formatToolError(error) {
|
|
442
|
+
if (error instanceof z.ZodError) {
|
|
443
|
+
const formattedErrors = error.issues.map((issue) => {
|
|
444
|
+
const path = issue.path.join(".");
|
|
445
|
+
const message = issue.code === "invalid_type" && issue.message.includes("Required")
|
|
446
|
+
? `Required field '${path}' is missing`
|
|
447
|
+
: issue.message;
|
|
448
|
+
return `${path ? `${path}: ` : ""}${message}`;
|
|
449
|
+
});
|
|
450
|
+
return {
|
|
451
|
+
content: [
|
|
452
|
+
{
|
|
453
|
+
type: "text",
|
|
454
|
+
text: `Validation error:\n${formattedErrors.join("\n")}`,
|
|
455
|
+
},
|
|
456
|
+
],
|
|
457
|
+
isError: true,
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
if (error instanceof MochiError) {
|
|
461
|
+
return {
|
|
462
|
+
content: [
|
|
463
|
+
{
|
|
464
|
+
type: "text",
|
|
465
|
+
text: `Mochi API error (${error.statusCode}): ${error.message}`,
|
|
466
|
+
},
|
|
467
|
+
],
|
|
468
|
+
isError: true,
|
|
469
|
+
};
|
|
470
|
+
}
|
|
648
471
|
return {
|
|
649
|
-
|
|
650
|
-
{
|
|
651
|
-
uri: `mochi://decks`,
|
|
652
|
-
name: "All Mochi Decks",
|
|
653
|
-
description: `List of all decks in Mochi.`,
|
|
654
|
-
mimeType: "application/json",
|
|
655
|
-
},
|
|
472
|
+
content: [
|
|
656
473
|
{
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
description: `List of all templates in Mochi.`,
|
|
660
|
-
mimeType: "application/json",
|
|
474
|
+
type: "text",
|
|
475
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
661
476
|
},
|
|
662
477
|
],
|
|
478
|
+
isError: true,
|
|
663
479
|
};
|
|
480
|
+
}
|
|
481
|
+
// Register tools
|
|
482
|
+
// Note: Using type assertions due to Zod version compatibility between SDK (v4) and project (v3)
|
|
483
|
+
server.registerTool("mochi_create_flashcard", {
|
|
484
|
+
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.",
|
|
486
|
+
inputSchema: CreateCardRequestSchema,
|
|
487
|
+
outputSchema: CreateCardResponseSchema,
|
|
488
|
+
annotations: {
|
|
489
|
+
readOnlyHint: false,
|
|
490
|
+
destructiveHint: false,
|
|
491
|
+
idempotentHint: false,
|
|
492
|
+
openWorldHint: true,
|
|
493
|
+
},
|
|
494
|
+
}, async (args) => {
|
|
495
|
+
try {
|
|
496
|
+
const response = await mochiClient.createCard(args);
|
|
497
|
+
return {
|
|
498
|
+
content: [{ type: "text", text: JSON.stringify(response, null, 2) }],
|
|
499
|
+
structuredContent: response,
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
catch (error) {
|
|
503
|
+
return formatToolError(error);
|
|
504
|
+
}
|
|
664
505
|
});
|
|
665
|
-
server.
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
return {
|
|
687
|
-
contents: [
|
|
688
|
-
{
|
|
689
|
-
uri,
|
|
690
|
-
mimeType: "application/json",
|
|
691
|
-
text: JSON.stringify(templates, null, 2),
|
|
692
|
-
},
|
|
693
|
-
],
|
|
694
|
-
};
|
|
695
|
-
}
|
|
696
|
-
default: {
|
|
697
|
-
throw new Error("Invalid resource URI");
|
|
698
|
-
}
|
|
506
|
+
server.registerTool("mochi_create_card_from_template", {
|
|
507
|
+
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.",
|
|
509
|
+
inputSchema: CreateCardFromTemplateSchema,
|
|
510
|
+
outputSchema: CreateCardResponseSchema,
|
|
511
|
+
annotations: {
|
|
512
|
+
readOnlyHint: false,
|
|
513
|
+
destructiveHint: false,
|
|
514
|
+
idempotentHint: false,
|
|
515
|
+
openWorldHint: true,
|
|
516
|
+
},
|
|
517
|
+
}, async (args) => {
|
|
518
|
+
try {
|
|
519
|
+
const response = await mochiClient.createCardFromTemplate(args);
|
|
520
|
+
return {
|
|
521
|
+
content: [{ type: "text", text: JSON.stringify(response, null, 2) }],
|
|
522
|
+
structuredContent: response,
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
catch (error) {
|
|
526
|
+
return formatToolError(error);
|
|
699
527
|
}
|
|
700
528
|
});
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
529
|
+
server.registerTool("mochi_update_flashcard", {
|
|
530
|
+
title: "Update flashcard on Mochi",
|
|
531
|
+
description: "Update or delete an existing flashcard. Set trashed to true to delete.",
|
|
532
|
+
inputSchema: UpdateFlashcardToolSchema,
|
|
533
|
+
outputSchema: UpdateCardResponseSchema,
|
|
534
|
+
annotations: {
|
|
535
|
+
readOnlyHint: false,
|
|
536
|
+
destructiveHint: false,
|
|
537
|
+
idempotentHint: false,
|
|
538
|
+
openWorldHint: true,
|
|
539
|
+
},
|
|
540
|
+
}, async (args) => {
|
|
541
|
+
try {
|
|
542
|
+
const { cardId, ...updateArgs } = args;
|
|
543
|
+
const response = await mochiClient.updateCard(cardId, updateArgs);
|
|
544
|
+
return {
|
|
545
|
+
content: [{ type: "text", text: JSON.stringify(response, null, 2) }],
|
|
546
|
+
structuredContent: response,
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
catch (error) {
|
|
550
|
+
return formatToolError(error);
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
server.registerTool("mochi_add_attachment", {
|
|
554
|
+
title: "Add attachment to flashcard on Mochi",
|
|
555
|
+
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
|
+
inputSchema: AddAttachmentSchema,
|
|
557
|
+
outputSchema: AddAttachmentResponseSchema,
|
|
558
|
+
annotations: {
|
|
559
|
+
readOnlyHint: false,
|
|
560
|
+
destructiveHint: false,
|
|
561
|
+
idempotentHint: false,
|
|
562
|
+
openWorldHint: true,
|
|
563
|
+
},
|
|
564
|
+
}, async (args) => {
|
|
565
|
+
try {
|
|
566
|
+
const response = await mochiClient.addAttachment(args);
|
|
567
|
+
return {
|
|
568
|
+
content: [{ type: "text", text: JSON.stringify(response, null, 2) }],
|
|
569
|
+
structuredContent: response,
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
catch (error) {
|
|
573
|
+
return formatToolError(error);
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
server.registerTool("mochi_list_flashcards", {
|
|
577
|
+
title: "List flashcards on Mochi",
|
|
578
|
+
description: "List flashcards, optionally filtered by deck. Returns paginated results.",
|
|
579
|
+
inputSchema: ListCardsParamsSchema.shape,
|
|
580
|
+
outputSchema: ListCardsResponseSchema,
|
|
581
|
+
annotations: {
|
|
582
|
+
readOnlyHint: true,
|
|
583
|
+
destructiveHint: false,
|
|
584
|
+
idempotentHint: true,
|
|
585
|
+
openWorldHint: false,
|
|
586
|
+
},
|
|
587
|
+
}, async (args) => {
|
|
588
|
+
try {
|
|
589
|
+
const response = await mochiClient.listCards(args);
|
|
590
|
+
return {
|
|
591
|
+
content: [{ type: "text", text: JSON.stringify(response, null, 2) }],
|
|
592
|
+
structuredContent: response,
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
catch (error) {
|
|
596
|
+
return formatToolError(error);
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
server.registerTool("mochi_list_decks", {
|
|
600
|
+
title: "List decks on Mochi",
|
|
601
|
+
description: "List all decks. Use to get deckId for other operations.",
|
|
602
|
+
inputSchema: ListDecksParamsSchema.shape,
|
|
603
|
+
outputSchema: ListDecksResponseSchema,
|
|
604
|
+
annotations: {
|
|
605
|
+
readOnlyHint: true,
|
|
606
|
+
destructiveHint: false,
|
|
607
|
+
idempotentHint: true,
|
|
608
|
+
openWorldHint: false,
|
|
609
|
+
},
|
|
610
|
+
}, async (args) => {
|
|
611
|
+
try {
|
|
612
|
+
const response = await mochiClient.listDecks(args);
|
|
613
|
+
return {
|
|
614
|
+
content: [{ type: "text", text: JSON.stringify(response, null, 2) }],
|
|
615
|
+
structuredContent: { bookmark: "", docs: response },
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
catch (error) {
|
|
619
|
+
return formatToolError(error);
|
|
620
|
+
}
|
|
706
621
|
});
|
|
707
|
-
server.
|
|
622
|
+
server.registerTool("mochi_list_templates", {
|
|
623
|
+
title: "List templates on Mochi",
|
|
624
|
+
description: "List all templates. Use with mochi_create_card_from_template for easy template-based card creation.",
|
|
625
|
+
inputSchema: ListTemplatesParamsSchema.shape,
|
|
626
|
+
outputSchema: ListTemplatesResponseSchema,
|
|
627
|
+
annotations: {
|
|
628
|
+
readOnlyHint: true,
|
|
629
|
+
destructiveHint: false,
|
|
630
|
+
idempotentHint: true,
|
|
631
|
+
openWorldHint: false,
|
|
632
|
+
},
|
|
633
|
+
}, async (args) => {
|
|
634
|
+
try {
|
|
635
|
+
const response = await mochiClient.listTemplates(args);
|
|
636
|
+
return {
|
|
637
|
+
content: [{ type: "text", text: JSON.stringify(response, null, 2) }],
|
|
638
|
+
structuredContent: response,
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
catch (error) {
|
|
642
|
+
return formatToolError(error);
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
server.registerTool("mochi_get_due_cards", {
|
|
646
|
+
title: "Get due flashcards on Mochi",
|
|
647
|
+
description: "Get flashcards due for review on a specific date (defaults to today).",
|
|
648
|
+
inputSchema: GetDueCardsParamsSchema.shape,
|
|
649
|
+
outputSchema: GetDueCardsResponseSchema,
|
|
650
|
+
annotations: {
|
|
651
|
+
readOnlyHint: true,
|
|
652
|
+
destructiveHint: false,
|
|
653
|
+
idempotentHint: true,
|
|
654
|
+
openWorldHint: false,
|
|
655
|
+
},
|
|
656
|
+
}, async (args) => {
|
|
657
|
+
try {
|
|
658
|
+
const response = await mochiClient.getDueCards(args);
|
|
659
|
+
return {
|
|
660
|
+
content: [{ type: "text", text: JSON.stringify(response, null, 2) }],
|
|
661
|
+
structuredContent: response,
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
catch (error) {
|
|
665
|
+
return formatToolError(error);
|
|
666
|
+
}
|
|
667
|
+
});
|
|
668
|
+
// Register resources
|
|
669
|
+
server.registerResource("decks", "mochi://decks", {
|
|
670
|
+
description: "List of all decks in Mochi.",
|
|
671
|
+
mimeType: "application/json",
|
|
672
|
+
}, async () => {
|
|
673
|
+
const decks = await mochiClient.listDecks();
|
|
708
674
|
return {
|
|
709
|
-
|
|
675
|
+
contents: [
|
|
710
676
|
{
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
{
|
|
715
|
-
name: "input",
|
|
716
|
-
description: "The information to base the flashcard on.",
|
|
717
|
-
},
|
|
718
|
-
],
|
|
677
|
+
uri: "mochi://decks",
|
|
678
|
+
mimeType: "application/json",
|
|
679
|
+
text: JSON.stringify(decks.map((deck) => ({ id: deck.id, name: deck.name })), null, 2),
|
|
719
680
|
},
|
|
720
681
|
],
|
|
721
682
|
};
|
|
722
683
|
});
|
|
723
|
-
server.
|
|
724
|
-
|
|
725
|
-
|
|
684
|
+
server.registerResource("templates", "mochi://templates", {
|
|
685
|
+
description: "List of all templates in Mochi.",
|
|
686
|
+
mimeType: "application/json",
|
|
687
|
+
}, async () => {
|
|
688
|
+
const templates = await mochiClient.listTemplates();
|
|
726
689
|
return {
|
|
727
|
-
|
|
690
|
+
contents: [
|
|
728
691
|
{
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
692
|
+
uri: "mochi://templates",
|
|
693
|
+
mimeType: "application/json",
|
|
694
|
+
text: JSON.stringify(templates, null, 2),
|
|
695
|
+
},
|
|
696
|
+
],
|
|
697
|
+
};
|
|
698
|
+
});
|
|
699
|
+
// Register prompts
|
|
700
|
+
server.registerPrompt("write-flashcard", {
|
|
701
|
+
description: "Write a flashcard based on user-provided information.",
|
|
702
|
+
argsSchema: {
|
|
703
|
+
input: z
|
|
704
|
+
.string()
|
|
705
|
+
.describe("The information to base the flashcard on.")
|
|
706
|
+
.optional(),
|
|
707
|
+
},
|
|
708
|
+
}, async ({ input }) => ({
|
|
709
|
+
messages: [
|
|
710
|
+
{
|
|
711
|
+
role: "user",
|
|
712
|
+
content: {
|
|
713
|
+
type: "text",
|
|
714
|
+
text: `Create a flashcard using the info below while adhering to these principles:
|
|
733
715
|
- Keep questions and answers atomic.
|
|
734
716
|
- Utilize cloze prompts when applicable, like "This is a text with {{hidden}} part. Then don't use '---' separator.".
|
|
735
717
|
- Focus on effective retrieval practice by being concise and clear.
|
|
736
718
|
- Make it just challenging enough to reinforce specific facts.
|
|
737
719
|
Input: ${input}
|
|
738
720
|
`,
|
|
739
|
-
},
|
|
740
721
|
},
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
});
|
|
744
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
745
|
-
try {
|
|
746
|
-
switch (request.params.name) {
|
|
747
|
-
case "mochi_create_flashcard": {
|
|
748
|
-
const validatedArgs = CreateCardRequestSchema.parse(request.params.arguments);
|
|
749
|
-
const response = await mochiClient.createCard(validatedArgs);
|
|
750
|
-
return {
|
|
751
|
-
content: [
|
|
752
|
-
{
|
|
753
|
-
type: "text",
|
|
754
|
-
text: JSON.stringify(response, null, 2),
|
|
755
|
-
},
|
|
756
|
-
],
|
|
757
|
-
isError: false,
|
|
758
|
-
};
|
|
759
|
-
}
|
|
760
|
-
case "mochi_create_card_from_template": {
|
|
761
|
-
const validatedArgs = CreateCardFromTemplateSchema.parse(request.params.arguments);
|
|
762
|
-
const response = await mochiClient.createCardFromTemplate(validatedArgs);
|
|
763
|
-
return {
|
|
764
|
-
content: [
|
|
765
|
-
{
|
|
766
|
-
type: "text",
|
|
767
|
-
text: JSON.stringify(response, null, 2),
|
|
768
|
-
},
|
|
769
|
-
],
|
|
770
|
-
isError: false,
|
|
771
|
-
};
|
|
772
|
-
}
|
|
773
|
-
case "mochi_update_flashcard": {
|
|
774
|
-
const { "card-id": cardId, ...updateArgs } = z
|
|
775
|
-
.object({
|
|
776
|
-
"card-id": z.string(),
|
|
777
|
-
...UpdateCardRequestSchema.shape,
|
|
778
|
-
})
|
|
779
|
-
.parse(request.params.arguments);
|
|
780
|
-
const response = await mochiClient.updateCard(cardId, updateArgs);
|
|
781
|
-
return {
|
|
782
|
-
content: [
|
|
783
|
-
{
|
|
784
|
-
type: "text",
|
|
785
|
-
text: JSON.stringify(response, null, 2),
|
|
786
|
-
},
|
|
787
|
-
],
|
|
788
|
-
isError: false,
|
|
789
|
-
};
|
|
790
|
-
}
|
|
791
|
-
case "mochi_add_attachment": {
|
|
792
|
-
const validatedArgs = AddAttachmentSchema.parse(request.params.arguments);
|
|
793
|
-
const response = await mochiClient.addAttachment(validatedArgs);
|
|
794
|
-
return {
|
|
795
|
-
content: [
|
|
796
|
-
{
|
|
797
|
-
type: "text",
|
|
798
|
-
text: JSON.stringify(response, null, 2),
|
|
799
|
-
},
|
|
800
|
-
],
|
|
801
|
-
isError: false,
|
|
802
|
-
};
|
|
803
|
-
}
|
|
804
|
-
case "mochi_list_decks": {
|
|
805
|
-
const validatedArgs = ListDecksParamsSchema.parse(request.params.arguments);
|
|
806
|
-
const response = await mochiClient.listDecks(validatedArgs);
|
|
807
|
-
return {
|
|
808
|
-
content: [
|
|
809
|
-
{
|
|
810
|
-
type: "text",
|
|
811
|
-
text: JSON.stringify(response, null, 2),
|
|
812
|
-
},
|
|
813
|
-
],
|
|
814
|
-
isError: false,
|
|
815
|
-
};
|
|
816
|
-
}
|
|
817
|
-
case "mochi_list_flashcards": {
|
|
818
|
-
const validatedArgs = ListCardsParamsSchema.parse(request.params.arguments);
|
|
819
|
-
const response = await mochiClient.listCards(validatedArgs);
|
|
820
|
-
return {
|
|
821
|
-
content: [
|
|
822
|
-
{
|
|
823
|
-
type: "text",
|
|
824
|
-
text: JSON.stringify(response, null, 2),
|
|
825
|
-
},
|
|
826
|
-
],
|
|
827
|
-
isError: false,
|
|
828
|
-
};
|
|
829
|
-
}
|
|
830
|
-
case "mochi_list_templates": {
|
|
831
|
-
const validatedArgs = ListTemplatesParamsSchema.parse(request.params.arguments);
|
|
832
|
-
const response = await mochiClient.listTemplates(validatedArgs);
|
|
833
|
-
return {
|
|
834
|
-
content: [
|
|
835
|
-
{
|
|
836
|
-
type: "text",
|
|
837
|
-
text: JSON.stringify(response, null, 2),
|
|
838
|
-
},
|
|
839
|
-
],
|
|
840
|
-
isError: false,
|
|
841
|
-
};
|
|
842
|
-
}
|
|
843
|
-
case "mochi_get_due_cards": {
|
|
844
|
-
const validatedArgs = GetDueCardsParamsSchema.parse(request.params.arguments);
|
|
845
|
-
const response = await mochiClient.getDueCards(validatedArgs);
|
|
846
|
-
return {
|
|
847
|
-
content: [
|
|
848
|
-
{
|
|
849
|
-
type: "text",
|
|
850
|
-
text: JSON.stringify(response, null, 2),
|
|
851
|
-
},
|
|
852
|
-
],
|
|
853
|
-
isError: false,
|
|
854
|
-
};
|
|
855
|
-
}
|
|
856
|
-
default:
|
|
857
|
-
return {
|
|
858
|
-
content: [
|
|
859
|
-
{
|
|
860
|
-
type: "text",
|
|
861
|
-
text: `Unknown tool: ${request.params.name}`,
|
|
862
|
-
},
|
|
863
|
-
],
|
|
864
|
-
isError: true,
|
|
865
|
-
};
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
catch (error) {
|
|
869
|
-
if (error instanceof z.ZodError) {
|
|
870
|
-
const formattedErrors = error.errors.map((err) => {
|
|
871
|
-
const path = err.path.join(".");
|
|
872
|
-
const message = err.code === "invalid_type" && err.message.includes("Required")
|
|
873
|
-
? `Required field '${path}' is missing`
|
|
874
|
-
: err.message;
|
|
875
|
-
return `${path ? `${path}: ` : ""}${message}`;
|
|
876
|
-
});
|
|
877
|
-
return {
|
|
878
|
-
content: [
|
|
879
|
-
{
|
|
880
|
-
type: "text",
|
|
881
|
-
text: `Validation error:\n${formattedErrors.join("\n")}`,
|
|
882
|
-
},
|
|
883
|
-
],
|
|
884
|
-
isError: true,
|
|
885
|
-
};
|
|
886
|
-
}
|
|
887
|
-
if (error instanceof MochiError) {
|
|
888
|
-
return {
|
|
889
|
-
content: [
|
|
890
|
-
{
|
|
891
|
-
type: "text",
|
|
892
|
-
text: `Mochi API error (${error.statusCode}): ${error.message}`,
|
|
893
|
-
},
|
|
894
|
-
],
|
|
895
|
-
isError: true,
|
|
896
|
-
};
|
|
897
|
-
}
|
|
898
|
-
return {
|
|
899
|
-
content: [
|
|
900
|
-
{
|
|
901
|
-
type: "text",
|
|
902
|
-
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
903
|
-
},
|
|
904
|
-
],
|
|
905
|
-
isError: true,
|
|
906
|
-
};
|
|
907
|
-
}
|
|
908
|
-
});
|
|
722
|
+
},
|
|
723
|
+
],
|
|
724
|
+
}));
|
|
909
725
|
async function runServer() {
|
|
910
726
|
const transport = new StdioServerTransport();
|
|
911
727
|
await server.connect(transport);
|