@fredrika/mcp-mochi 1.0.1 → 1.0.2

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.
@@ -0,0 +1 @@
1
+ export declare const VERSION = "1.0.2";
@@ -0,0 +1,3 @@
1
+ // If the format of this file changes, so it doesn't simply export a VERSION constant,
2
+ // this will break .github/workflows/version-check.yml.
3
+ export const VERSION = "1.0.2";
package/dist/index.d.ts CHANGED
@@ -4,11 +4,8 @@ declare const CreateCardRequestSchema: z.ZodObject<{
4
4
  content: z.ZodString;
5
5
  "deck-id": z.ZodString;
6
6
  "template-id": z.ZodOptional<z.ZodString>;
7
- archived: z.ZodOptional<z.ZodBoolean>;
8
- "review-reverse": z.ZodOptional<z.ZodBoolean>;
9
- pos: z.ZodOptional<z.ZodString>;
10
7
  "manual-tags": z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
11
- fields: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
8
+ fields: z.ZodRecord<z.ZodString, z.ZodObject<{
12
9
  id: z.ZodString;
13
10
  value: z.ZodString;
14
11
  }, "strip", z.ZodTypeAny, {
@@ -17,31 +14,62 @@ declare const CreateCardRequestSchema: z.ZodObject<{
17
14
  }, {
18
15
  id: string;
19
16
  value: string;
20
- }>>>;
17
+ }>>;
21
18
  }, "strip", z.ZodTypeAny, {
22
19
  content: string;
23
20
  "deck-id": string;
21
+ fields: Record<string, {
22
+ id: string;
23
+ value: string;
24
+ }>;
25
+ "template-id"?: string | undefined;
26
+ "manual-tags"?: string[] | undefined;
27
+ }, {
28
+ content: string;
29
+ "deck-id": string;
30
+ fields: Record<string, {
31
+ id: string;
32
+ value: string;
33
+ }>;
24
34
  "template-id"?: string | undefined;
25
- archived?: boolean | undefined;
26
- "review-reverse"?: boolean | undefined;
27
- pos?: string | undefined;
28
35
  "manual-tags"?: string[] | undefined;
36
+ }>;
37
+ declare const UpdateCardRequestSchema: z.ZodObject<{
38
+ content: z.ZodOptional<z.ZodString>;
39
+ "deck-id": z.ZodOptional<z.ZodString>;
40
+ "template-id": z.ZodOptional<z.ZodString>;
41
+ "archived?": z.ZodOptional<z.ZodBoolean>;
42
+ "trashed?": z.ZodOptional<z.ZodString>;
43
+ fields: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
44
+ id: z.ZodString;
45
+ value: z.ZodString;
46
+ }, "strip", z.ZodTypeAny, {
47
+ id: string;
48
+ value: string;
49
+ }, {
50
+ id: string;
51
+ value: string;
52
+ }>>>;
53
+ }, "strip", z.ZodTypeAny, {
54
+ content?: string | undefined;
55
+ "deck-id"?: string | undefined;
56
+ "template-id"?: string | undefined;
29
57
  fields?: Record<string, {
30
58
  id: string;
31
59
  value: string;
32
60
  }> | undefined;
61
+ "archived?"?: boolean | undefined;
62
+ "trashed?"?: string | undefined;
33
63
  }, {
34
- content: string;
35
- "deck-id": string;
64
+ content?: string | undefined;
65
+ "deck-id"?: string | undefined;
36
66
  "template-id"?: string | undefined;
37
- archived?: boolean | undefined;
38
- "review-reverse"?: boolean | undefined;
39
- pos?: string | undefined;
40
- "manual-tags"?: string[] | undefined;
41
67
  fields?: Record<string, {
42
68
  id: string;
43
69
  value: string;
44
70
  }> | undefined;
71
+ "archived?"?: boolean | undefined;
72
+ "trashed?"?: string | undefined;
45
73
  }>;
46
74
  declare const ListDecksParamsSchema: z.ZodObject<{
47
75
  bookmark: z.ZodOptional<z.ZodString>;
@@ -63,54 +91,134 @@ declare const ListCardsParamsSchema: z.ZodObject<{
63
91
  bookmark?: string | undefined;
64
92
  limit?: number | undefined;
65
93
  }>;
66
- type ListCardsParams = z.infer<typeof ListCardsParamsSchema>;
67
- type ListDecksParams = z.infer<typeof ListDecksParamsSchema>;
68
- type CreateCardRequest = z.infer<typeof CreateCardRequestSchema>;
69
- declare const CreateCardResponseSchema: z.ZodObject<{
70
- status: z.ZodString;
71
- error_message: z.ZodOptional<z.ZodString>;
94
+ declare const ListTemplatesParamsSchema: z.ZodObject<{
95
+ bookmark: z.ZodOptional<z.ZodString>;
72
96
  }, "strip", z.ZodTypeAny, {
73
- status: string;
74
- error_message?: string | undefined;
97
+ bookmark?: string | undefined;
75
98
  }, {
76
- status: string;
77
- error_message?: string | undefined;
99
+ bookmark?: string | undefined;
78
100
  }>;
79
- declare const ListDecksResponseSchema: z.ZodObject<{
101
+ declare const ListTemplatesResponseSchema: z.ZodObject<{
80
102
  bookmark: z.ZodString;
81
103
  docs: z.ZodArray<z.ZodObject<{
82
104
  id: z.ZodString;
83
- sort: z.ZodNumber;
84
105
  name: z.ZodString;
85
- archived: z.ZodOptional<z.ZodBoolean>;
106
+ content: z.ZodString;
107
+ pos: z.ZodString;
108
+ fields: z.ZodRecord<z.ZodString, z.ZodObject<{
109
+ id: z.ZodString;
110
+ name: z.ZodString;
111
+ pos: z.ZodString;
112
+ options: z.ZodOptional<z.ZodObject<{
113
+ "multi-line?": z.ZodOptional<z.ZodBoolean>;
114
+ }, "strip", z.ZodTypeAny, {
115
+ "multi-line?"?: boolean | undefined;
116
+ }, {
117
+ "multi-line?"?: boolean | undefined;
118
+ }>>;
119
+ }, "strip", z.ZodTypeAny, {
120
+ name: string;
121
+ id: string;
122
+ pos: string;
123
+ options?: {
124
+ "multi-line?"?: boolean | undefined;
125
+ } | undefined;
126
+ }, {
127
+ name: string;
128
+ id: string;
129
+ pos: string;
130
+ options?: {
131
+ "multi-line?"?: boolean | undefined;
132
+ } | undefined;
133
+ }>>;
86
134
  }, "strip", z.ZodTypeAny, {
87
- sort: number;
88
135
  name: string;
89
136
  id: string;
90
- archived?: boolean | undefined;
137
+ content: string;
138
+ fields: Record<string, {
139
+ name: string;
140
+ id: string;
141
+ pos: string;
142
+ options?: {
143
+ "multi-line?"?: boolean | undefined;
144
+ } | undefined;
145
+ }>;
146
+ pos: string;
91
147
  }, {
92
- sort: number;
93
148
  name: string;
94
149
  id: string;
95
- archived?: boolean | undefined;
150
+ content: string;
151
+ fields: Record<string, {
152
+ name: string;
153
+ id: string;
154
+ pos: string;
155
+ options?: {
156
+ "multi-line?"?: boolean | undefined;
157
+ } | undefined;
158
+ }>;
159
+ pos: string;
96
160
  }>, "many">;
97
161
  }, "strip", z.ZodTypeAny, {
98
162
  bookmark: string;
99
163
  docs: {
100
- sort: number;
101
164
  name: string;
102
165
  id: string;
103
- archived?: boolean | undefined;
166
+ content: string;
167
+ fields: Record<string, {
168
+ name: string;
169
+ id: string;
170
+ pos: string;
171
+ options?: {
172
+ "multi-line?"?: boolean | undefined;
173
+ } | undefined;
174
+ }>;
175
+ pos: string;
104
176
  }[];
105
177
  }, {
106
178
  bookmark: string;
107
179
  docs: {
108
- sort: number;
109
180
  name: string;
110
181
  id: string;
111
- archived?: boolean | undefined;
182
+ content: string;
183
+ fields: Record<string, {
184
+ name: string;
185
+ id: string;
186
+ pos: string;
187
+ options?: {
188
+ "multi-line?"?: boolean | undefined;
189
+ } | undefined;
190
+ }>;
191
+ pos: string;
112
192
  }[];
113
193
  }>;
194
+ type ListTemplatesParams = z.infer<typeof ListTemplatesParamsSchema>;
195
+ type ListTemplatesResponse = z.infer<typeof ListTemplatesResponseSchema>;
196
+ type ListCardsParams = z.infer<typeof ListCardsParamsSchema>;
197
+ type ListDecksParams = z.infer<typeof ListDecksParamsSchema>;
198
+ type CreateCardRequest = z.infer<typeof CreateCardRequestSchema>;
199
+ type UpdateCardRequest = z.infer<typeof UpdateCardRequestSchema>;
200
+ declare const CreateCardResponseSchema: z.ZodObject<{
201
+ id: z.ZodString;
202
+ tags: z.ZodArray<z.ZodString, "many">;
203
+ content: z.ZodString;
204
+ name: z.ZodString;
205
+ "deck-id": z.ZodString;
206
+ fields: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
207
+ }, "strip", z.ZodTypeAny, {
208
+ name: string;
209
+ id: string;
210
+ content: string;
211
+ "deck-id": string;
212
+ tags: string[];
213
+ fields?: Record<string, unknown> | undefined;
214
+ }, {
215
+ name: string;
216
+ id: string;
217
+ content: string;
218
+ "deck-id": string;
219
+ tags: string[];
220
+ fields?: Record<string, unknown> | undefined;
221
+ }>;
114
222
  declare const ListCardsResponseSchema: z.ZodObject<{
115
223
  bookmark: z.ZodString;
116
224
  docs: z.ZodArray<z.ZodObject<{
@@ -119,43 +227,21 @@ declare const ListCardsResponseSchema: z.ZodObject<{
119
227
  content: z.ZodString;
120
228
  name: z.ZodString;
121
229
  "deck-id": z.ZodString;
122
- fields: z.ZodRecord<z.ZodString, z.ZodUnknown>;
123
- pos: z.ZodString;
124
- references: z.ZodArray<z.ZodUnknown, "many">;
125
- reviews: z.ZodArray<z.ZodUnknown, "many">;
126
- "created-at": z.ZodObject<{
127
- date: z.ZodString;
128
- }, "strip", z.ZodTypeAny, {
129
- date: string;
130
- }, {
131
- date: string;
132
- }>;
230
+ fields: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
133
231
  }, "strip", z.ZodTypeAny, {
134
232
  name: string;
135
233
  id: string;
136
234
  content: string;
137
235
  "deck-id": string;
138
- pos: string;
139
- fields: Record<string, unknown>;
140
236
  tags: string[];
141
- references: unknown[];
142
- reviews: unknown[];
143
- "created-at": {
144
- date: string;
145
- };
237
+ fields?: Record<string, unknown> | undefined;
146
238
  }, {
147
239
  name: string;
148
240
  id: string;
149
241
  content: string;
150
242
  "deck-id": string;
151
- pos: string;
152
- fields: Record<string, unknown>;
153
243
  tags: string[];
154
- references: unknown[];
155
- reviews: unknown[];
156
- "created-at": {
157
- date: string;
158
- };
244
+ fields?: Record<string, unknown> | undefined;
159
245
  }>, "many">;
160
246
  }, "strip", z.ZodTypeAny, {
161
247
  bookmark: string;
@@ -164,14 +250,8 @@ declare const ListCardsResponseSchema: z.ZodObject<{
164
250
  id: string;
165
251
  content: string;
166
252
  "deck-id": string;
167
- pos: string;
168
- fields: Record<string, unknown>;
169
253
  tags: string[];
170
- references: unknown[];
171
- reviews: unknown[];
172
- "created-at": {
173
- date: string;
174
- };
254
+ fields?: Record<string, unknown> | undefined;
175
255
  }[];
176
256
  }, {
177
257
  bookmark: string;
@@ -180,25 +260,56 @@ declare const ListCardsResponseSchema: z.ZodObject<{
180
260
  id: string;
181
261
  content: string;
182
262
  "deck-id": string;
183
- pos: string;
184
- fields: Record<string, unknown>;
185
263
  tags: string[];
186
- references: unknown[];
187
- reviews: unknown[];
188
- "created-at": {
189
- date: string;
190
- };
264
+ fields?: Record<string, unknown> | undefined;
191
265
  }[];
192
266
  }>;
193
267
  type CreateCardResponse = z.infer<typeof CreateCardResponseSchema>;
194
- type ListDecksResponse = z.infer<typeof ListDecksResponseSchema>;
268
+ type ListDecksResponse = z.infer<typeof ListDecksResponseSchema>["docs"];
195
269
  type ListCardsResponse = z.infer<typeof ListCardsResponseSchema>;
270
+ declare const ListDecksResponseSchema: z.ZodObject<{
271
+ bookmark: z.ZodString;
272
+ docs: z.ZodArray<z.ZodObject<{
273
+ id: z.ZodString;
274
+ sort: z.ZodNumber;
275
+ name: z.ZodString;
276
+ archived: z.ZodOptional<z.ZodBoolean>;
277
+ }, "strip", z.ZodTypeAny, {
278
+ sort: number;
279
+ name: string;
280
+ id: string;
281
+ archived?: boolean | undefined;
282
+ }, {
283
+ sort: number;
284
+ name: string;
285
+ id: string;
286
+ archived?: boolean | undefined;
287
+ }>, "many">;
288
+ }, "strip", z.ZodTypeAny, {
289
+ bookmark: string;
290
+ docs: {
291
+ sort: number;
292
+ name: string;
293
+ id: string;
294
+ archived?: boolean | undefined;
295
+ }[];
296
+ }, {
297
+ bookmark: string;
298
+ docs: {
299
+ sort: number;
300
+ name: string;
301
+ id: string;
302
+ archived?: boolean | undefined;
303
+ }[];
304
+ }>;
196
305
  export declare class MochiClient {
197
306
  private api;
198
307
  private token;
199
308
  constructor(token: string);
200
309
  createCard(request: CreateCardRequest): Promise<CreateCardResponse>;
310
+ updateCard(cardId: string, request: UpdateCardRequest): Promise<CreateCardResponse>;
201
311
  listDecks(params?: ListDecksParams): Promise<ListDecksResponse>;
202
312
  listCards(params?: ListCardsParams): Promise<ListCardsResponse>;
313
+ listTemplates(params?: ListTemplatesParams): Promise<ListTemplatesResponse>;
203
314
  }
204
315
  export {};
package/dist/index.js CHANGED
@@ -35,10 +35,15 @@ const CreateCardRequestSchema = z.object({
35
35
  content: z.string().min(1),
36
36
  "deck-id": z.string().min(1),
37
37
  "template-id": z.string().optional(),
38
- archived: z.boolean().optional(),
39
- "review-reverse": z.boolean().optional(),
40
- pos: z.string().optional(),
41
38
  "manual-tags": z.array(z.string()).optional(),
39
+ fields: z.record(z.string(), CreateCardFieldSchema),
40
+ });
41
+ const UpdateCardRequestSchema = z.object({
42
+ content: z.string().optional(),
43
+ "deck-id": z.string().optional(),
44
+ "template-id": z.string().optional(),
45
+ "archived?": z.boolean().optional(),
46
+ "trashed?": z.string().optional(),
42
47
  fields: z.record(z.string(), CreateCardFieldSchema).optional(),
43
48
  });
44
49
  const ListDecksParamsSchema = z.object({
@@ -49,27 +54,35 @@ const ListCardsParamsSchema = z.object({
49
54
  limit: z.number().min(1).max(100).optional(),
50
55
  bookmark: z.string().optional(),
51
56
  });
52
- // Response Zod schemas
53
- const CreateCardResponseSchema = z
54
- .object({
55
- status: z.string(),
56
- error_message: z.string().optional(),
57
- })
58
- .strip();
59
- const DeckSchema = z
57
+ const ListTemplatesParamsSchema = z.object({
58
+ bookmark: z.string().optional(),
59
+ });
60
+ const TemplateFieldSchema = z.object({
61
+ id: z.string(),
62
+ name: z.string(),
63
+ pos: z.string(),
64
+ options: z
65
+ .object({
66
+ "multi-line?": z.boolean().optional(),
67
+ })
68
+ .optional(),
69
+ });
70
+ const TemplateSchema = z
60
71
  .object({
61
72
  id: z.string(),
62
- sort: z.number(),
63
73
  name: z.string(),
64
- archived: z.boolean().optional(),
74
+ content: z.string(),
75
+ pos: z.string(),
76
+ fields: z.record(z.string(), TemplateFieldSchema),
65
77
  })
66
78
  .strip();
67
- const ListDecksResponseSchema = z
79
+ const ListTemplatesResponseSchema = z
68
80
  .object({
69
81
  bookmark: z.string(),
70
- docs: z.array(DeckSchema),
82
+ docs: z.array(TemplateSchema),
71
83
  })
72
84
  .strip();
85
+ // Response Zod schemas
73
86
  const CardSchema = z
74
87
  .object({
75
88
  id: z.string(),
@@ -77,21 +90,30 @@ const CardSchema = z
77
90
  content: z.string(),
78
91
  name: z.string(),
79
92
  "deck-id": z.string(),
80
- fields: z.record(z.unknown()),
81
- pos: z.string(),
82
- references: z.array(z.unknown()),
83
- reviews: z.array(z.unknown()),
84
- "created-at": z.object({
85
- date: z.string(),
86
- }),
93
+ fields: z.record(z.unknown()).optional(),
87
94
  })
88
95
  .strip();
96
+ const CreateCardResponseSchema = CardSchema.strip();
89
97
  const ListCardsResponseSchema = z
90
98
  .object({
91
99
  bookmark: z.string(),
92
100
  docs: z.array(CardSchema),
93
101
  })
94
102
  .strip();
103
+ const DeckSchema = z
104
+ .object({
105
+ id: z.string(),
106
+ sort: z.number(),
107
+ name: z.string(),
108
+ archived: z.boolean().optional(),
109
+ })
110
+ .strip();
111
+ const ListDecksResponseSchema = z
112
+ .object({
113
+ bookmark: z.string(),
114
+ docs: z.array(DeckSchema),
115
+ })
116
+ .strip();
95
117
  function getApiKey() {
96
118
  const apiKey = process.env.MOCHI_API_KEY;
97
119
  if (!apiKey) {
@@ -118,12 +140,16 @@ export class MochiClient {
118
140
  const response = await this.api.post("/cards", request);
119
141
  return CreateCardResponseSchema.parse(response.data);
120
142
  }
143
+ async updateCard(cardId, request) {
144
+ const response = await this.api.post(`/cards/${cardId}`, request);
145
+ return CreateCardResponseSchema.parse(response.data);
146
+ }
121
147
  async listDecks(params) {
122
148
  const validatedParams = params
123
149
  ? ListDecksParamsSchema.parse(params)
124
150
  : undefined;
125
151
  const response = await this.api.get("/decks", { params: validatedParams });
126
- return ListDecksResponseSchema.parse(response.data);
152
+ return ListDecksResponseSchema.parse(response.data).docs.filter((deck) => !deck.archived);
127
153
  }
128
154
  async listCards(params) {
129
155
  const validatedParams = params
@@ -132,11 +158,20 @@ export class MochiClient {
132
158
  const response = await this.api.get("/cards", { params: validatedParams });
133
159
  return ListCardsResponseSchema.parse(response.data);
134
160
  }
161
+ async listTemplates(params) {
162
+ const validatedParams = params
163
+ ? ListTemplatesParamsSchema.parse(params)
164
+ : undefined;
165
+ const response = await this.api.get("/templates", {
166
+ params: validatedParams,
167
+ });
168
+ return ListTemplatesResponseSchema.parse(response.data);
169
+ }
135
170
  }
136
171
  // Server setup
137
172
  const server = new Server({
138
173
  name: "mcp-server/mochi",
139
- version: "0.1.0",
174
+ version: "1.0.2",
140
175
  }, {
141
176
  capabilities: {
142
177
  tools: {},
@@ -148,58 +183,192 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
148
183
  tools: [
149
184
  {
150
185
  name: "mochi_create_card",
151
- description: "Create a new flashcard",
186
+ description: `Create a new flashcard in Mochi. Use this whenever I ask questions about something that is interesting to remember. E.g. if I ask "What is the capital of France?", you should create a new flashcard with the content "What is the capital of France?\n---\nParis".
187
+
188
+ ## Parameters
189
+
190
+ ### deck-id (required)
191
+ ALWAYS look up deck-id with the mochi_list_decks tool.
192
+
193
+ ### content (required)
194
+ The markdown content of the card. Separate front and back using a horizontal rule (---).
195
+
196
+ ### fields (optional)
197
+ A map of field IDs (keyword) to field values. The field IDs should correspond to the fields defined on the template used by the card.
198
+
199
+ ## Example
200
+ {
201
+ "content": "New card from API. ![](@media/foobar03.png)",
202
+ "deck-id": "btmZUXWM",
203
+ "template-id": "8BtaEAXe",
204
+ "fields": {
205
+ "name": {
206
+ "id": "name",
207
+ "value": "Hello,"
208
+ },
209
+ "JNEnw1e7": {
210
+ "id": "JNEnw1e7",
211
+ "value": "World!"
212
+ },
213
+ },
214
+ }
215
+
216
+ ## Good cards:
217
+ - **concise**: as short question and answer as possible.
218
+ - **focused:** A question or answer involving too much detail will dull your concentration and stimulate incomplete retrievals, leaving some bulbs unlit.
219
+ - **precise** about what they're asking for. Vague questions will elicit vague answers, which won't reliably light the bulbs you're targeting.
220
+ - **consistent** answers, lighting the same bulbs each time you perform the task.
221
+ - **tractable**: Write prompts which you can almost always answer correctly. This often means breaking the task down, or adding cues
222
+ - **effortful**: You shouldn't be able to trivially infer the answer.
223
+ `,
152
224
  inputSchema: zodToJsonSchema(CreateCardRequestSchema),
225
+ annotations: {
226
+ title: "Create flashcard on Mochi",
227
+ readOnlyHint: false,
228
+ destructiveHint: false,
229
+ idempotentHint: false,
230
+ openWorldHint: true,
231
+ },
232
+ },
233
+ {
234
+ name: "mochi_update_card",
235
+ description: `Update an existing flashcard in Mochi.`,
236
+ inputSchema: zodToJsonSchema(z.object({
237
+ "card-id": z.string(),
238
+ ...UpdateCardRequestSchema.shape,
239
+ })),
240
+ annotations: {
241
+ title: "Update flashcard on Mochi",
242
+ readOnlyHint: false,
243
+ destructiveHint: false,
244
+ idempotentHint: false,
245
+ openWorldHint: true,
246
+ },
153
247
  },
154
248
  {
155
249
  name: "mochi_list_cards",
156
250
  description: "List cards in pages of 10 cards per page",
157
251
  inputSchema: zodToJsonSchema(ListCardsParamsSchema),
252
+ annotations: {
253
+ title: "List flashcards on Mochi",
254
+ readOnlyHint: true,
255
+ destructiveHint: false,
256
+ idempotentHint: true,
257
+ openWorldHint: false,
258
+ },
158
259
  },
159
260
  {
160
261
  name: "mochi_list_decks",
161
262
  description: "List all decks",
162
263
  inputSchema: zodToJsonSchema(ListDecksParamsSchema),
264
+ annotations: {
265
+ title: "List decks on Mochi",
266
+ readOnlyHint: true,
267
+ destructiveHint: false,
268
+ idempotentHint: true,
269
+ openWorldHint: false,
270
+ },
271
+ },
272
+ {
273
+ name: "mochi_list_templates",
274
+ description: `Templates can be used to create cards with pre-defined fields using the template_id field.
275
+
276
+ Example response:
277
+ {
278
+ "bookmark": "g1AAAABAeJzLYWBgYMpgSmHgKy5JLCrJTq2MT8lPzkzJBYpzVBn4JgaaVZiC5Dlg8igyWQAxwRHd",
279
+ "docs": [
280
+ {
281
+ "id": "YDELNZSu",
282
+ "name": "Simple flashcard",
283
+ "content": "# << Front >>\n---\n<< Back >>",
284
+ "pos": "s",
285
+ "fields": {
286
+ "name": {
287
+ "id": "name",
288
+ "name": "Front",
289
+ "pos": "a"
290
+ },
291
+ "Ysrde7Lj": {
292
+ "id": "Ysrde7Lj",
293
+ "name": "Back",
294
+ "pos": "m",
295
+ "options": {
296
+ "multi-line?": true
297
+ }
298
+ }
299
+ }
300
+ },
301
+ ...
302
+ ]
303
+ }`,
304
+ inputSchema: zodToJsonSchema(ListTemplatesParamsSchema),
305
+ annotations: {
306
+ title: "List templates on Mochi",
307
+ readOnlyHint: true,
308
+ destructiveHint: false,
309
+ idempotentHint: true,
310
+ openWorldHint: false,
311
+ },
163
312
  },
164
313
  ],
165
314
  }));
315
+ // Create Mochi client
316
+ const mochiClient = new MochiClient(MOCHI_API_KEY);
166
317
  // Add resource handlers
167
318
  server.setRequestHandler(ListResourcesRequestSchema, async () => {
168
- const decks = await mochiClient.listDecks();
169
- return {
170
- resources: decks.docs.map((deck) => ({
171
- uri: `mochi://decks/${deck.id}`,
172
- name: deck.name + ` (Deck ID: ${deck.id})`,
173
- description: `Deck ID: ${deck.id}`,
174
- mimeType: "application/json",
175
- })),
176
- };
177
- });
178
- server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
179
- const uri = request.params.uri;
180
- const match = uri.match(/^mochi:\/\/decks\/(.+)$/);
181
- if (!match) {
182
- throw new Error("Invalid resource URI");
183
- }
184
- const deckId = match[1];
185
- const deck = await mochiClient.listCards({ "deck-id": deckId });
186
319
  return {
187
- contents: [
320
+ resources: [
188
321
  {
189
- uri,
322
+ uri: `mochi://decks`,
323
+ name: "All Mochi Decks",
324
+ description: `List of all decks in Mochi.`,
325
+ mimeType: "application/json",
326
+ },
327
+ {
328
+ uri: `mochi://templates`,
329
+ name: "All Mochi Templates",
330
+ description: `List of all templates in Mochi.`,
190
331
  mimeType: "application/json",
191
- text: JSON.stringify(deck.docs.map((card) => ({
192
- id: card.id,
193
- name: card.name,
194
- content: card.content,
195
- fields: card.fields,
196
- })), null, 2),
197
332
  },
198
333
  ],
199
334
  };
200
335
  });
201
- // Create Mochi client
202
- const mochiClient = new MochiClient(MOCHI_API_KEY);
336
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
337
+ const uri = request.params.uri;
338
+ switch (uri) {
339
+ case "mochi://decks": {
340
+ const decks = await mochiClient.listDecks();
341
+ return {
342
+ contents: [
343
+ {
344
+ uri,
345
+ mimeType: "application/json",
346
+ text: JSON.stringify(decks.map((deck) => ({
347
+ id: deck.id,
348
+ name: deck.name,
349
+ archived: deck.archived,
350
+ })), null, 2),
351
+ },
352
+ ],
353
+ };
354
+ }
355
+ case "mochi://templates": {
356
+ const templates = await mochiClient.listTemplates();
357
+ return {
358
+ contents: [
359
+ {
360
+ uri,
361
+ mimeType: "application/json",
362
+ text: JSON.stringify(templates, null, 2),
363
+ },
364
+ ],
365
+ };
366
+ }
367
+ default: {
368
+ throw new Error("Invalid resource URI");
369
+ }
370
+ }
371
+ });
203
372
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
204
373
  try {
205
374
  switch (request.params.name) {
@@ -216,6 +385,24 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
216
385
  isError: false,
217
386
  };
218
387
  }
388
+ case "mochi_update_card": {
389
+ const { "card-id": cardId, ...updateArgs } = z
390
+ .object({
391
+ "card-id": z.string(),
392
+ ...UpdateCardRequestSchema.shape,
393
+ })
394
+ .parse(request.params.arguments);
395
+ const response = await mochiClient.updateCard(cardId, updateArgs);
396
+ return {
397
+ content: [
398
+ {
399
+ type: "text",
400
+ text: JSON.stringify(response, null, 2),
401
+ },
402
+ ],
403
+ isError: false,
404
+ };
405
+ }
219
406
  case "mochi_list_decks": {
220
407
  const validatedArgs = ListDecksParamsSchema.parse(request.params.arguments);
221
408
  const response = await mochiClient.listDecks(validatedArgs);
@@ -242,6 +429,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
242
429
  isError: false,
243
430
  };
244
431
  }
432
+ case "mochi_list_templates": {
433
+ const validatedArgs = ListTemplatesParamsSchema.parse(request.params.arguments);
434
+ const response = await mochiClient.listTemplates(validatedArgs);
435
+ return {
436
+ content: [
437
+ {
438
+ type: "text",
439
+ text: JSON.stringify(response, null, 2),
440
+ },
441
+ ],
442
+ isError: false,
443
+ };
444
+ }
245
445
  default:
246
446
  return {
247
447
  content: [
@@ -256,13 +456,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
256
456
  }
257
457
  catch (error) {
258
458
  if (error instanceof z.ZodError) {
459
+ const formattedErrors = error.errors.map((err) => {
460
+ const path = err.path.join(".");
461
+ const message = err.code === "invalid_type" && err.message.includes("Required")
462
+ ? `Required field '${path}' is missing`
463
+ : err.message;
464
+ return `${path ? `${path}: ` : ""}${message}`;
465
+ });
259
466
  return {
260
467
  content: [
261
468
  {
262
469
  type: "text",
263
- text: `Validation error: ${error.errors
264
- .map((e) => e.message)
265
- .join(", ")}`,
470
+ text: `Validation error:\n${formattedErrors.join("\n")}`,
266
471
  },
267
472
  ],
268
473
  isError: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fredrika/mcp-mochi",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "MCP server for Mochi flashcard integration",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -1,74 +0,0 @@
1
- export interface Flashcard {
2
- id: string;
3
- name: string;
4
- content: string;
5
- tags: string[];
6
- 'deck-id': string;
7
- fields: Record<string, unknown>;
8
- pos: string;
9
- references: unknown[];
10
- reviews: unknown[];
11
- 'created-at': {
12
- date: string;
13
- };
14
- }
15
- export interface FlashcardInput {
16
- front: string;
17
- back: string;
18
- tags?: string[];
19
- }
20
- export interface ReviewResult {
21
- success: boolean;
22
- timeSpentMs: number;
23
- }
24
- export interface ListFlashcardsParams {
25
- 'deck-id'?: string;
26
- limit?: number;
27
- bookmark?: string;
28
- }
29
- export interface ListFlashcardsResponse {
30
- bookmark: string;
31
- docs: Flashcard[];
32
- }
33
- export interface TemplateField {
34
- id: string;
35
- name: string;
36
- pos: string;
37
- options?: {
38
- 'multi-line?'?: boolean;
39
- };
40
- }
41
- export interface Template {
42
- id: string;
43
- name: string;
44
- content: string;
45
- pos: string;
46
- fields: Record<string, TemplateField>;
47
- }
48
- export interface ListTemplatesResponse {
49
- bookmark: string;
50
- docs: Template[];
51
- }
52
- export interface ListTemplatesParams {
53
- bookmark?: string;
54
- }
55
- export declare class MochiClient {
56
- private api;
57
- private token;
58
- constructor(token: string);
59
- getFlashcards(params?: ListFlashcardsParams): Promise<ListFlashcardsResponse>;
60
- getFlashcard(id: string): Promise<Flashcard>;
61
- createFlashcard(input: FlashcardInput): Promise<Flashcard>;
62
- updateFlashcard(id: string, input: Partial<FlashcardInput>): Promise<Flashcard>;
63
- deleteFlashcard(id: string): Promise<void>;
64
- getDueFlashcards(): Promise<Flashcard[]>;
65
- reviewFlashcard(id: string, result: ReviewResult): Promise<Flashcard>;
66
- getStats(): Promise<{
67
- totalCards: number;
68
- dueCards: number;
69
- averageSuccessRate: number;
70
- cardsReviewedToday: number;
71
- }>;
72
- getTemplate(id: string): Promise<Template>;
73
- listTemplates(params?: ListTemplatesParams): Promise<ListTemplatesResponse>;
74
- }
@@ -1,67 +0,0 @@
1
- import axios from 'axios';
2
- export class MochiClient {
3
- api;
4
- token;
5
- constructor(token) {
6
- this.token = '228b69396efc60896d3033e3';
7
- this.api = axios.create({
8
- baseURL: 'https://app.mochi.cards/api/',
9
- headers: {
10
- 'Authorization': `Basic ${Buffer.from(`${this.token}:`).toString('base64')}`,
11
- 'Content-Type': 'application/json'
12
- }
13
- });
14
- }
15
- async getFlashcards(params) {
16
- try {
17
- const response = await this.api.get('/cards', { params });
18
- console.log(response.data);
19
- return response.data;
20
- }
21
- catch (error) {
22
- console.error('Error fetching flashcards:', error);
23
- throw error;
24
- }
25
- }
26
- async getFlashcard(id) {
27
- const response = await this.api.get(`/cards/${id}`);
28
- return response.data;
29
- }
30
- async createFlashcard(input) {
31
- const response = await this.api.post('/cards', input);
32
- return response.data;
33
- }
34
- async updateFlashcard(id, input) {
35
- const response = await this.api.patch(`/cards/${id}`, input);
36
- return response.data;
37
- }
38
- async deleteFlashcard(id) {
39
- await this.api.delete(`/cards/${id}`);
40
- }
41
- async getDueFlashcards() {
42
- const response = await this.api.get('/cards');
43
- return response.data;
44
- }
45
- async reviewFlashcard(id, result) {
46
- const response = await this.api.post(`/cards/${id}/review`, result);
47
- return response.data;
48
- }
49
- async getStats() {
50
- const response = await this.api.get('/v1/stats');
51
- return response.data;
52
- }
53
- async getTemplate(id) {
54
- const response = await this.api.get(`/templates/${id}`);
55
- return response.data;
56
- }
57
- async listTemplates(params) {
58
- try {
59
- const response = await this.api.get('/templates', { params });
60
- return response.data;
61
- }
62
- catch (error) {
63
- console.error('Error fetching templates:', error);
64
- throw error;
65
- }
66
- }
67
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,69 +0,0 @@
1
- import axios from 'axios';
2
- import { MochiClient } from './mochi-client.js';
3
- import { config } from 'dotenv';
4
- // Load environment variables
5
- config();
6
- const MOCHI_TOKEN = process.env.MOCHI_TOKEN;
7
- if (!MOCHI_TOKEN) {
8
- throw new Error("MOCHI_TOKEN environment variable is required");
9
- }
10
- // After the check above, TypeScript knows MOCHI_TOKEN is defined
11
- const token = MOCHI_TOKEN;
12
- async function testMochiApi() {
13
- console.log("Testing Mochi API connection...");
14
- console.log("Using token:", token.slice(0, 5) + "..." + token.slice(-5));
15
- const client = new MochiClient(token);
16
- try {
17
- // Test getting stats (simplest operation)
18
- console.log("\nTesting getStats...");
19
- const stats = await client.getStats();
20
- console.log("Stats response:", stats);
21
- // Test getting flashcards
22
- console.log("\nTesting getFlashcards...");
23
- const cards = await client.getFlashcards();
24
- console.log("Flashcards response:", cards);
25
- // Test creating a flashcard
26
- console.log("\nTesting createFlashcard...");
27
- const newCard = await client.createFlashcard({
28
- front: "Test Front",
29
- back: "Test Back",
30
- tags: ["test"]
31
- });
32
- console.log("Created card:", newCard);
33
- // Test getting a specific flashcard
34
- console.log("\nTesting getFlashcard...");
35
- const card = await client.getFlashcard(newCard.id);
36
- console.log("Retrieved card:", card);
37
- // Test updating the flashcard
38
- console.log("\nTesting updateFlashcard...");
39
- const updatedCard = await client.updateFlashcard(newCard.id, {
40
- front: "Updated Front"
41
- });
42
- console.log("Updated card:", updatedCard);
43
- // Test deleting the flashcard
44
- console.log("\nTesting deleteFlashcard...");
45
- await client.deleteFlashcard(newCard.id);
46
- console.log("Card deleted successfully");
47
- }
48
- catch (error) {
49
- console.error("Error testing Mochi API:", error);
50
- if (axios.isAxiosError(error)) {
51
- const axiosError = error;
52
- if (axiosError.response) {
53
- console.error("Response status:", axiosError.response.status);
54
- console.error("Response data:", axiosError.response.data);
55
- console.error("Response headers:", axiosError.response.headers);
56
- console.error("Request URL:", axiosError.config?.url);
57
- console.error("Request method:", axiosError.config?.method);
58
- }
59
- else if (axiosError.request) {
60
- console.error("No response received. Request details:", axiosError.request);
61
- }
62
- else {
63
- console.error("Error setting up request:", axiosError.message);
64
- }
65
- }
66
- }
67
- }
68
- // Run the test
69
- testMochiApi().catch(console.error);