@fredrika/mcp-mochi 1.0.5 → 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/README.md +112 -89
- package/dist/index.d.ts +83 -243
- package/dist/index.js +494 -372
- package/package.json +3 -3
- package/dist/common/version.d.ts +0 -1
- package/dist/common/version.js +0 -3
package/dist/index.js
CHANGED
|
@@ -1,11 +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
|
-
import
|
|
3
|
+
import FormData from "form-data";
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
6
|
import dotenv from "dotenv";
|
|
7
7
|
import { z } from "zod";
|
|
8
|
-
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
9
8
|
dotenv.config();
|
|
10
9
|
/**
|
|
11
10
|
* Custom error class for Mochi API errors
|
|
@@ -36,14 +35,14 @@ const CreateCardRequestSchema = z.object({
|
|
|
36
35
|
.string()
|
|
37
36
|
.min(1)
|
|
38
37
|
.describe("Markdown content of the card. Separate front and back using a horizontal rule (---) or use brackets for {{cloze deletion}}."),
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
deckId: z.string().min(1).describe("ID of the deck to create the card in"),
|
|
39
|
+
templateId: z
|
|
41
40
|
.string()
|
|
42
41
|
.optional()
|
|
43
42
|
.nullable()
|
|
44
43
|
.default(null)
|
|
45
44
|
.describe("Optional template ID to use for the card. Defaults to null if not set."),
|
|
46
|
-
|
|
45
|
+
tags: z
|
|
47
46
|
.array(z.string())
|
|
48
47
|
.optional()
|
|
49
48
|
.describe("Optional array of tags to add to the card"),
|
|
@@ -57,16 +56,10 @@ const UpdateCardRequestSchema = z.object({
|
|
|
57
56
|
.string()
|
|
58
57
|
.optional()
|
|
59
58
|
.describe("Updated markdown content of the card"),
|
|
60
|
-
"deck
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
"template-id": z
|
|
65
|
-
.string()
|
|
66
|
-
.optional()
|
|
67
|
-
.describe("Template ID to use for the card"),
|
|
68
|
-
"archived?": z.boolean().optional().describe("Whether the card is archived"),
|
|
69
|
-
"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"),
|
|
70
63
|
fields: z
|
|
71
64
|
.record(z.string(), CreateCardFieldSchema)
|
|
72
65
|
.optional()
|
|
@@ -79,7 +72,7 @@ const ListDecksParamsSchema = z.object({
|
|
|
79
72
|
.describe("Pagination bookmark for fetching next page of results"),
|
|
80
73
|
});
|
|
81
74
|
const ListCardsParamsSchema = z.object({
|
|
82
|
-
|
|
75
|
+
deckId: z.string().optional().describe("Get cards from deck ID"),
|
|
83
76
|
limit: z
|
|
84
77
|
.number()
|
|
85
78
|
.min(1)
|
|
@@ -97,10 +90,96 @@ const ListTemplatesParamsSchema = z.object({
|
|
|
97
90
|
.optional()
|
|
98
91
|
.describe("Pagination bookmark for fetching next page of results"),
|
|
99
92
|
});
|
|
93
|
+
const GetDueCardsParamsSchema = z.object({
|
|
94
|
+
deckId: z
|
|
95
|
+
.string()
|
|
96
|
+
.optional()
|
|
97
|
+
.describe("Optional deck ID to filter due cards by a specific deck"),
|
|
98
|
+
date: z
|
|
99
|
+
.string()
|
|
100
|
+
.optional()
|
|
101
|
+
.describe("Optional ISO 8601 date to get cards due on that date. Defaults to today."),
|
|
102
|
+
});
|
|
103
|
+
const CreateCardFromTemplateSchema = z.object({
|
|
104
|
+
templateId: z
|
|
105
|
+
.string()
|
|
106
|
+
.min(1)
|
|
107
|
+
.describe("ID of the template to use. Get this from mochi_list_templates."),
|
|
108
|
+
deckId: z
|
|
109
|
+
.string()
|
|
110
|
+
.min(1)
|
|
111
|
+
.describe("ID of the deck to create the card in. Get this from mochi_list_decks."),
|
|
112
|
+
fields: z
|
|
113
|
+
.record(z.string(), z.string())
|
|
114
|
+
.describe('Map of field NAMES (not IDs) to values. E.g., { "Word": "serendipity" } or { "Front": "Question?", "Back": "Answer" }'),
|
|
115
|
+
tags: z
|
|
116
|
+
.array(z.string())
|
|
117
|
+
.optional()
|
|
118
|
+
.describe("Optional array of tags to add to the card"),
|
|
119
|
+
});
|
|
120
|
+
// Schema for adding attachments
|
|
121
|
+
const AddAttachmentSchema = z.object({
|
|
122
|
+
cardId: z.string().min(1).describe("ID of the card to attach the file to"),
|
|
123
|
+
data: z.string().min(1).describe("Base64-encoded file data"),
|
|
124
|
+
filename: z
|
|
125
|
+
.string()
|
|
126
|
+
.min(1)
|
|
127
|
+
.describe("Filename with extension (e.g., 'image.png', 'audio.mp3')"),
|
|
128
|
+
contentType: z
|
|
129
|
+
.string()
|
|
130
|
+
.optional()
|
|
131
|
+
.describe("MIME type of the file (e.g., 'image/png'). Can be inferred from filename if not provided."),
|
|
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
|
+
}
|
|
100
169
|
const TemplateFieldSchema = z.object({
|
|
101
170
|
id: z.string().describe("Unique identifier for the template field"),
|
|
102
171
|
name: z.string().describe("Display name of the field"),
|
|
103
172
|
pos: z.string().describe("Position of the field in the template"),
|
|
173
|
+
type: z
|
|
174
|
+
.string()
|
|
175
|
+
.optional()
|
|
176
|
+
.nullable()
|
|
177
|
+
.describe("Field type: null/text for user input, or ai/speech/translate/dictionary for auto-generated"),
|
|
178
|
+
source: z
|
|
179
|
+
.string()
|
|
180
|
+
.optional()
|
|
181
|
+
.nullable()
|
|
182
|
+
.describe("Source field ID for auto-generated fields"),
|
|
104
183
|
options: z
|
|
105
184
|
.object({
|
|
106
185
|
"multi-line?": z
|
|
@@ -108,6 +187,7 @@ const TemplateFieldSchema = z.object({
|
|
|
108
187
|
.optional()
|
|
109
188
|
.describe("Whether the field supports multiple lines of text"),
|
|
110
189
|
})
|
|
190
|
+
.passthrough()
|
|
111
191
|
.optional()
|
|
112
192
|
.describe("Additional options for the field"),
|
|
113
193
|
});
|
|
@@ -141,12 +221,13 @@ const CardSchema = z
|
|
|
141
221
|
name: z.string().describe("Display name of the card"),
|
|
142
222
|
"deck-id": z.string().describe("ID of the deck containing the card"),
|
|
143
223
|
fields: z
|
|
144
|
-
.record(z.unknown())
|
|
224
|
+
.record(z.string(), z.unknown())
|
|
145
225
|
.optional()
|
|
146
226
|
.describe("Map of field IDs to field values. Need to match the field IDs in the template"),
|
|
147
227
|
})
|
|
148
228
|
.strip();
|
|
149
229
|
const CreateCardResponseSchema = CardSchema.strip();
|
|
230
|
+
const UpdateCardResponseSchema = CardSchema.strip();
|
|
150
231
|
const ListCardsResponseSchema = z
|
|
151
232
|
.object({
|
|
152
233
|
bookmark: z.string().describe("Pagination bookmark for fetching next page"),
|
|
@@ -158,7 +239,16 @@ const DeckSchema = z
|
|
|
158
239
|
id: z.string().describe("Unique identifier for the deck"),
|
|
159
240
|
sort: z.number().describe("Sort order of the deck"),
|
|
160
241
|
name: z.string().describe("Display name of the deck"),
|
|
161
|
-
archived: z
|
|
242
|
+
"archived?": z
|
|
243
|
+
.boolean()
|
|
244
|
+
.optional()
|
|
245
|
+
.nullable()
|
|
246
|
+
.describe("Whether the deck is archived"),
|
|
247
|
+
"trashed?": z
|
|
248
|
+
.object({ date: z.string() })
|
|
249
|
+
.optional()
|
|
250
|
+
.nullable()
|
|
251
|
+
.describe("Whether the deck is trashed"),
|
|
162
252
|
})
|
|
163
253
|
.strip();
|
|
164
254
|
const ListDecksResponseSchema = z
|
|
@@ -167,6 +257,18 @@ const ListDecksResponseSchema = z
|
|
|
167
257
|
docs: z.array(DeckSchema).describe("Array of decks"),
|
|
168
258
|
})
|
|
169
259
|
.strip();
|
|
260
|
+
const DueCardSchema = z
|
|
261
|
+
.object({
|
|
262
|
+
id: z.string().describe("Unique identifier for the card"),
|
|
263
|
+
content: z.string().describe("Markdown content of the card"),
|
|
264
|
+
name: z.string().describe("Display name of the card"),
|
|
265
|
+
"deck-id": z.string().describe("ID of the deck containing the card"),
|
|
266
|
+
"new?": z.boolean().describe("Whether the card is new (never reviewed)"),
|
|
267
|
+
})
|
|
268
|
+
.passthrough();
|
|
269
|
+
const GetDueCardsResponseSchema = z.object({
|
|
270
|
+
cards: z.array(DueCardSchema).describe("Array of cards due for review"),
|
|
271
|
+
});
|
|
170
272
|
function getApiKey() {
|
|
171
273
|
const apiKey = process.env.MOCHI_API_KEY;
|
|
172
274
|
if (!apiKey) {
|
|
@@ -190,11 +292,13 @@ export class MochiClient {
|
|
|
190
292
|
});
|
|
191
293
|
}
|
|
192
294
|
async createCard(request) {
|
|
193
|
-
const
|
|
295
|
+
const mochiRequest = toMochiCreateCardRequest(request);
|
|
296
|
+
const response = await this.api.post("/cards", mochiRequest);
|
|
194
297
|
return CreateCardResponseSchema.parse(response.data);
|
|
195
298
|
}
|
|
196
299
|
async updateCard(cardId, request) {
|
|
197
|
-
const
|
|
300
|
+
const mochiRequest = toMochiUpdateCardRequest(request);
|
|
301
|
+
const response = await this.api.post(`/cards/${cardId}`, mochiRequest);
|
|
198
302
|
return CreateCardResponseSchema.parse(response.data);
|
|
199
303
|
}
|
|
200
304
|
async listDecks(params) {
|
|
@@ -202,13 +306,18 @@ export class MochiClient {
|
|
|
202
306
|
? ListDecksParamsSchema.parse(params)
|
|
203
307
|
: undefined;
|
|
204
308
|
const response = await this.api.get("/decks", { params: validatedParams });
|
|
205
|
-
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);
|
|
206
312
|
}
|
|
207
313
|
async listCards(params) {
|
|
208
314
|
const validatedParams = params
|
|
209
315
|
? ListCardsParamsSchema.parse(params)
|
|
210
316
|
: undefined;
|
|
211
|
-
const
|
|
317
|
+
const mochiParams = validatedParams
|
|
318
|
+
? toMochiListCardsParams(validatedParams)
|
|
319
|
+
: undefined;
|
|
320
|
+
const response = await this.api.get("/cards", { params: mochiParams });
|
|
212
321
|
return ListCardsResponseSchema.parse(response.data);
|
|
213
322
|
}
|
|
214
323
|
async listTemplates(params) {
|
|
@@ -220,386 +329,399 @@ export class MochiClient {
|
|
|
220
329
|
});
|
|
221
330
|
return ListTemplatesResponseSchema.parse(response.data);
|
|
222
331
|
}
|
|
332
|
+
async getDueCards(params) {
|
|
333
|
+
const validatedParams = params
|
|
334
|
+
? GetDueCardsParamsSchema.parse(params)
|
|
335
|
+
: undefined;
|
|
336
|
+
const deckId = validatedParams?.deckId;
|
|
337
|
+
const endpoint = deckId ? `/due/${deckId}` : "/due";
|
|
338
|
+
const queryParams = validatedParams?.date
|
|
339
|
+
? { date: validatedParams.date }
|
|
340
|
+
: undefined;
|
|
341
|
+
const response = await this.api.get(endpoint, { params: queryParams });
|
|
342
|
+
return GetDueCardsResponseSchema.parse(response.data);
|
|
343
|
+
}
|
|
344
|
+
async getTemplate(templateId) {
|
|
345
|
+
const response = await this.api.get(`/templates/${templateId}`);
|
|
346
|
+
return TemplateSchema.parse(response.data);
|
|
347
|
+
}
|
|
348
|
+
async createCardFromTemplate(request) {
|
|
349
|
+
// Fetch the template to get field definitions
|
|
350
|
+
const template = await this.getTemplate(request.templateId);
|
|
351
|
+
// Map field names to IDs
|
|
352
|
+
const fieldNameToId = {};
|
|
353
|
+
for (const [fieldId, field] of Object.entries(template.fields)) {
|
|
354
|
+
fieldNameToId[field.name] = fieldId;
|
|
355
|
+
}
|
|
356
|
+
// Build the fields object with IDs
|
|
357
|
+
const fields = {};
|
|
358
|
+
const fieldValues = [];
|
|
359
|
+
for (const [fieldName, value] of Object.entries(request.fields)) {
|
|
360
|
+
const fieldId = fieldNameToId[fieldName];
|
|
361
|
+
if (!fieldId) {
|
|
362
|
+
throw new MochiError([
|
|
363
|
+
`Unknown field name: "${fieldName}". Available fields: ${Object.keys(fieldNameToId).join(", ")}`,
|
|
364
|
+
], 400);
|
|
365
|
+
}
|
|
366
|
+
fields[fieldId] = { id: fieldId, value };
|
|
367
|
+
fieldValues.push(value);
|
|
368
|
+
}
|
|
369
|
+
// Build content from field values (joined with separator for multi-field templates)
|
|
370
|
+
const content = fieldValues.join("\n---\n");
|
|
371
|
+
const createRequest = {
|
|
372
|
+
content,
|
|
373
|
+
deckId: request.deckId,
|
|
374
|
+
templateId: request.templateId,
|
|
375
|
+
fields,
|
|
376
|
+
tags: request.tags,
|
|
377
|
+
};
|
|
378
|
+
return this.createCard(createRequest);
|
|
379
|
+
}
|
|
380
|
+
async addAttachment(request) {
|
|
381
|
+
// Infer content-type from filename if not provided
|
|
382
|
+
let contentType = request.contentType;
|
|
383
|
+
if (!contentType) {
|
|
384
|
+
const ext = request.filename.split(".").pop()?.toLowerCase();
|
|
385
|
+
const mimeTypes = {
|
|
386
|
+
png: "image/png",
|
|
387
|
+
jpg: "image/jpeg",
|
|
388
|
+
jpeg: "image/jpeg",
|
|
389
|
+
gif: "image/gif",
|
|
390
|
+
webp: "image/webp",
|
|
391
|
+
svg: "image/svg+xml",
|
|
392
|
+
mp3: "audio/mpeg",
|
|
393
|
+
wav: "audio/wav",
|
|
394
|
+
ogg: "audio/ogg",
|
|
395
|
+
mp4: "video/mp4",
|
|
396
|
+
pdf: "application/pdf",
|
|
397
|
+
};
|
|
398
|
+
contentType = mimeTypes[ext ?? ""] ?? "application/octet-stream";
|
|
399
|
+
}
|
|
400
|
+
// Convert base64 to Buffer
|
|
401
|
+
const buffer = Buffer.from(request.data, "base64");
|
|
402
|
+
// Create form data
|
|
403
|
+
const formData = new FormData();
|
|
404
|
+
formData.append("file", buffer, {
|
|
405
|
+
filename: request.filename,
|
|
406
|
+
contentType,
|
|
407
|
+
});
|
|
408
|
+
// Upload attachment
|
|
409
|
+
await this.api.post(`/cards/${request.cardId}/attachments/${encodeURIComponent(request.filename)}`, formData, {
|
|
410
|
+
headers: {
|
|
411
|
+
...formData.getHeaders(),
|
|
412
|
+
Authorization: `Basic ${Buffer.from(`${this.token}:`).toString("base64")}`,
|
|
413
|
+
},
|
|
414
|
+
});
|
|
415
|
+
return {
|
|
416
|
+
filename: request.filename,
|
|
417
|
+
markdown: ``,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
223
420
|
}
|
|
224
421
|
// Server setup
|
|
225
|
-
const server = new
|
|
422
|
+
const server = new McpServer({
|
|
226
423
|
name: "mcp-server/mochi",
|
|
227
424
|
version: "1.0.3",
|
|
228
|
-
}, {
|
|
229
|
-
capabilities: {
|
|
230
|
-
tools: {},
|
|
231
|
-
resources: {},
|
|
232
|
-
prompts: {},
|
|
233
|
-
},
|
|
234
425
|
});
|
|
235
|
-
//
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
### content (required)
|
|
248
|
-
The markdown content of the card. Separate front and back using a horizontal rule (---).
|
|
249
|
-
|
|
250
|
-
### template-id (optional)
|
|
251
|
-
When using a template, the field ids MUST match the template ones. If not using a template, omit this field.
|
|
252
|
-
|
|
253
|
-
### fields (optional)
|
|
254
|
-
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.
|
|
255
|
-
|
|
256
|
-
## Example without template
|
|
257
|
-
{
|
|
258
|
-
"content": "What is the capital of France?\n---\nParis",
|
|
259
|
-
"deck-id": "btmZUXWM"
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
## Example with template
|
|
263
|
-
{
|
|
264
|
-
"content": "New card from API. ",
|
|
265
|
-
"deck-id": "btmZUXWM",
|
|
266
|
-
"template-id": "8BtaEAXe",
|
|
267
|
-
"fields": {
|
|
268
|
-
"name": {
|
|
269
|
-
"id": "name",
|
|
270
|
-
"value": "Hello,"
|
|
271
|
-
},
|
|
272
|
-
"JNEnw1e7": {
|
|
273
|
-
"id": "JNEnw1e7",
|
|
274
|
-
"value": "World!"
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
## Properties of good flashcards:
|
|
280
|
-
- **focused:** A question or answer involving too much detail will dull your concentration and stimulate incomplete retrievals, leaving some bulbs unlit.
|
|
281
|
-
- **precise** about what they're asking for. Vague questions will elicit vague answers, which won't reliably light the bulbs you're targeting.
|
|
282
|
-
- **consistent** answers, lighting the same bulbs each time you perform the task.
|
|
283
|
-
- **tractable**: Write prompts which you can almost always answer correctly. This often means breaking the task down, or adding cues
|
|
284
|
-
- **effortful**: You shouldn't be able to trivially infer the answer.
|
|
285
|
-
`,
|
|
286
|
-
inputSchema: zodToJsonSchema(CreateCardRequestSchema),
|
|
287
|
-
annotations: {
|
|
288
|
-
title: "Create flashcard on Mochi",
|
|
289
|
-
readOnlyHint: false,
|
|
290
|
-
destructiveHint: false,
|
|
291
|
-
idempotentHint: false,
|
|
292
|
-
openWorldHint: true,
|
|
293
|
-
},
|
|
294
|
-
},
|
|
295
|
-
{
|
|
296
|
-
name: "mochi_update_card",
|
|
297
|
-
description: `Update or delete an existing flashcard in Mochi. To delete set trashed to true.`,
|
|
298
|
-
inputSchema: zodToJsonSchema(z.object({
|
|
299
|
-
"card-id": z.string(),
|
|
300
|
-
...UpdateCardRequestSchema.shape,
|
|
301
|
-
})),
|
|
302
|
-
annotations: {
|
|
303
|
-
title: "Update flashcard on Mochi",
|
|
304
|
-
readOnlyHint: false,
|
|
305
|
-
destructiveHint: false,
|
|
306
|
-
idempotentHint: false,
|
|
307
|
-
openWorldHint: true,
|
|
308
|
-
},
|
|
309
|
-
},
|
|
310
|
-
{
|
|
311
|
-
name: "mochi_list_cards",
|
|
312
|
-
description: "List cards in pages of 10 cards per page",
|
|
313
|
-
inputSchema: zodToJsonSchema(ListCardsParamsSchema),
|
|
314
|
-
annotations: {
|
|
315
|
-
title: "List flashcards on Mochi",
|
|
316
|
-
readOnlyHint: true,
|
|
317
|
-
destructiveHint: false,
|
|
318
|
-
idempotentHint: true,
|
|
319
|
-
openWorldHint: false,
|
|
320
|
-
},
|
|
321
|
-
},
|
|
322
|
-
{
|
|
323
|
-
name: "mochi_list_decks",
|
|
324
|
-
description: "List all decks",
|
|
325
|
-
inputSchema: zodToJsonSchema(ListDecksParamsSchema),
|
|
326
|
-
annotations: {
|
|
327
|
-
title: "List decks on Mochi",
|
|
328
|
-
readOnlyHint: true,
|
|
329
|
-
destructiveHint: false,
|
|
330
|
-
idempotentHint: true,
|
|
331
|
-
openWorldHint: false,
|
|
332
|
-
},
|
|
333
|
-
},
|
|
334
|
-
{
|
|
335
|
-
name: "mochi_list_templates",
|
|
336
|
-
description: `Templates can be used to create cards with pre-defined fields using the template_id field.
|
|
337
|
-
|
|
338
|
-
Example response:
|
|
339
|
-
{
|
|
340
|
-
"bookmark": "g1AAAABAeJzLYWBgYMpgSmHgKy5JLCrJTq2MT8lPzkzJBYpzVBn4JgaaVZiC5Dlg8igyWQAxwRHd",
|
|
341
|
-
"docs": [
|
|
342
|
-
{
|
|
343
|
-
"id": "YDELNZSu",
|
|
344
|
-
"name": "Simple flashcard",
|
|
345
|
-
"content": "# << Front >>\n---\n<< Back >>",
|
|
346
|
-
"pos": "s",
|
|
347
|
-
"fields": {
|
|
348
|
-
"name": {
|
|
349
|
-
"id": "name",
|
|
350
|
-
"name": "Front",
|
|
351
|
-
"pos": "a"
|
|
352
|
-
},
|
|
353
|
-
"Ysrde7Lj": {
|
|
354
|
-
"id": "Ysrde7Lj",
|
|
355
|
-
"name": "Back",
|
|
356
|
-
"pos": "m",
|
|
357
|
-
"options": {
|
|
358
|
-
"multi-line?": true
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
},
|
|
363
|
-
...
|
|
364
|
-
]
|
|
365
|
-
}`,
|
|
366
|
-
inputSchema: zodToJsonSchema(ListTemplatesParamsSchema),
|
|
367
|
-
annotations: {
|
|
368
|
-
title: "List templates on Mochi",
|
|
369
|
-
readOnlyHint: true,
|
|
370
|
-
destructiveHint: false,
|
|
371
|
-
idempotentHint: true,
|
|
372
|
-
openWorldHint: false,
|
|
373
|
-
},
|
|
374
|
-
},
|
|
375
|
-
],
|
|
376
|
-
}));
|
|
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
|
+
});
|
|
377
438
|
// Create Mochi client
|
|
378
439
|
const mochiClient = new MochiClient(MOCHI_API_KEY);
|
|
379
|
-
//
|
|
380
|
-
|
|
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
|
+
}
|
|
381
471
|
return {
|
|
382
|
-
|
|
472
|
+
content: [
|
|
383
473
|
{
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
description: `List of all decks in Mochi.`,
|
|
387
|
-
mimeType: "application/json",
|
|
388
|
-
},
|
|
389
|
-
{
|
|
390
|
-
uri: `mochi://templates`,
|
|
391
|
-
name: "All Mochi Templates",
|
|
392
|
-
description: `List of all templates in Mochi.`,
|
|
393
|
-
mimeType: "application/json",
|
|
474
|
+
type: "text",
|
|
475
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
394
476
|
},
|
|
395
477
|
],
|
|
478
|
+
isError: true,
|
|
396
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
|
+
}
|
|
397
505
|
});
|
|
398
|
-
server.
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
return {
|
|
420
|
-
contents: [
|
|
421
|
-
{
|
|
422
|
-
uri,
|
|
423
|
-
mimeType: "application/json",
|
|
424
|
-
text: JSON.stringify(templates, null, 2),
|
|
425
|
-
},
|
|
426
|
-
],
|
|
427
|
-
};
|
|
428
|
-
}
|
|
429
|
-
default: {
|
|
430
|
-
throw new Error("Invalid resource URI");
|
|
431
|
-
}
|
|
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);
|
|
432
527
|
}
|
|
433
528
|
});
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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
|
+
}
|
|
621
|
+
});
|
|
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
|
+
}
|
|
439
667
|
});
|
|
440
|
-
|
|
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();
|
|
441
674
|
return {
|
|
442
|
-
|
|
675
|
+
contents: [
|
|
443
676
|
{
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
{
|
|
448
|
-
name: "input",
|
|
449
|
-
description: "The information to base the flashcard on.",
|
|
450
|
-
},
|
|
451
|
-
],
|
|
677
|
+
uri: "mochi://decks",
|
|
678
|
+
mimeType: "application/json",
|
|
679
|
+
text: JSON.stringify(decks.map((deck) => ({ id: deck.id, name: deck.name })), null, 2),
|
|
452
680
|
},
|
|
453
681
|
],
|
|
454
682
|
};
|
|
455
683
|
});
|
|
456
|
-
server.
|
|
457
|
-
|
|
458
|
-
|
|
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();
|
|
459
689
|
return {
|
|
460
|
-
|
|
690
|
+
contents: [
|
|
461
691
|
{
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
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:
|
|
466
715
|
- Keep questions and answers atomic.
|
|
467
716
|
- Utilize cloze prompts when applicable, like "This is a text with {{hidden}} part. Then don't use '---' separator.".
|
|
468
717
|
- Focus on effective retrieval practice by being concise and clear.
|
|
469
718
|
- Make it just challenging enough to reinforce specific facts.
|
|
470
719
|
Input: ${input}
|
|
471
720
|
`,
|
|
472
|
-
},
|
|
473
721
|
},
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
});
|
|
477
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
478
|
-
try {
|
|
479
|
-
switch (request.params.name) {
|
|
480
|
-
case "mochi_create_card": {
|
|
481
|
-
const validatedArgs = CreateCardRequestSchema.parse(request.params.arguments);
|
|
482
|
-
const response = await mochiClient.createCard(validatedArgs);
|
|
483
|
-
return {
|
|
484
|
-
content: [
|
|
485
|
-
{
|
|
486
|
-
type: "text",
|
|
487
|
-
text: JSON.stringify(response, null, 2),
|
|
488
|
-
},
|
|
489
|
-
],
|
|
490
|
-
isError: false,
|
|
491
|
-
};
|
|
492
|
-
}
|
|
493
|
-
case "mochi_update_card": {
|
|
494
|
-
const { "card-id": cardId, ...updateArgs } = z
|
|
495
|
-
.object({
|
|
496
|
-
"card-id": z.string(),
|
|
497
|
-
...UpdateCardRequestSchema.shape,
|
|
498
|
-
})
|
|
499
|
-
.parse(request.params.arguments);
|
|
500
|
-
const response = await mochiClient.updateCard(cardId, updateArgs);
|
|
501
|
-
return {
|
|
502
|
-
content: [
|
|
503
|
-
{
|
|
504
|
-
type: "text",
|
|
505
|
-
text: JSON.stringify(response, null, 2),
|
|
506
|
-
},
|
|
507
|
-
],
|
|
508
|
-
isError: false,
|
|
509
|
-
};
|
|
510
|
-
}
|
|
511
|
-
case "mochi_list_decks": {
|
|
512
|
-
const validatedArgs = ListDecksParamsSchema.parse(request.params.arguments);
|
|
513
|
-
const response = await mochiClient.listDecks(validatedArgs);
|
|
514
|
-
return {
|
|
515
|
-
content: [
|
|
516
|
-
{
|
|
517
|
-
type: "text",
|
|
518
|
-
text: JSON.stringify(response, null, 2),
|
|
519
|
-
},
|
|
520
|
-
],
|
|
521
|
-
isError: false,
|
|
522
|
-
};
|
|
523
|
-
}
|
|
524
|
-
case "mochi_list_cards": {
|
|
525
|
-
const validatedArgs = ListCardsParamsSchema.parse(request.params.arguments);
|
|
526
|
-
const response = await mochiClient.listCards(validatedArgs);
|
|
527
|
-
return {
|
|
528
|
-
content: [
|
|
529
|
-
{
|
|
530
|
-
type: "text",
|
|
531
|
-
text: JSON.stringify(response, null, 2),
|
|
532
|
-
},
|
|
533
|
-
],
|
|
534
|
-
isError: false,
|
|
535
|
-
};
|
|
536
|
-
}
|
|
537
|
-
case "mochi_list_templates": {
|
|
538
|
-
const validatedArgs = ListTemplatesParamsSchema.parse(request.params.arguments);
|
|
539
|
-
const response = await mochiClient.listTemplates(validatedArgs);
|
|
540
|
-
return {
|
|
541
|
-
content: [
|
|
542
|
-
{
|
|
543
|
-
type: "text",
|
|
544
|
-
text: JSON.stringify(response, null, 2),
|
|
545
|
-
},
|
|
546
|
-
],
|
|
547
|
-
isError: false,
|
|
548
|
-
};
|
|
549
|
-
}
|
|
550
|
-
default:
|
|
551
|
-
return {
|
|
552
|
-
content: [
|
|
553
|
-
{
|
|
554
|
-
type: "text",
|
|
555
|
-
text: `Unknown tool: ${request.params.name}`,
|
|
556
|
-
},
|
|
557
|
-
],
|
|
558
|
-
isError: true,
|
|
559
|
-
};
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
catch (error) {
|
|
563
|
-
if (error instanceof z.ZodError) {
|
|
564
|
-
const formattedErrors = error.errors.map((err) => {
|
|
565
|
-
const path = err.path.join(".");
|
|
566
|
-
const message = err.code === "invalid_type" && err.message.includes("Required")
|
|
567
|
-
? `Required field '${path}' is missing`
|
|
568
|
-
: err.message;
|
|
569
|
-
return `${path ? `${path}: ` : ""}${message}`;
|
|
570
|
-
});
|
|
571
|
-
return {
|
|
572
|
-
content: [
|
|
573
|
-
{
|
|
574
|
-
type: "text",
|
|
575
|
-
text: `Validation error:\n${formattedErrors.join("\n")}`,
|
|
576
|
-
},
|
|
577
|
-
],
|
|
578
|
-
isError: true,
|
|
579
|
-
};
|
|
580
|
-
}
|
|
581
|
-
if (error instanceof MochiError) {
|
|
582
|
-
return {
|
|
583
|
-
content: [
|
|
584
|
-
{
|
|
585
|
-
type: "text",
|
|
586
|
-
text: `Mochi API error (${error.statusCode}): ${error.message}`,
|
|
587
|
-
},
|
|
588
|
-
],
|
|
589
|
-
isError: true,
|
|
590
|
-
};
|
|
591
|
-
}
|
|
592
|
-
return {
|
|
593
|
-
content: [
|
|
594
|
-
{
|
|
595
|
-
type: "text",
|
|
596
|
-
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
597
|
-
},
|
|
598
|
-
],
|
|
599
|
-
isError: true,
|
|
600
|
-
};
|
|
601
|
-
}
|
|
602
|
-
});
|
|
722
|
+
},
|
|
723
|
+
],
|
|
724
|
+
}));
|
|
603
725
|
async function runServer() {
|
|
604
726
|
const transport = new StdioServerTransport();
|
|
605
727
|
await server.connect(transport);
|