@fredrika/mcp-mochi 1.0.5 → 1.0.6-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +112 -89
- package/dist/index.d.ts +218 -28
- package/dist/index.js +318 -12
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -5,126 +5,149 @@ This MCP server provides integration with the Mochi flashcard system, allowing y
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- Create, update, and delete flashcards
|
|
8
|
+
- Create cards from templates with automatic field name-to-ID mapping
|
|
9
|
+
- Add attachments (images, audio) to cards
|
|
10
|
+
- Get cards due for review
|
|
8
11
|
- List flashcards, decks, and templates
|
|
9
12
|
|
|
10
|
-
##
|
|
13
|
+
## Usage with Claude Desktop
|
|
14
|
+
|
|
15
|
+
Add the following to your `claude_desktop_config.json`:
|
|
16
|
+
|
|
17
|
+
### NPX (recommended)
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"mcpServers": {
|
|
22
|
+
"mochi": {
|
|
23
|
+
"command": "npx",
|
|
24
|
+
"args": ["-y", "@fredrika/mcp-mochi"],
|
|
25
|
+
"env": {
|
|
26
|
+
"MOCHI_API_KEY": "<YOUR_TOKEN>"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Local Development
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"mcpServers": {
|
|
38
|
+
"mochi": {
|
|
39
|
+
"command": "node",
|
|
40
|
+
"args": ["/path/to/mcp-mochi/dist/index.js"],
|
|
41
|
+
"env": {
|
|
42
|
+
"MOCHI_API_KEY": "<YOUR_TOKEN>"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Local Development Setup
|
|
11
50
|
|
|
12
|
-
1.
|
|
51
|
+
1. Clone and install dependencies:
|
|
13
52
|
```bash
|
|
53
|
+
git clone https://github.com/fredrika/mcp-mochi.git
|
|
54
|
+
cd mcp-mochi
|
|
14
55
|
npm install
|
|
15
56
|
```
|
|
16
57
|
|
|
17
|
-
2.
|
|
18
|
-
- Copy `.env.example` to `.env`
|
|
19
|
-
- Replace `your_mochi_api_token_here` with your actual Mochi API token
|
|
20
|
-
|
|
21
|
-
3. Build the project:
|
|
58
|
+
2. Build the project:
|
|
22
59
|
```bash
|
|
23
60
|
npm run build
|
|
24
61
|
```
|
|
25
62
|
|
|
26
|
-
|
|
63
|
+
3. Test with MCP Inspector:
|
|
27
64
|
```bash
|
|
28
|
-
|
|
65
|
+
MOCHI_API_KEY=<YOUR_TOKEN> npx @modelcontextprotocol/inspector node dist/index.js
|
|
29
66
|
```
|
|
30
67
|
|
|
31
68
|
## Available Tools
|
|
32
69
|
|
|
33
|
-
### `
|
|
70
|
+
### `mochi_create_flashcard`
|
|
34
71
|
Create a new flashcard in Mochi.
|
|
35
|
-
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
72
|
+
- `content`: Markdown content (separate front/back with `---`)
|
|
73
|
+
- `deck-id`: ID of the deck (use `mochi_list_decks` to find)
|
|
74
|
+
- `template-id`: (optional) Template to use
|
|
75
|
+
- `fields`: (optional) Map of field IDs to values (required with template)
|
|
76
|
+
- `manual-tags`: (optional) Array of tags
|
|
77
|
+
|
|
78
|
+
### `mochi_create_card_from_template`
|
|
79
|
+
Create a flashcard using a template with field **names** (not IDs). The MCP automatically maps names to IDs.
|
|
80
|
+
- `template-id`: Template ID (use `mochi_list_templates` to find)
|
|
81
|
+
- `deck-id`: Deck ID
|
|
82
|
+
- `fields`: Map of field names to values (e.g., `{"Front": "Question?", "Back": "Answer"}`)
|
|
83
|
+
- `manual-tags`: (optional) Array of tags
|
|
84
|
+
|
|
85
|
+
### `mochi_update_flashcard`
|
|
86
|
+
Update or delete a flashcard. Set `trashed?` to `true` to delete.
|
|
87
|
+
- `card-id`: ID of the card to update
|
|
88
|
+
- Any updatable card fields
|
|
89
|
+
|
|
90
|
+
### `mochi_add_attachment`
|
|
91
|
+
Add an attachment (image, audio, etc.) to a card using base64 data.
|
|
92
|
+
- `card-id`: ID of the card
|
|
93
|
+
- `data`: Base64-encoded file data
|
|
94
|
+
- `filename`: Filename with extension (e.g., `image.png`)
|
|
95
|
+
- `content-type`: (optional) MIME type (inferred from filename if omitted)
|
|
96
|
+
|
|
97
|
+
### `mochi_list_flashcards`
|
|
98
|
+
List flashcards (paginated).
|
|
99
|
+
- `deck-id`: (optional) Filter by deck
|
|
100
|
+
- `limit`: (optional) 1-100
|
|
101
|
+
- `bookmark`: (optional) Pagination token
|
|
55
102
|
|
|
56
103
|
### `mochi_list_decks`
|
|
57
104
|
List all decks.
|
|
58
|
-
-
|
|
59
|
-
- `bookmark`: string (optional, for pagination)
|
|
105
|
+
- `bookmark`: (optional) Pagination token
|
|
60
106
|
|
|
61
107
|
### `mochi_list_templates`
|
|
62
|
-
List all templates.
|
|
63
|
-
-
|
|
64
|
-
- `bookmark`: string (optional, for pagination)
|
|
65
|
-
|
|
66
|
-
## Example Usage
|
|
108
|
+
List all templates with their field definitions.
|
|
109
|
+
- `bookmark`: (optional) Pagination token
|
|
67
110
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
npm start
|
|
73
|
-
```
|
|
111
|
+
### `mochi_get_due_cards`
|
|
112
|
+
Get flashcards due for review.
|
|
113
|
+
- `deck-id`: (optional) Filter by deck
|
|
114
|
+
- `date`: (optional) ISO 8601 date (defaults to today)
|
|
74
115
|
|
|
75
|
-
|
|
76
|
-
```bash
|
|
77
|
-
mcp-inspector
|
|
78
|
-
```
|
|
116
|
+
## Examples
|
|
79
117
|
|
|
80
|
-
|
|
81
|
-
```json
|
|
82
|
-
{
|
|
83
|
-
"tool": "mochi_create_card",
|
|
84
|
-
"params": {
|
|
85
|
-
"content": "What is MCP?\n---\nModel Context Protocol - a protocol for providing context to LLMs",
|
|
86
|
-
"deck-id": "<YOUR_DECK_ID>"
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
```
|
|
118
|
+
### Create a simple flashcard
|
|
90
119
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
```json
|
|
101
|
-
{
|
|
102
|
-
"tool": "mochi_update_card",
|
|
103
|
-
"params": {
|
|
104
|
-
"card-id": "<CARD_ID>",
|
|
105
|
-
"trashed?": "true"
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
## Usage with Claude Desktop
|
|
111
|
-
To use this with Claude Desktop, add the following to your `claude_desktop_config.json`:
|
|
120
|
+
```json
|
|
121
|
+
{
|
|
122
|
+
"tool": "mochi_create_flashcard",
|
|
123
|
+
"params": {
|
|
124
|
+
"content": "What is MCP?\n---\nModel Context Protocol - a protocol for providing context to LLMs",
|
|
125
|
+
"deck-id": "<DECK_ID>"
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
112
129
|
|
|
113
|
-
###
|
|
130
|
+
### Create a card from template
|
|
114
131
|
|
|
115
132
|
```json
|
|
116
133
|
{
|
|
117
|
-
"
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
"env": {
|
|
125
|
-
"MOCHI_API_KEY": "<YOUR_TOKEN>"
|
|
126
|
-
}
|
|
134
|
+
"tool": "mochi_create_card_from_template",
|
|
135
|
+
"params": {
|
|
136
|
+
"template-id": "<TEMPLATE_ID>",
|
|
137
|
+
"deck-id": "<DECK_ID>",
|
|
138
|
+
"fields": {
|
|
139
|
+
"Front": "What is the capital of France?",
|
|
140
|
+
"Back": "Paris"
|
|
127
141
|
}
|
|
128
142
|
}
|
|
129
143
|
}
|
|
130
144
|
```
|
|
145
|
+
|
|
146
|
+
### Get today's due cards
|
|
147
|
+
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"tool": "mochi_get_due_cards",
|
|
151
|
+
"params": {}
|
|
152
|
+
}
|
|
153
|
+
```
|
package/dist/index.d.ts
CHANGED
|
@@ -98,6 +98,117 @@ declare const ListTemplatesParamsSchema: z.ZodObject<{
|
|
|
98
98
|
}, {
|
|
99
99
|
bookmark?: string | undefined;
|
|
100
100
|
}>;
|
|
101
|
+
declare const GetDueCardsParamsSchema: z.ZodObject<{
|
|
102
|
+
"deck-id": z.ZodOptional<z.ZodString>;
|
|
103
|
+
date: z.ZodOptional<z.ZodString>;
|
|
104
|
+
}, "strip", z.ZodTypeAny, {
|
|
105
|
+
date?: string | undefined;
|
|
106
|
+
"deck-id"?: string | undefined;
|
|
107
|
+
}, {
|
|
108
|
+
date?: string | undefined;
|
|
109
|
+
"deck-id"?: string | undefined;
|
|
110
|
+
}>;
|
|
111
|
+
declare const CreateCardFromTemplateSchema: z.ZodObject<{
|
|
112
|
+
"template-id": z.ZodString;
|
|
113
|
+
"deck-id": z.ZodString;
|
|
114
|
+
fields: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
115
|
+
"manual-tags": z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
116
|
+
}, "strip", z.ZodTypeAny, {
|
|
117
|
+
"deck-id": string;
|
|
118
|
+
"template-id": string;
|
|
119
|
+
fields: Record<string, string>;
|
|
120
|
+
"manual-tags"?: string[] | undefined;
|
|
121
|
+
}, {
|
|
122
|
+
"deck-id": string;
|
|
123
|
+
"template-id": string;
|
|
124
|
+
fields: Record<string, string>;
|
|
125
|
+
"manual-tags"?: string[] | undefined;
|
|
126
|
+
}>;
|
|
127
|
+
declare const AddAttachmentSchema: z.ZodObject<{
|
|
128
|
+
"card-id": z.ZodString;
|
|
129
|
+
data: z.ZodString;
|
|
130
|
+
filename: z.ZodString;
|
|
131
|
+
"content-type": z.ZodOptional<z.ZodString>;
|
|
132
|
+
}, "strip", z.ZodTypeAny, {
|
|
133
|
+
data: string;
|
|
134
|
+
filename: string;
|
|
135
|
+
"card-id": string;
|
|
136
|
+
"content-type"?: string | undefined;
|
|
137
|
+
}, {
|
|
138
|
+
data: string;
|
|
139
|
+
filename: string;
|
|
140
|
+
"card-id": string;
|
|
141
|
+
"content-type"?: string | undefined;
|
|
142
|
+
}>;
|
|
143
|
+
type AddAttachmentRequest = z.infer<typeof AddAttachmentSchema>;
|
|
144
|
+
declare const TemplateSchema: z.ZodObject<{
|
|
145
|
+
id: z.ZodString;
|
|
146
|
+
name: z.ZodString;
|
|
147
|
+
content: z.ZodString;
|
|
148
|
+
pos: z.ZodString;
|
|
149
|
+
fields: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
150
|
+
id: z.ZodString;
|
|
151
|
+
name: z.ZodString;
|
|
152
|
+
pos: z.ZodString;
|
|
153
|
+
type: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
154
|
+
source: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
155
|
+
options: z.ZodOptional<z.ZodObject<{
|
|
156
|
+
"multi-line?": z.ZodOptional<z.ZodBoolean>;
|
|
157
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
158
|
+
"multi-line?": z.ZodOptional<z.ZodBoolean>;
|
|
159
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
160
|
+
"multi-line?": z.ZodOptional<z.ZodBoolean>;
|
|
161
|
+
}, z.ZodTypeAny, "passthrough">>>;
|
|
162
|
+
}, "strip", z.ZodTypeAny, {
|
|
163
|
+
name: string;
|
|
164
|
+
id: string;
|
|
165
|
+
pos: string;
|
|
166
|
+
type?: string | null | undefined;
|
|
167
|
+
source?: string | null | undefined;
|
|
168
|
+
options?: z.objectOutputType<{
|
|
169
|
+
"multi-line?": z.ZodOptional<z.ZodBoolean>;
|
|
170
|
+
}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
171
|
+
}, {
|
|
172
|
+
name: string;
|
|
173
|
+
id: string;
|
|
174
|
+
pos: string;
|
|
175
|
+
type?: string | null | undefined;
|
|
176
|
+
source?: string | null | undefined;
|
|
177
|
+
options?: z.objectInputType<{
|
|
178
|
+
"multi-line?": z.ZodOptional<z.ZodBoolean>;
|
|
179
|
+
}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
180
|
+
}>>;
|
|
181
|
+
}, "strip", z.ZodTypeAny, {
|
|
182
|
+
name: string;
|
|
183
|
+
id: string;
|
|
184
|
+
content: string;
|
|
185
|
+
fields: Record<string, {
|
|
186
|
+
name: string;
|
|
187
|
+
id: string;
|
|
188
|
+
pos: string;
|
|
189
|
+
type?: string | null | undefined;
|
|
190
|
+
source?: string | null | undefined;
|
|
191
|
+
options?: z.objectOutputType<{
|
|
192
|
+
"multi-line?": z.ZodOptional<z.ZodBoolean>;
|
|
193
|
+
}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
194
|
+
}>;
|
|
195
|
+
pos: string;
|
|
196
|
+
}, {
|
|
197
|
+
name: string;
|
|
198
|
+
id: string;
|
|
199
|
+
content: string;
|
|
200
|
+
fields: Record<string, {
|
|
201
|
+
name: string;
|
|
202
|
+
id: string;
|
|
203
|
+
pos: string;
|
|
204
|
+
type?: string | null | undefined;
|
|
205
|
+
source?: string | null | undefined;
|
|
206
|
+
options?: z.objectInputType<{
|
|
207
|
+
"multi-line?": z.ZodOptional<z.ZodBoolean>;
|
|
208
|
+
}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
209
|
+
}>;
|
|
210
|
+
pos: string;
|
|
211
|
+
}>;
|
|
101
212
|
declare const ListTemplatesResponseSchema: z.ZodObject<{
|
|
102
213
|
bookmark: z.ZodString;
|
|
103
214
|
docs: z.ZodArray<z.ZodObject<{
|
|
@@ -109,27 +220,33 @@ declare const ListTemplatesResponseSchema: z.ZodObject<{
|
|
|
109
220
|
id: z.ZodString;
|
|
110
221
|
name: z.ZodString;
|
|
111
222
|
pos: z.ZodString;
|
|
223
|
+
type: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
224
|
+
source: z.ZodNullable<z.ZodOptional<z.ZodString>>;
|
|
112
225
|
options: z.ZodOptional<z.ZodObject<{
|
|
113
226
|
"multi-line?": z.ZodOptional<z.ZodBoolean>;
|
|
114
|
-
}, "
|
|
115
|
-
"multi-line?"
|
|
116
|
-
}, {
|
|
117
|
-
"multi-line?"
|
|
118
|
-
}
|
|
227
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
228
|
+
"multi-line?": z.ZodOptional<z.ZodBoolean>;
|
|
229
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
230
|
+
"multi-line?": z.ZodOptional<z.ZodBoolean>;
|
|
231
|
+
}, z.ZodTypeAny, "passthrough">>>;
|
|
119
232
|
}, "strip", z.ZodTypeAny, {
|
|
120
233
|
name: string;
|
|
121
234
|
id: string;
|
|
122
235
|
pos: string;
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
236
|
+
type?: string | null | undefined;
|
|
237
|
+
source?: string | null | undefined;
|
|
238
|
+
options?: z.objectOutputType<{
|
|
239
|
+
"multi-line?": z.ZodOptional<z.ZodBoolean>;
|
|
240
|
+
}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
126
241
|
}, {
|
|
127
242
|
name: string;
|
|
128
243
|
id: string;
|
|
129
244
|
pos: string;
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
245
|
+
type?: string | null | undefined;
|
|
246
|
+
source?: string | null | undefined;
|
|
247
|
+
options?: z.objectInputType<{
|
|
248
|
+
"multi-line?": z.ZodOptional<z.ZodBoolean>;
|
|
249
|
+
}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
133
250
|
}>>;
|
|
134
251
|
}, "strip", z.ZodTypeAny, {
|
|
135
252
|
name: string;
|
|
@@ -139,9 +256,11 @@ declare const ListTemplatesResponseSchema: z.ZodObject<{
|
|
|
139
256
|
name: string;
|
|
140
257
|
id: string;
|
|
141
258
|
pos: string;
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
259
|
+
type?: string | null | undefined;
|
|
260
|
+
source?: string | null | undefined;
|
|
261
|
+
options?: z.objectOutputType<{
|
|
262
|
+
"multi-line?": z.ZodOptional<z.ZodBoolean>;
|
|
263
|
+
}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
145
264
|
}>;
|
|
146
265
|
pos: string;
|
|
147
266
|
}, {
|
|
@@ -152,9 +271,11 @@ declare const ListTemplatesResponseSchema: z.ZodObject<{
|
|
|
152
271
|
name: string;
|
|
153
272
|
id: string;
|
|
154
273
|
pos: string;
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
274
|
+
type?: string | null | undefined;
|
|
275
|
+
source?: string | null | undefined;
|
|
276
|
+
options?: z.objectInputType<{
|
|
277
|
+
"multi-line?": z.ZodOptional<z.ZodBoolean>;
|
|
278
|
+
}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
158
279
|
}>;
|
|
159
280
|
pos: string;
|
|
160
281
|
}>, "many">;
|
|
@@ -168,9 +289,11 @@ declare const ListTemplatesResponseSchema: z.ZodObject<{
|
|
|
168
289
|
name: string;
|
|
169
290
|
id: string;
|
|
170
291
|
pos: string;
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
292
|
+
type?: string | null | undefined;
|
|
293
|
+
source?: string | null | undefined;
|
|
294
|
+
options?: z.objectOutputType<{
|
|
295
|
+
"multi-line?": z.ZodOptional<z.ZodBoolean>;
|
|
296
|
+
}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
174
297
|
}>;
|
|
175
298
|
pos: string;
|
|
176
299
|
}[];
|
|
@@ -184,9 +307,11 @@ declare const ListTemplatesResponseSchema: z.ZodObject<{
|
|
|
184
307
|
name: string;
|
|
185
308
|
id: string;
|
|
186
309
|
pos: string;
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
310
|
+
type?: string | null | undefined;
|
|
311
|
+
source?: string | null | undefined;
|
|
312
|
+
options?: z.objectInputType<{
|
|
313
|
+
"multi-line?": z.ZodOptional<z.ZodBoolean>;
|
|
314
|
+
}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
190
315
|
}>;
|
|
191
316
|
pos: string;
|
|
192
317
|
}[];
|
|
@@ -197,6 +322,8 @@ type ListCardsParams = z.infer<typeof ListCardsParamsSchema>;
|
|
|
197
322
|
type ListDecksParams = z.infer<typeof ListDecksParamsSchema>;
|
|
198
323
|
type CreateCardRequest = z.infer<typeof CreateCardRequestSchema>;
|
|
199
324
|
type UpdateCardRequest = z.infer<typeof UpdateCardRequestSchema>;
|
|
325
|
+
type GetDueCardsParams = z.infer<typeof GetDueCardsParamsSchema>;
|
|
326
|
+
type CreateCardFromTemplateParams = z.infer<typeof CreateCardFromTemplateSchema>;
|
|
200
327
|
declare const CreateCardResponseSchema: z.ZodObject<{
|
|
201
328
|
id: z.ZodString;
|
|
202
329
|
tags: z.ZodArray<z.ZodString, "many">;
|
|
@@ -273,17 +400,30 @@ declare const ListDecksResponseSchema: z.ZodObject<{
|
|
|
273
400
|
id: z.ZodString;
|
|
274
401
|
sort: z.ZodNumber;
|
|
275
402
|
name: z.ZodString;
|
|
276
|
-
archived: z.ZodOptional<z.ZodBoolean
|
|
403
|
+
"archived?": z.ZodNullable<z.ZodOptional<z.ZodBoolean>>;
|
|
404
|
+
"trashed?": z.ZodNullable<z.ZodOptional<z.ZodObject<{
|
|
405
|
+
date: z.ZodString;
|
|
406
|
+
}, "strip", z.ZodTypeAny, {
|
|
407
|
+
date: string;
|
|
408
|
+
}, {
|
|
409
|
+
date: string;
|
|
410
|
+
}>>>;
|
|
277
411
|
}, "strip", z.ZodTypeAny, {
|
|
278
412
|
sort: number;
|
|
279
413
|
name: string;
|
|
280
414
|
id: string;
|
|
281
|
-
archived?: boolean | undefined;
|
|
415
|
+
"archived?"?: boolean | null | undefined;
|
|
416
|
+
"trashed?"?: {
|
|
417
|
+
date: string;
|
|
418
|
+
} | null | undefined;
|
|
282
419
|
}, {
|
|
283
420
|
sort: number;
|
|
284
421
|
name: string;
|
|
285
422
|
id: string;
|
|
286
|
-
archived?: boolean | undefined;
|
|
423
|
+
"archived?"?: boolean | null | undefined;
|
|
424
|
+
"trashed?"?: {
|
|
425
|
+
date: string;
|
|
426
|
+
} | null | undefined;
|
|
287
427
|
}>, "many">;
|
|
288
428
|
}, "strip", z.ZodTypeAny, {
|
|
289
429
|
bookmark: string;
|
|
@@ -291,7 +431,10 @@ declare const ListDecksResponseSchema: z.ZodObject<{
|
|
|
291
431
|
sort: number;
|
|
292
432
|
name: string;
|
|
293
433
|
id: string;
|
|
294
|
-
archived?: boolean | undefined;
|
|
434
|
+
"archived?"?: boolean | null | undefined;
|
|
435
|
+
"trashed?"?: {
|
|
436
|
+
date: string;
|
|
437
|
+
} | null | undefined;
|
|
295
438
|
}[];
|
|
296
439
|
}, {
|
|
297
440
|
bookmark: string;
|
|
@@ -299,9 +442,49 @@ declare const ListDecksResponseSchema: z.ZodObject<{
|
|
|
299
442
|
sort: number;
|
|
300
443
|
name: string;
|
|
301
444
|
id: string;
|
|
302
|
-
archived?: boolean | undefined;
|
|
445
|
+
"archived?"?: boolean | null | undefined;
|
|
446
|
+
"trashed?"?: {
|
|
447
|
+
date: string;
|
|
448
|
+
} | null | undefined;
|
|
303
449
|
}[];
|
|
304
450
|
}>;
|
|
451
|
+
declare const GetDueCardsResponseSchema: z.ZodObject<{
|
|
452
|
+
cards: z.ZodArray<z.ZodObject<{
|
|
453
|
+
id: z.ZodString;
|
|
454
|
+
content: z.ZodString;
|
|
455
|
+
name: z.ZodString;
|
|
456
|
+
"deck-id": z.ZodString;
|
|
457
|
+
"new?": z.ZodBoolean;
|
|
458
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
459
|
+
id: z.ZodString;
|
|
460
|
+
content: z.ZodString;
|
|
461
|
+
name: z.ZodString;
|
|
462
|
+
"deck-id": z.ZodString;
|
|
463
|
+
"new?": z.ZodBoolean;
|
|
464
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
465
|
+
id: z.ZodString;
|
|
466
|
+
content: z.ZodString;
|
|
467
|
+
name: z.ZodString;
|
|
468
|
+
"deck-id": z.ZodString;
|
|
469
|
+
"new?": z.ZodBoolean;
|
|
470
|
+
}, z.ZodTypeAny, "passthrough">>, "many">;
|
|
471
|
+
}, "strip", z.ZodTypeAny, {
|
|
472
|
+
cards: z.objectOutputType<{
|
|
473
|
+
id: z.ZodString;
|
|
474
|
+
content: z.ZodString;
|
|
475
|
+
name: z.ZodString;
|
|
476
|
+
"deck-id": z.ZodString;
|
|
477
|
+
"new?": z.ZodBoolean;
|
|
478
|
+
}, z.ZodTypeAny, "passthrough">[];
|
|
479
|
+
}, {
|
|
480
|
+
cards: z.objectInputType<{
|
|
481
|
+
id: z.ZodString;
|
|
482
|
+
content: z.ZodString;
|
|
483
|
+
name: z.ZodString;
|
|
484
|
+
"deck-id": z.ZodString;
|
|
485
|
+
"new?": z.ZodBoolean;
|
|
486
|
+
}, z.ZodTypeAny, "passthrough">[];
|
|
487
|
+
}>;
|
|
305
488
|
export declare class MochiClient {
|
|
306
489
|
private api;
|
|
307
490
|
private token;
|
|
@@ -311,5 +494,12 @@ export declare class MochiClient {
|
|
|
311
494
|
listDecks(params?: ListDecksParams): Promise<ListDecksResponse>;
|
|
312
495
|
listCards(params?: ListCardsParams): Promise<ListCardsResponse>;
|
|
313
496
|
listTemplates(params?: ListTemplatesParams): Promise<ListTemplatesResponse>;
|
|
497
|
+
getDueCards(params?: GetDueCardsParams): Promise<z.infer<typeof GetDueCardsResponseSchema>>;
|
|
498
|
+
getTemplate(templateId: string): Promise<z.infer<typeof TemplateSchema>>;
|
|
499
|
+
createCardFromTemplate(request: CreateCardFromTemplateParams): Promise<CreateCardResponse>;
|
|
500
|
+
addAttachment(request: AddAttachmentRequest): Promise<{
|
|
501
|
+
filename: string;
|
|
502
|
+
markdown: string;
|
|
503
|
+
}>;
|
|
314
504
|
}
|
|
315
505
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
3
3
|
import axios from "axios";
|
|
4
|
+
import FormData from "form-data";
|
|
4
5
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
5
6
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
7
|
import dotenv from "dotenv";
|
|
@@ -97,10 +98,60 @@ const ListTemplatesParamsSchema = z.object({
|
|
|
97
98
|
.optional()
|
|
98
99
|
.describe("Pagination bookmark for fetching next page of results"),
|
|
99
100
|
});
|
|
101
|
+
const GetDueCardsParamsSchema = z.object({
|
|
102
|
+
"deck-id": z
|
|
103
|
+
.string()
|
|
104
|
+
.optional()
|
|
105
|
+
.describe("Optional deck ID to filter due cards by a specific deck"),
|
|
106
|
+
date: z
|
|
107
|
+
.string()
|
|
108
|
+
.optional()
|
|
109
|
+
.describe("Optional ISO 8601 date to get cards due on that date. Defaults to today."),
|
|
110
|
+
});
|
|
111
|
+
const CreateCardFromTemplateSchema = z.object({
|
|
112
|
+
"template-id": z
|
|
113
|
+
.string()
|
|
114
|
+
.min(1)
|
|
115
|
+
.describe("ID of the template to use. Get this from mochi_list_templates."),
|
|
116
|
+
"deck-id": z
|
|
117
|
+
.string()
|
|
118
|
+
.min(1)
|
|
119
|
+
.describe("ID of the deck to create the card in. Get this from mochi_list_decks."),
|
|
120
|
+
fields: z
|
|
121
|
+
.record(z.string(), z.string())
|
|
122
|
+
.describe('Map of field NAMES (not IDs) to values. E.g., { "Word": "serendipity" } or { "Front": "Question?", "Back": "Answer" }'),
|
|
123
|
+
"manual-tags": z
|
|
124
|
+
.array(z.string())
|
|
125
|
+
.optional()
|
|
126
|
+
.describe("Optional array of tags to add to the card"),
|
|
127
|
+
});
|
|
128
|
+
// Schema for adding attachments
|
|
129
|
+
const AddAttachmentSchema = z.object({
|
|
130
|
+
"card-id": z.string().min(1).describe("ID of the card to attach the file to"),
|
|
131
|
+
data: z.string().min(1).describe("Base64-encoded file data"),
|
|
132
|
+
filename: z
|
|
133
|
+
.string()
|
|
134
|
+
.min(1)
|
|
135
|
+
.describe("Filename with extension (e.g., 'image.png', 'audio.mp3')"),
|
|
136
|
+
"content-type": z
|
|
137
|
+
.string()
|
|
138
|
+
.optional()
|
|
139
|
+
.describe("MIME type of the file (e.g., 'image/png'). Can be inferred from filename if not provided."),
|
|
140
|
+
});
|
|
100
141
|
const TemplateFieldSchema = z.object({
|
|
101
142
|
id: z.string().describe("Unique identifier for the template field"),
|
|
102
143
|
name: z.string().describe("Display name of the field"),
|
|
103
144
|
pos: z.string().describe("Position of the field in the template"),
|
|
145
|
+
type: z
|
|
146
|
+
.string()
|
|
147
|
+
.optional()
|
|
148
|
+
.nullable()
|
|
149
|
+
.describe("Field type: null/text for user input, or ai/speech/translate/dictionary for auto-generated"),
|
|
150
|
+
source: z
|
|
151
|
+
.string()
|
|
152
|
+
.optional()
|
|
153
|
+
.nullable()
|
|
154
|
+
.describe("Source field ID for auto-generated fields"),
|
|
104
155
|
options: z
|
|
105
156
|
.object({
|
|
106
157
|
"multi-line?": z
|
|
@@ -108,6 +159,7 @@ const TemplateFieldSchema = z.object({
|
|
|
108
159
|
.optional()
|
|
109
160
|
.describe("Whether the field supports multiple lines of text"),
|
|
110
161
|
})
|
|
162
|
+
.passthrough()
|
|
111
163
|
.optional()
|
|
112
164
|
.describe("Additional options for the field"),
|
|
113
165
|
});
|
|
@@ -158,7 +210,16 @@ const DeckSchema = z
|
|
|
158
210
|
id: z.string().describe("Unique identifier for the deck"),
|
|
159
211
|
sort: z.number().describe("Sort order of the deck"),
|
|
160
212
|
name: z.string().describe("Display name of the deck"),
|
|
161
|
-
archived: z
|
|
213
|
+
"archived?": z
|
|
214
|
+
.boolean()
|
|
215
|
+
.optional()
|
|
216
|
+
.nullable()
|
|
217
|
+
.describe("Whether the deck is archived"),
|
|
218
|
+
"trashed?": z
|
|
219
|
+
.object({ date: z.string() })
|
|
220
|
+
.optional()
|
|
221
|
+
.nullable()
|
|
222
|
+
.describe("Whether the deck is trashed"),
|
|
162
223
|
})
|
|
163
224
|
.strip();
|
|
164
225
|
const ListDecksResponseSchema = z
|
|
@@ -167,6 +228,18 @@ const ListDecksResponseSchema = z
|
|
|
167
228
|
docs: z.array(DeckSchema).describe("Array of decks"),
|
|
168
229
|
})
|
|
169
230
|
.strip();
|
|
231
|
+
const DueCardSchema = z
|
|
232
|
+
.object({
|
|
233
|
+
id: z.string().describe("Unique identifier for the card"),
|
|
234
|
+
content: z.string().describe("Markdown content of the card"),
|
|
235
|
+
name: z.string().describe("Display name of the card"),
|
|
236
|
+
"deck-id": z.string().describe("ID of the deck containing the card"),
|
|
237
|
+
"new?": z.boolean().describe("Whether the card is new (never reviewed)"),
|
|
238
|
+
})
|
|
239
|
+
.passthrough();
|
|
240
|
+
const GetDueCardsResponseSchema = z.object({
|
|
241
|
+
cards: z.array(DueCardSchema).describe("Array of cards due for review"),
|
|
242
|
+
});
|
|
170
243
|
function getApiKey() {
|
|
171
244
|
const apiKey = process.env.MOCHI_API_KEY;
|
|
172
245
|
if (!apiKey) {
|
|
@@ -202,7 +275,7 @@ export class MochiClient {
|
|
|
202
275
|
? ListDecksParamsSchema.parse(params)
|
|
203
276
|
: undefined;
|
|
204
277
|
const response = await this.api.get("/decks", { params: validatedParams });
|
|
205
|
-
return ListDecksResponseSchema.parse(response.data).docs.filter((deck) => !deck
|
|
278
|
+
return ListDecksResponseSchema.parse(response.data).docs.filter((deck) => !deck["archived?"] && !deck["trashed?"]);
|
|
206
279
|
}
|
|
207
280
|
async listCards(params) {
|
|
208
281
|
const validatedParams = params
|
|
@@ -220,6 +293,94 @@ export class MochiClient {
|
|
|
220
293
|
});
|
|
221
294
|
return ListTemplatesResponseSchema.parse(response.data);
|
|
222
295
|
}
|
|
296
|
+
async getDueCards(params) {
|
|
297
|
+
const validatedParams = params
|
|
298
|
+
? GetDueCardsParamsSchema.parse(params)
|
|
299
|
+
: undefined;
|
|
300
|
+
const deckId = validatedParams?.["deck-id"];
|
|
301
|
+
const endpoint = deckId ? `/due/${deckId}` : "/due";
|
|
302
|
+
const queryParams = validatedParams?.date
|
|
303
|
+
? { date: validatedParams.date }
|
|
304
|
+
: undefined;
|
|
305
|
+
const response = await this.api.get(endpoint, { params: queryParams });
|
|
306
|
+
return GetDueCardsResponseSchema.parse(response.data);
|
|
307
|
+
}
|
|
308
|
+
async getTemplate(templateId) {
|
|
309
|
+
const response = await this.api.get(`/templates/${templateId}`);
|
|
310
|
+
return TemplateSchema.parse(response.data);
|
|
311
|
+
}
|
|
312
|
+
async createCardFromTemplate(request) {
|
|
313
|
+
// Fetch the template to get field definitions
|
|
314
|
+
const template = await this.getTemplate(request["template-id"]);
|
|
315
|
+
// Map field names to IDs
|
|
316
|
+
const fieldNameToId = {};
|
|
317
|
+
for (const [fieldId, field] of Object.entries(template.fields)) {
|
|
318
|
+
fieldNameToId[field.name] = fieldId;
|
|
319
|
+
}
|
|
320
|
+
// Build the fields object with IDs
|
|
321
|
+
const fields = {};
|
|
322
|
+
const fieldValues = [];
|
|
323
|
+
for (const [fieldName, value] of Object.entries(request.fields)) {
|
|
324
|
+
const fieldId = fieldNameToId[fieldName];
|
|
325
|
+
if (!fieldId) {
|
|
326
|
+
throw new MochiError([
|
|
327
|
+
`Unknown field name: "${fieldName}". Available fields: ${Object.keys(fieldNameToId).join(", ")}`,
|
|
328
|
+
], 400);
|
|
329
|
+
}
|
|
330
|
+
fields[fieldId] = { id: fieldId, value };
|
|
331
|
+
fieldValues.push(value);
|
|
332
|
+
}
|
|
333
|
+
// Build content from field values (joined with separator for multi-field templates)
|
|
334
|
+
const content = fieldValues.join("\n---\n");
|
|
335
|
+
const createRequest = {
|
|
336
|
+
content,
|
|
337
|
+
"deck-id": request["deck-id"],
|
|
338
|
+
"template-id": request["template-id"],
|
|
339
|
+
fields,
|
|
340
|
+
"manual-tags": request["manual-tags"],
|
|
341
|
+
};
|
|
342
|
+
return this.createCard(createRequest);
|
|
343
|
+
}
|
|
344
|
+
async addAttachment(request) {
|
|
345
|
+
// Infer content-type from filename if not provided
|
|
346
|
+
let contentType = request["content-type"];
|
|
347
|
+
if (!contentType) {
|
|
348
|
+
const ext = request.filename.split(".").pop()?.toLowerCase();
|
|
349
|
+
const mimeTypes = {
|
|
350
|
+
png: "image/png",
|
|
351
|
+
jpg: "image/jpeg",
|
|
352
|
+
jpeg: "image/jpeg",
|
|
353
|
+
gif: "image/gif",
|
|
354
|
+
webp: "image/webp",
|
|
355
|
+
svg: "image/svg+xml",
|
|
356
|
+
mp3: "audio/mpeg",
|
|
357
|
+
wav: "audio/wav",
|
|
358
|
+
ogg: "audio/ogg",
|
|
359
|
+
mp4: "video/mp4",
|
|
360
|
+
pdf: "application/pdf",
|
|
361
|
+
};
|
|
362
|
+
contentType = mimeTypes[ext ?? ""] ?? "application/octet-stream";
|
|
363
|
+
}
|
|
364
|
+
// Convert base64 to Buffer
|
|
365
|
+
const buffer = Buffer.from(request.data, "base64");
|
|
366
|
+
// Create form data
|
|
367
|
+
const formData = new FormData();
|
|
368
|
+
formData.append("file", buffer, {
|
|
369
|
+
filename: request.filename,
|
|
370
|
+
contentType,
|
|
371
|
+
});
|
|
372
|
+
// Upload attachment
|
|
373
|
+
await this.api.post(`/cards/${request["card-id"]}/attachments/${encodeURIComponent(request.filename)}`, formData, {
|
|
374
|
+
headers: {
|
|
375
|
+
...formData.getHeaders(),
|
|
376
|
+
Authorization: `Basic ${Buffer.from(`${this.token}:`).toString("base64")}`,
|
|
377
|
+
},
|
|
378
|
+
});
|
|
379
|
+
return {
|
|
380
|
+
filename: request.filename,
|
|
381
|
+
markdown: ``,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
223
384
|
}
|
|
224
385
|
// Server setup
|
|
225
386
|
const server = new Server({
|
|
@@ -236,7 +397,7 @@ const server = new Server({
|
|
|
236
397
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
237
398
|
tools: [
|
|
238
399
|
{
|
|
239
|
-
name: "
|
|
400
|
+
name: "mochi_create_flashcard",
|
|
240
401
|
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".
|
|
241
402
|
|
|
242
403
|
## Parameters
|
|
@@ -248,7 +409,7 @@ ALWAYS look up deck-id with the mochi_list_decks tool.
|
|
|
248
409
|
The markdown content of the card. Separate front and back using a horizontal rule (---).
|
|
249
410
|
|
|
250
411
|
### template-id (optional)
|
|
251
|
-
When using a template, the field ids MUST match the template ones. If not using a template, omit this field.
|
|
412
|
+
When using a template, the field ids MUST match the template ones. If not using a template, omit this field. Consider using mochi_create_card_from_template instead for easier template-based card creation.
|
|
252
413
|
|
|
253
414
|
### fields (optional)
|
|
254
415
|
A map of field IDs (keyword) to field values. Only required when using a template. The field IDs must correspond to the fields defined on the template.
|
|
@@ -293,7 +454,57 @@ A map of field IDs (keyword) to field values. Only required when using a templat
|
|
|
293
454
|
},
|
|
294
455
|
},
|
|
295
456
|
{
|
|
296
|
-
name: "
|
|
457
|
+
name: "mochi_create_card_from_template",
|
|
458
|
+
description: `Create a flashcard using a template with field names (not IDs). This is the preferred way to create template-based cards.
|
|
459
|
+
|
|
460
|
+
The MCP automatically:
|
|
461
|
+
1. Fetches the template to get field definitions
|
|
462
|
+
2. Maps provided field names to their IDs
|
|
463
|
+
3. Builds the fields object in the format Mochi expects
|
|
464
|
+
|
|
465
|
+
## Parameters
|
|
466
|
+
|
|
467
|
+
### template-id (required)
|
|
468
|
+
The ID of the template to use. Get available templates with mochi_list_templates.
|
|
469
|
+
|
|
470
|
+
### deck-id (required)
|
|
471
|
+
The ID of the deck to create the card in. Get available decks with mochi_list_decks.
|
|
472
|
+
|
|
473
|
+
### fields (required)
|
|
474
|
+
A map of field **names** (not IDs) to their values. The MCP will map names to IDs automatically.
|
|
475
|
+
|
|
476
|
+
### manual-tags (optional)
|
|
477
|
+
Array of tags to add to the card.
|
|
478
|
+
|
|
479
|
+
## Example: "Word" template (single field)
|
|
480
|
+
{
|
|
481
|
+
"template-id": "mzROLUuD",
|
|
482
|
+
"deck-id": "HGOW9dWP",
|
|
483
|
+
"fields": {
|
|
484
|
+
"Word": "serendipity"
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
## Example: "Basic Flashcard" template (front/back)
|
|
489
|
+
{
|
|
490
|
+
"template-id": "Jyv52qHg",
|
|
491
|
+
"deck-id": "jJAIs2ZZ",
|
|
492
|
+
"fields": {
|
|
493
|
+
"Front": "What is the capital of France?",
|
|
494
|
+
"Back": "Paris"
|
|
495
|
+
}
|
|
496
|
+
}`,
|
|
497
|
+
inputSchema: zodToJsonSchema(CreateCardFromTemplateSchema),
|
|
498
|
+
annotations: {
|
|
499
|
+
title: "Create flashcard from template on Mochi",
|
|
500
|
+
readOnlyHint: false,
|
|
501
|
+
destructiveHint: false,
|
|
502
|
+
idempotentHint: false,
|
|
503
|
+
openWorldHint: true,
|
|
504
|
+
},
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
name: "mochi_update_flashcard",
|
|
297
508
|
description: `Update or delete an existing flashcard in Mochi. To delete set trashed to true.`,
|
|
298
509
|
inputSchema: zodToJsonSchema(z.object({
|
|
299
510
|
"card-id": z.string(),
|
|
@@ -308,8 +519,42 @@ A map of field IDs (keyword) to field values. Only required when using a templat
|
|
|
308
519
|
},
|
|
309
520
|
},
|
|
310
521
|
{
|
|
311
|
-
name: "
|
|
312
|
-
description:
|
|
522
|
+
name: "mochi_add_attachment",
|
|
523
|
+
description: `Add an attachment (image, audio, etc.) to a card using base64 data.
|
|
524
|
+
|
|
525
|
+
Use this when you have base64-encoded file data (e.g., from images inserted in chat).
|
|
526
|
+
|
|
527
|
+
## Parameters
|
|
528
|
+
|
|
529
|
+
### card-id (required)
|
|
530
|
+
The ID of the card to attach the file to.
|
|
531
|
+
|
|
532
|
+
### data (required)
|
|
533
|
+
Base64-encoded file data.
|
|
534
|
+
|
|
535
|
+
### filename (required)
|
|
536
|
+
Filename with extension (e.g., "image.png", "audio.mp3").
|
|
537
|
+
|
|
538
|
+
### content-type (optional)
|
|
539
|
+
MIME type (e.g., "image/png"). Can be inferred from filename.
|
|
540
|
+
|
|
541
|
+
## Returns
|
|
542
|
+
The markdown reference to use in card content: \`\`
|
|
543
|
+
|
|
544
|
+
## Note
|
|
545
|
+
For URL-based images, just use markdown directly in card content: \`\` - no attachment upload needed.`,
|
|
546
|
+
inputSchema: zodToJsonSchema(AddAttachmentSchema),
|
|
547
|
+
annotations: {
|
|
548
|
+
title: "Add attachment to flashcard on Mochi",
|
|
549
|
+
readOnlyHint: false,
|
|
550
|
+
destructiveHint: false,
|
|
551
|
+
idempotentHint: false,
|
|
552
|
+
openWorldHint: true,
|
|
553
|
+
},
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
name: "mochi_list_flashcards",
|
|
557
|
+
description: "List flashcards in pages of 10 cards per page",
|
|
313
558
|
inputSchema: zodToJsonSchema(ListCardsParamsSchema),
|
|
314
559
|
annotations: {
|
|
315
560
|
title: "List flashcards on Mochi",
|
|
@@ -333,7 +578,9 @@ A map of field IDs (keyword) to field values. Only required when using a templat
|
|
|
333
578
|
},
|
|
334
579
|
{
|
|
335
580
|
name: "mochi_list_templates",
|
|
336
|
-
description: `Templates can be used to create cards with pre-defined fields
|
|
581
|
+
description: `List all templates. Templates can be used to create cards with pre-defined fields.
|
|
582
|
+
|
|
583
|
+
Use mochi_create_card_from_template for easy template-based card creation with field names instead of IDs.
|
|
337
584
|
|
|
338
585
|
Example response:
|
|
339
586
|
{
|
|
@@ -372,6 +619,26 @@ Example response:
|
|
|
372
619
|
openWorldHint: false,
|
|
373
620
|
},
|
|
374
621
|
},
|
|
622
|
+
{
|
|
623
|
+
name: "mochi_get_due_cards",
|
|
624
|
+
description: `Get flashcards that are due for review. Returns cards scheduled for review on the specified date (defaults to today).
|
|
625
|
+
|
|
626
|
+
## Parameters
|
|
627
|
+
|
|
628
|
+
### deck-id (optional)
|
|
629
|
+
Filter to only show due cards from a specific deck.
|
|
630
|
+
|
|
631
|
+
### date (optional)
|
|
632
|
+
ISO 8601 date string (e.g., "2026-01-11T00:00:00.000Z") to get cards due on that date. Defaults to today.`,
|
|
633
|
+
inputSchema: zodToJsonSchema(GetDueCardsParamsSchema),
|
|
634
|
+
annotations: {
|
|
635
|
+
title: "Get due flashcards on Mochi",
|
|
636
|
+
readOnlyHint: true,
|
|
637
|
+
destructiveHint: false,
|
|
638
|
+
idempotentHint: true,
|
|
639
|
+
openWorldHint: false,
|
|
640
|
+
},
|
|
641
|
+
},
|
|
375
642
|
],
|
|
376
643
|
}));
|
|
377
644
|
// Create Mochi client
|
|
@@ -408,7 +675,7 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
|
408
675
|
text: JSON.stringify(decks.map((deck) => ({
|
|
409
676
|
id: deck.id,
|
|
410
677
|
name: deck.name,
|
|
411
|
-
archived: deck
|
|
678
|
+
archived: deck["archived?"],
|
|
412
679
|
})), null, 2),
|
|
413
680
|
},
|
|
414
681
|
],
|
|
@@ -477,7 +744,7 @@ Input: ${input}
|
|
|
477
744
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
478
745
|
try {
|
|
479
746
|
switch (request.params.name) {
|
|
480
|
-
case "
|
|
747
|
+
case "mochi_create_flashcard": {
|
|
481
748
|
const validatedArgs = CreateCardRequestSchema.parse(request.params.arguments);
|
|
482
749
|
const response = await mochiClient.createCard(validatedArgs);
|
|
483
750
|
return {
|
|
@@ -490,7 +757,20 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
490
757
|
isError: false,
|
|
491
758
|
};
|
|
492
759
|
}
|
|
493
|
-
case "
|
|
760
|
+
case "mochi_create_card_from_template": {
|
|
761
|
+
const validatedArgs = CreateCardFromTemplateSchema.parse(request.params.arguments);
|
|
762
|
+
const response = await mochiClient.createCardFromTemplate(validatedArgs);
|
|
763
|
+
return {
|
|
764
|
+
content: [
|
|
765
|
+
{
|
|
766
|
+
type: "text",
|
|
767
|
+
text: JSON.stringify(response, null, 2),
|
|
768
|
+
},
|
|
769
|
+
],
|
|
770
|
+
isError: false,
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
case "mochi_update_flashcard": {
|
|
494
774
|
const { "card-id": cardId, ...updateArgs } = z
|
|
495
775
|
.object({
|
|
496
776
|
"card-id": z.string(),
|
|
@@ -508,6 +788,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
508
788
|
isError: false,
|
|
509
789
|
};
|
|
510
790
|
}
|
|
791
|
+
case "mochi_add_attachment": {
|
|
792
|
+
const validatedArgs = AddAttachmentSchema.parse(request.params.arguments);
|
|
793
|
+
const response = await mochiClient.addAttachment(validatedArgs);
|
|
794
|
+
return {
|
|
795
|
+
content: [
|
|
796
|
+
{
|
|
797
|
+
type: "text",
|
|
798
|
+
text: JSON.stringify(response, null, 2),
|
|
799
|
+
},
|
|
800
|
+
],
|
|
801
|
+
isError: false,
|
|
802
|
+
};
|
|
803
|
+
}
|
|
511
804
|
case "mochi_list_decks": {
|
|
512
805
|
const validatedArgs = ListDecksParamsSchema.parse(request.params.arguments);
|
|
513
806
|
const response = await mochiClient.listDecks(validatedArgs);
|
|
@@ -521,7 +814,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
521
814
|
isError: false,
|
|
522
815
|
};
|
|
523
816
|
}
|
|
524
|
-
case "
|
|
817
|
+
case "mochi_list_flashcards": {
|
|
525
818
|
const validatedArgs = ListCardsParamsSchema.parse(request.params.arguments);
|
|
526
819
|
const response = await mochiClient.listCards(validatedArgs);
|
|
527
820
|
return {
|
|
@@ -547,6 +840,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
547
840
|
isError: false,
|
|
548
841
|
};
|
|
549
842
|
}
|
|
843
|
+
case "mochi_get_due_cards": {
|
|
844
|
+
const validatedArgs = GetDueCardsParamsSchema.parse(request.params.arguments);
|
|
845
|
+
const response = await mochiClient.getDueCards(validatedArgs);
|
|
846
|
+
return {
|
|
847
|
+
content: [
|
|
848
|
+
{
|
|
849
|
+
type: "text",
|
|
850
|
+
text: JSON.stringify(response, null, 2),
|
|
851
|
+
},
|
|
852
|
+
],
|
|
853
|
+
isError: false,
|
|
854
|
+
};
|
|
855
|
+
}
|
|
550
856
|
default:
|
|
551
857
|
return {
|
|
552
858
|
content: [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fredrika/mcp-mochi",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6-beta.0",
|
|
4
4
|
"description": "MCP server for Mochi flashcard integration",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"@modelcontextprotocol/sdk": "^1.9.0",
|
|
33
33
|
"axios": "^1.6.7",
|
|
34
34
|
"dotenv": "^16.4.5",
|
|
35
|
+
"form-data": "^4.0.5",
|
|
35
36
|
"zod": "^3.22.4"
|
|
36
37
|
},
|
|
37
38
|
"devDependencies": {
|