@fredrika/mcp-mochi 1.0.0 → 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.
package/README.md CHANGED
@@ -114,7 +114,7 @@ To use this with Claude Desktop, add the following to your `claude_desktop_confi
114
114
  ```json
115
115
  {
116
116
  "mcpServers": {
117
- "github": {
117
+ "mochi": {
118
118
  "command": "docker",
119
119
  "args": [
120
120
  "run",
@@ -137,11 +137,11 @@ To use this with Claude Desktop, add the following to your `claude_desktop_confi
137
137
  ```json
138
138
  {
139
139
  "mcpServers": {
140
- "github": {
140
+ "mochi": {
141
141
  "command": "npx",
142
142
  "args": [
143
143
  "-y",
144
- "@modelcontextprotocol/server-github"
144
+ "@fredrika/mcp-mochi"
145
145
  ],
146
146
  "env": {
147
147
  "MOCHI_API_KEY": "<YOUR_TOKEN>"
@@ -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
@@ -5,6 +5,7 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.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";
8
9
  dotenv.config();
9
10
  /**
10
11
  * Custom error class for Mochi API errors
@@ -34,10 +35,15 @@ const CreateCardRequestSchema = z.object({
34
35
  content: z.string().min(1),
35
36
  "deck-id": z.string().min(1),
36
37
  "template-id": z.string().optional(),
37
- archived: z.boolean().optional(),
38
- "review-reverse": z.boolean().optional(),
39
- pos: z.string().optional(),
40
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(),
41
47
  fields: z.record(z.string(), CreateCardFieldSchema).optional(),
42
48
  });
43
49
  const ListDecksParamsSchema = z.object({
@@ -48,27 +54,35 @@ const ListCardsParamsSchema = z.object({
48
54
  limit: z.number().min(1).max(100).optional(),
49
55
  bookmark: z.string().optional(),
50
56
  });
51
- // Response Zod schemas
52
- const CreateCardResponseSchema = z
53
- .object({
54
- status: z.string(),
55
- error_message: z.string().optional(),
56
- })
57
- .strip();
58
- 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
59
71
  .object({
60
72
  id: z.string(),
61
- sort: z.number(),
62
73
  name: z.string(),
63
- archived: z.boolean().optional(),
74
+ content: z.string(),
75
+ pos: z.string(),
76
+ fields: z.record(z.string(), TemplateFieldSchema),
64
77
  })
65
78
  .strip();
66
- const ListDecksResponseSchema = z
79
+ const ListTemplatesResponseSchema = z
67
80
  .object({
68
81
  bookmark: z.string(),
69
- docs: z.array(DeckSchema),
82
+ docs: z.array(TemplateSchema),
70
83
  })
71
84
  .strip();
85
+ // Response Zod schemas
72
86
  const CardSchema = z
73
87
  .object({
74
88
  id: z.string(),
@@ -76,21 +90,30 @@ const CardSchema = z
76
90
  content: z.string(),
77
91
  name: z.string(),
78
92
  "deck-id": z.string(),
79
- fields: z.record(z.unknown()),
80
- pos: z.string(),
81
- references: z.array(z.unknown()),
82
- reviews: z.array(z.unknown()),
83
- "created-at": z.object({
84
- date: z.string(),
85
- }),
93
+ fields: z.record(z.unknown()).optional(),
86
94
  })
87
95
  .strip();
96
+ const CreateCardResponseSchema = CardSchema.strip();
88
97
  const ListCardsResponseSchema = z
89
98
  .object({
90
99
  bookmark: z.string(),
91
100
  docs: z.array(CardSchema),
92
101
  })
93
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();
94
117
  function getApiKey() {
95
118
  const apiKey = process.env.MOCHI_API_KEY;
96
119
  if (!apiKey) {
@@ -100,59 +123,6 @@ function getApiKey() {
100
123
  return apiKey;
101
124
  }
102
125
  const MOCHI_API_KEY = getApiKey();
103
- // Tool definitions
104
- const CREATE_CARD_TOOL = {
105
- name: "mochi_create_card",
106
- description: "Create a new flashcard",
107
- inputSchema: {
108
- type: "object",
109
- properties: {
110
- content: {
111
- type: "string",
112
- description: "The markdown content of the card. Separate front from back with `\n---\n`",
113
- },
114
- "deck-id": {
115
- type: "string",
116
- description: "The deck ID that the card belongs too.",
117
- },
118
- },
119
- required: ["content", "deck-id"],
120
- },
121
- };
122
- const LIST_CARDS_TOOL = {
123
- name: "mochi_list_cards",
124
- description: "List cards in pages of 10 cards per page",
125
- inputSchema: {
126
- type: "object",
127
- properties: {
128
- "deck-id": {
129
- type: "string",
130
- description: "Only return cards for the specified deck ID",
131
- },
132
- limit: {
133
- type: "number",
134
- description: "Number of cards to return per page (1-100, default 10)",
135
- },
136
- bookmark: {
137
- type: "string",
138
- description: "Cursor for pagination from a previous list request",
139
- },
140
- },
141
- },
142
- };
143
- const LIST_DECKS_TOOL = {
144
- name: "mochi_list_decks",
145
- description: "List all decks",
146
- inputSchema: {
147
- type: "object",
148
- properties: {},
149
- },
150
- };
151
- const MOCHI_TOOLS = [
152
- CREATE_CARD_TOOL,
153
- LIST_CARDS_TOOL,
154
- LIST_DECKS_TOOL,
155
- ];
156
126
  export class MochiClient {
157
127
  api;
158
128
  token;
@@ -170,12 +140,16 @@ export class MochiClient {
170
140
  const response = await this.api.post("/cards", request);
171
141
  return CreateCardResponseSchema.parse(response.data);
172
142
  }
143
+ async updateCard(cardId, request) {
144
+ const response = await this.api.post(`/cards/${cardId}`, request);
145
+ return CreateCardResponseSchema.parse(response.data);
146
+ }
173
147
  async listDecks(params) {
174
148
  const validatedParams = params
175
149
  ? ListDecksParamsSchema.parse(params)
176
150
  : undefined;
177
151
  const response = await this.api.get("/decks", { params: validatedParams });
178
- return ListDecksResponseSchema.parse(response.data);
152
+ return ListDecksResponseSchema.parse(response.data).docs.filter((deck) => !deck.archived);
179
153
  }
180
154
  async listCards(params) {
181
155
  const validatedParams = params
@@ -184,11 +158,20 @@ export class MochiClient {
184
158
  const response = await this.api.get("/cards", { params: validatedParams });
185
159
  return ListCardsResponseSchema.parse(response.data);
186
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
+ }
187
170
  }
188
171
  // Server setup
189
172
  const server = new Server({
190
173
  name: "mcp-server/mochi",
191
- version: "0.1.0",
174
+ version: "1.0.2",
192
175
  }, {
193
176
  capabilities: {
194
177
  tools: {},
@@ -197,59 +180,267 @@ const server = new Server({
197
180
  });
198
181
  // Set up request handlers
199
182
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
200
- tools: MOCHI_TOOLS,
183
+ tools: [
184
+ {
185
+ name: "mochi_create_card",
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
+ `,
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
+ },
247
+ },
248
+ {
249
+ name: "mochi_list_cards",
250
+ description: "List cards in pages of 10 cards per page",
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
+ },
259
+ },
260
+ {
261
+ name: "mochi_list_decks",
262
+ description: "List all decks",
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
+ },
312
+ },
313
+ ],
201
314
  }));
315
+ // Create Mochi client
316
+ const mochiClient = new MochiClient(MOCHI_API_KEY);
202
317
  // Add resource handlers
203
318
  server.setRequestHandler(ListResourcesRequestSchema, async () => {
204
- const decks = await mochiClient.listDecks();
205
- return {
206
- resources: decks.docs.map((deck) => ({
207
- uri: `mochi://decks/${deck.id}`,
208
- name: deck.name + ` (Deck ID: ${deck.id})`,
209
- description: `Deck ID: ${deck.id}`,
210
- mimeType: "application/json",
211
- })),
212
- };
213
- });
214
- server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
215
- const uri = request.params.uri;
216
- const match = uri.match(/^mochi:\/\/decks\/(.+)$/);
217
- if (!match) {
218
- throw new Error("Invalid resource URI");
219
- }
220
- const deckId = match[1];
221
- const deck = await mochiClient.listCards({ "deck-id": deckId });
222
319
  return {
223
- contents: [
320
+ resources: [
224
321
  {
225
- 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.`,
226
331
  mimeType: "application/json",
227
- text: JSON.stringify(deck.docs.map((card) => ({
228
- id: card.id,
229
- name: card.name,
230
- content: card.content,
231
- fields: card.fields,
232
- })), null, 2),
233
332
  },
234
333
  ],
235
334
  };
236
335
  });
237
- // Create Mochi client
238
- 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
+ });
239
372
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
240
373
  try {
241
374
  switch (request.params.name) {
242
375
  case "mochi_create_card": {
243
376
  const validatedArgs = CreateCardRequestSchema.parse(request.params.arguments);
244
- return mochiClient.createCard(validatedArgs);
377
+ const response = await mochiClient.createCard(validatedArgs);
378
+ return {
379
+ content: [
380
+ {
381
+ type: "text",
382
+ text: JSON.stringify(response, null, 2),
383
+ },
384
+ ],
385
+ isError: false,
386
+ };
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
+ };
245
405
  }
246
406
  case "mochi_list_decks": {
247
407
  const validatedArgs = ListDecksParamsSchema.parse(request.params.arguments);
248
- return mochiClient.listDecks(validatedArgs);
408
+ const response = await mochiClient.listDecks(validatedArgs);
409
+ return {
410
+ content: [
411
+ {
412
+ type: "text",
413
+ text: JSON.stringify(response, null, 2),
414
+ },
415
+ ],
416
+ isError: false,
417
+ };
249
418
  }
250
419
  case "mochi_list_cards": {
251
420
  const validatedArgs = ListCardsParamsSchema.parse(request.params.arguments);
252
- return mochiClient.listCards(validatedArgs);
421
+ const response = await mochiClient.listCards(validatedArgs);
422
+ return {
423
+ content: [
424
+ {
425
+ type: "text",
426
+ text: JSON.stringify(response, null, 2),
427
+ },
428
+ ],
429
+ isError: false,
430
+ };
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
+ };
253
444
  }
254
445
  default:
255
446
  return {
@@ -265,13 +456,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
265
456
  }
266
457
  catch (error) {
267
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
+ });
268
466
  return {
269
467
  content: [
270
468
  {
271
469
  type: "text",
272
- text: `Validation error: ${error.errors
273
- .map((e) => e.message)
274
- .join(", ")}`,
470
+ text: `Validation error:\n${formattedErrors.join("\n")}`,
275
471
  },
276
472
  ],
277
473
  isError: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fredrika/mcp-mochi",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "MCP server for Mochi flashcard integration",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -15,7 +15,7 @@
15
15
  "build": "tsc",
16
16
  "start": "node dist/index.js",
17
17
  "dev": "ts-node-esm src/index.ts",
18
- "dev:inspect": "npx @modelcontextprotocol/inspector ts-node-esm src/mochi.ts",
18
+ "dev:inspect": "npx @modelcontextprotocol/inspector ts-node-esm src/index.ts",
19
19
  "test": "jest",
20
20
  "prepublishOnly": "npm run build"
21
21
  },
@@ -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);