@vue-skuilder/mcp 0.1.8-3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1630 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ CourseConfigUriSchema: () => CourseConfigUriSchema,
24
+ CreateCardInputSchema: () => CreateCardInputSchema,
25
+ DeleteCardInputSchema: () => DeleteCardInputSchema,
26
+ MCPServer: () => MCPServer,
27
+ RESOURCE_PATTERNS: () => RESOURCE_PATTERNS,
28
+ TOOL_PATTERNS: () => TOOL_PATTERNS,
29
+ TagCardInputSchema: () => TagCardInputSchema,
30
+ UpdateCardInputSchema: () => UpdateCardInputSchema,
31
+ handleCardsAllResource: () => handleCardsAllResource,
32
+ handleCardsEloResource: () => handleCardsEloResource,
33
+ handleCardsShapeResource: () => handleCardsShapeResource,
34
+ handleCardsTagResource: () => handleCardsTagResource,
35
+ handleCourseConfigResource: () => handleCourseConfigResource,
36
+ handleCreateCard: () => handleCreateCard,
37
+ handleDeleteCard: () => handleDeleteCard,
38
+ handleShapeSpecificResource: () => handleShapeSpecificResource,
39
+ handleShapesAllResource: () => handleShapesAllResource,
40
+ handleTagCard: () => handleTagCard,
41
+ handleTagSpecificResource: () => handleTagSpecificResource,
42
+ handleTagsAllResource: () => handleTagsAllResource,
43
+ handleTagsDistributionResource: () => handleTagsDistributionResource,
44
+ handleTagsExclusiveResource: () => handleTagsExclusiveResource,
45
+ handleTagsIntersectResource: () => handleTagsIntersectResource,
46
+ handleTagsStatsResource: () => handleTagsStatsResource,
47
+ handleTagsUnionResource: () => handleTagsUnionResource,
48
+ handleUpdateCard: () => handleUpdateCard
49
+ });
50
+ module.exports = __toCommonJS(index_exports);
51
+
52
+ // src/server.ts
53
+ var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
54
+
55
+ // src/resources/course.ts
56
+ var import_zod = require("zod");
57
+ async function handleCourseConfigResource(courseDB) {
58
+ try {
59
+ const config = await courseDB.getCourseConfig();
60
+ const eloStats = {
61
+ min: 1e3,
62
+ max: 2e3,
63
+ mean: 1500,
64
+ distribution: [100, 200, 300, 250, 150]
65
+ // Placeholder histogram
66
+ };
67
+ return {
68
+ config,
69
+ eloStats
70
+ };
71
+ } catch (error) {
72
+ throw new Error(`Failed to load course config: ${error instanceof Error ? error.message : "Unknown error"}`);
73
+ }
74
+ }
75
+ var CourseConfigUriSchema = import_zod.z.string().regex(/^course:\/\/config$/);
76
+
77
+ // src/resources/cards.ts
78
+ var import_zod3 = require("zod");
79
+
80
+ // src/utils/pouchdb.ts
81
+ function isSuccessRow(row) {
82
+ return row.doc != null && !("error" in row);
83
+ }
84
+
85
+ // src/utils/tools.ts
86
+ var import_zod2 = require("zod");
87
+ var MCP_AGENT_AUTHOR = "mcp-agent";
88
+ function handleToolError(error, context) {
89
+ if (error instanceof import_zod2.z.ZodError) {
90
+ throw new Error(`Invalid input: ${error.errors.map((e) => e.message).join(", ")}`);
91
+ }
92
+ const message = error instanceof Error ? error.message : "Unknown error";
93
+ console.error(`Error in ${context}:`, error);
94
+ throw new Error(`${context} failed: ${message}`);
95
+ }
96
+ function logToolStart(toolName, input) {
97
+ console.log(`[${toolName}] Starting with input:`, JSON.stringify(input, null, 2));
98
+ }
99
+ function logToolSuccess(toolName, result) {
100
+ console.log(`[${toolName}] Completed successfully:`, JSON.stringify(result, null, 2));
101
+ }
102
+ function logToolWarning(toolName, message, details) {
103
+ console.warn(`[${toolName}] Warning: ${message}`, details || "");
104
+ }
105
+ function logToolError(toolName, message, error) {
106
+ console.error(`[${toolName}] Error: ${message}`, error || "");
107
+ }
108
+
109
+ // src/resources/cards.ts
110
+ var EloRangeSchema = import_zod3.z.object({
111
+ min: import_zod3.z.number().min(0).max(5e3),
112
+ max: import_zod3.z.number().min(0).max(5e3)
113
+ }).refine((data) => data.min <= data.max, {
114
+ message: "Min ELO must be less than or equal to max ELO"
115
+ });
116
+ async function handleCardsAllResource(courseDB, limit = 50, offset = 0) {
117
+ try {
118
+ const courseInfo = await courseDB.getCourseInfo();
119
+ const cardIds = await courseDB.getCardsByELO(1500, limit + offset);
120
+ const targetCardIds = cardIds.slice(offset, offset + limit);
121
+ if (targetCardIds.length === 0) {
122
+ return {
123
+ cards: [],
124
+ total: courseInfo.cardCount,
125
+ page: Math.floor(offset / limit) + 1,
126
+ limit,
127
+ filter: "all"
128
+ };
129
+ }
130
+ const cardDocs = await courseDB.getCourseDocs(targetCardIds);
131
+ const eloData = await courseDB.getCardEloData(targetCardIds);
132
+ const eloMap = new Map(eloData.map((elo, index) => [targetCardIds[index], elo.global?.score || 1500]));
133
+ const cards = [];
134
+ for (const row of cardDocs.rows) {
135
+ if (isSuccessRow(row)) {
136
+ const doc = row.doc;
137
+ cards.push({
138
+ cardId: doc._id,
139
+ datashape: doc.shape?.name || "unknown",
140
+ data: doc.data || {},
141
+ tags: [],
142
+ // Will be populated separately if needed
143
+ elo: eloMap.get(doc._id),
144
+ created: doc.created,
145
+ modified: doc.modified
146
+ });
147
+ }
148
+ }
149
+ return {
150
+ cards,
151
+ total: courseInfo.cardCount,
152
+ page: Math.floor(offset / limit) + 1,
153
+ limit,
154
+ filter: "all"
155
+ };
156
+ } catch (error) {
157
+ console.error("Error fetching all cards:", error);
158
+ throw new Error(`Failed to fetch cards: ${error instanceof Error ? error.message : "Unknown error"}`);
159
+ }
160
+ }
161
+ async function handleCardsTagResource(courseDB, tagName, limit = 50, offset = 0) {
162
+ try {
163
+ const tag = await courseDB.getTag(tagName);
164
+ if (!tag) {
165
+ throw new Error(`Tag not found: ${tagName}`);
166
+ }
167
+ return {
168
+ cards: [],
169
+ total: 0,
170
+ page: Math.floor(offset / limit) + 1,
171
+ limit,
172
+ filter: `tag:${tagName}`
173
+ };
174
+ } catch (error) {
175
+ console.error(`Error fetching cards for tag ${tagName}:`, error);
176
+ throw new Error(`Failed to fetch cards for tag ${tagName}: ${error instanceof Error ? error.message : "Unknown error"}`);
177
+ }
178
+ }
179
+ async function handleCardsShapeResource(courseDB, shapeName, limit = 50, offset = 0) {
180
+ try {
181
+ const courseConfig = await courseDB.getCourseConfig();
182
+ const validShapes = courseConfig.dataShapes.map((ds) => ds.name);
183
+ if (!validShapes.includes(shapeName)) {
184
+ throw new Error(`DataShape not found: ${shapeName}. Available: ${validShapes.join(", ")}`);
185
+ }
186
+ const allCardIds = await courseDB.getCardsByELO(1500, 1e3);
187
+ if (allCardIds.length === 0) {
188
+ return {
189
+ cards: [],
190
+ total: 0,
191
+ page: Math.floor(offset / limit) + 1,
192
+ limit,
193
+ filter: `shape:${shapeName}`
194
+ };
195
+ }
196
+ const cardDocs = await courseDB.getCourseDocs(allCardIds);
197
+ const filteredCardIds = [];
198
+ const allFilteredRows = [];
199
+ for (const row of cardDocs.rows) {
200
+ if (isSuccessRow(row) && row.doc.shape?.name === shapeName) {
201
+ allFilteredRows.push(row);
202
+ filteredCardIds.push(row.doc._id);
203
+ }
204
+ }
205
+ const paginatedRows = allFilteredRows.slice(offset, offset + limit);
206
+ const paginatedCardIds = paginatedRows.map((row) => row.doc._id);
207
+ const eloData = await courseDB.getCardEloData(paginatedCardIds);
208
+ const eloMap = new Map(eloData.map((elo, index) => [paginatedCardIds[index], elo.global?.score || 1500]));
209
+ const cards = [];
210
+ for (const row of paginatedRows) {
211
+ if (isSuccessRow(row)) {
212
+ const doc = row.doc;
213
+ cards.push({
214
+ cardId: doc._id,
215
+ datashape: doc.shape?.name || "unknown",
216
+ data: doc.data || {},
217
+ tags: [],
218
+ elo: eloMap.get(doc._id),
219
+ created: doc.created,
220
+ modified: doc.modified
221
+ });
222
+ }
223
+ }
224
+ const totalFiltered = filteredCardIds.length;
225
+ return {
226
+ cards,
227
+ total: totalFiltered,
228
+ page: Math.floor(offset / limit) + 1,
229
+ limit,
230
+ filter: `shape:${shapeName}`
231
+ };
232
+ } catch (error) {
233
+ console.error(`Error fetching cards for shape ${shapeName}:`, error);
234
+ throw new Error(`Failed to fetch cards for shape ${shapeName}: ${error instanceof Error ? error.message : "Unknown error"}`);
235
+ }
236
+ }
237
+ async function handleCardsEloResource(courseDB, eloRange, limit = 50, offset = 0) {
238
+ try {
239
+ const [minStr, maxStr] = eloRange.split("-");
240
+ if (!minStr || !maxStr) {
241
+ throw new Error(`Invalid ELO range format: ${eloRange}. Expected format: min-max (e.g., 1200-1800)`);
242
+ }
243
+ const parsedRange = EloRangeSchema.parse({
244
+ min: parseInt(minStr, 10),
245
+ max: parseInt(maxStr, 10)
246
+ });
247
+ const targetElo = Math.floor((parsedRange.min + parsedRange.max) / 2);
248
+ const cardIds = await courseDB.getCardsByELO(targetElo, 1e3);
249
+ if (cardIds.length === 0) {
250
+ return {
251
+ cards: [],
252
+ total: 0,
253
+ page: Math.floor(offset / limit) + 1,
254
+ limit,
255
+ filter: `elo:${eloRange}`
256
+ };
257
+ }
258
+ const eloData = await courseDB.getCardEloData(cardIds);
259
+ const filteredEloData = eloData.map((elo, index) => ({ elo, cardId: cardIds[index] })).filter(({ elo }) => {
260
+ const score = elo.global?.score || 1500;
261
+ return score >= parsedRange.min && score <= parsedRange.max;
262
+ });
263
+ const paginatedEloData = filteredEloData.slice(offset, offset + limit);
264
+ const paginatedCardIds = paginatedEloData.map(({ cardId }) => cardId);
265
+ if (paginatedCardIds.length === 0) {
266
+ return {
267
+ cards: [],
268
+ total: filteredEloData.length,
269
+ page: Math.floor(offset / limit) + 1,
270
+ limit,
271
+ filter: `elo:${eloRange}`
272
+ };
273
+ }
274
+ const cardDocs = await courseDB.getCourseDocs(paginatedCardIds);
275
+ const eloMap = new Map(paginatedEloData.map(({ elo, cardId }) => [cardId, elo.global?.score || 1500]));
276
+ const cards = [];
277
+ for (const row of cardDocs.rows) {
278
+ if (isSuccessRow(row)) {
279
+ const doc = row.doc;
280
+ cards.push({
281
+ cardId: doc._id,
282
+ datashape: doc.shape?.name || "unknown",
283
+ data: doc.data || {},
284
+ tags: [],
285
+ elo: eloMap.get(doc._id),
286
+ created: doc.created,
287
+ modified: doc.modified
288
+ });
289
+ }
290
+ }
291
+ return {
292
+ cards,
293
+ total: filteredEloData.length,
294
+ page: Math.floor(offset / limit) + 1,
295
+ limit,
296
+ filter: `elo:${eloRange}`
297
+ };
298
+ } catch (error) {
299
+ console.error(`Error fetching cards for ELO range ${eloRange}:`, error);
300
+ throw new Error(`Failed to fetch cards for ELO range ${eloRange}: ${error instanceof Error ? error.message : "Unknown error"}`);
301
+ }
302
+ }
303
+
304
+ // src/resources/shapes.ts
305
+ async function handleShapesAllResource(courseDB) {
306
+ try {
307
+ const courseConfig = await courseDB.getCourseConfig();
308
+ const dataShapes = courseConfig.dataShapes || [];
309
+ const shapes = dataShapes.map((shape) => ({
310
+ name: shape.name,
311
+ description: `DataShape for ${shape.name} content type`,
312
+ fields: shape.fields?.map((field) => ({
313
+ name: field.name,
314
+ type: field.type || "string",
315
+ required: field.required || false,
316
+ description: field.description || `Field for ${field.name}`
317
+ })) || [],
318
+ category: "course-content",
319
+ examples: []
320
+ // Could be populated with example cards
321
+ }));
322
+ const availableShapes = dataShapes.map((shape) => shape.name);
323
+ return {
324
+ shapes,
325
+ total: shapes.length,
326
+ availableShapes
327
+ };
328
+ } catch (error) {
329
+ console.error("Error fetching all shapes:", error);
330
+ throw new Error(`Failed to fetch shapes: ${error instanceof Error ? error.message : "Unknown error"}`);
331
+ }
332
+ }
333
+ async function handleShapeSpecificResource(courseDB, shapeName) {
334
+ try {
335
+ const courseConfig = await courseDB.getCourseConfig();
336
+ const dataShapes = courseConfig.dataShapes || [];
337
+ const targetShape = dataShapes.find((shape) => shape.name === shapeName);
338
+ if (!targetShape) {
339
+ const availableShapes = dataShapes.map((s) => s.name);
340
+ throw new Error(`DataShape not found: ${shapeName}. Available shapes: ${availableShapes.join(", ")}`);
341
+ }
342
+ let examples = [];
343
+ try {
344
+ const cardIds = await courseDB.getCardsByELO(1500, 10);
345
+ if (cardIds.length > 0) {
346
+ const cardDocs = await courseDB.getCourseDocs(cardIds.slice(0, 5));
347
+ examples = [];
348
+ for (const row of cardDocs.rows) {
349
+ if (isSuccessRow(row) && row.doc.shape?.name === shapeName) {
350
+ const data = row.doc?.data;
351
+ if (data) {
352
+ examples.push(data);
353
+ if (examples.length >= 3) break;
354
+ }
355
+ }
356
+ }
357
+ }
358
+ } catch (error) {
359
+ console.warn("Could not fetch examples for shape:", error);
360
+ }
361
+ return {
362
+ name: targetShape.name,
363
+ description: `DataShape definition for ${targetShape.name} content type`,
364
+ fields: targetShape.fields?.map((field) => ({
365
+ name: field.name,
366
+ type: field.type || "string",
367
+ required: field.required || false,
368
+ description: field.description || `Field for ${field.name}`
369
+ })) || [],
370
+ category: "course-content",
371
+ examples
372
+ };
373
+ } catch (error) {
374
+ console.error(`Error fetching shape ${shapeName}:`, error);
375
+ throw new Error(`Failed to fetch shape ${shapeName}: ${error instanceof Error ? error.message : "Unknown error"}`);
376
+ }
377
+ }
378
+
379
+ // src/resources/tags.ts
380
+ async function handleTagsAllResource(courseDB) {
381
+ try {
382
+ const tagStubsResponse = await courseDB.getCourseTagStubs();
383
+ const tagStubs = tagStubsResponse.rows || [];
384
+ const tags = [];
385
+ for (const stub of tagStubs) {
386
+ if (stub.doc) {
387
+ tags.push({
388
+ name: stub.doc.name || stub.id,
389
+ description: stub.doc.snippet || `Tag: ${stub.doc.name}`,
390
+ cardCount: stub.doc.taggedCards?.length || 0,
391
+ created: stub.doc.created,
392
+ author: stub.doc.author,
393
+ metadata: stub.doc.metadata
394
+ });
395
+ }
396
+ }
397
+ return {
398
+ tags,
399
+ total: tags.length,
400
+ stats: {
401
+ totalTags: tags.length,
402
+ totalTaggedCards: 0,
403
+ // Would need additional queries
404
+ averageTagsPerCard: 0,
405
+ // Would need calculation
406
+ mostUsedTags: []
407
+ // Would need tag usage analysis
408
+ }
409
+ };
410
+ } catch (error) {
411
+ console.error("Error fetching all tags:", error);
412
+ throw new Error(`Failed to fetch tags: ${error instanceof Error ? error.message : "Unknown error"}`);
413
+ }
414
+ }
415
+ async function handleTagsStatsResource(courseDB) {
416
+ try {
417
+ const tagStubsResponse = await courseDB.getCourseTagStubs();
418
+ const totalTags = tagStubsResponse.rows?.length || 0;
419
+ const courseInfo = await courseDB.getCourseInfo();
420
+ console.log("Course info loaded for stats:", courseInfo.cardCount);
421
+ return {
422
+ totalTags,
423
+ totalTaggedCards: 0,
424
+ // Placeholder
425
+ averageTagsPerCard: 0,
426
+ // Placeholder
427
+ mostUsedTags: []
428
+ // Placeholder
429
+ };
430
+ } catch (error) {
431
+ console.error("Error fetching tag stats:", error);
432
+ throw new Error(`Failed to fetch tag statistics: ${error instanceof Error ? error.message : "Unknown error"}`);
433
+ }
434
+ }
435
+ async function handleTagSpecificResource(courseDB, tagName) {
436
+ try {
437
+ const tag = await courseDB.getTag(tagName);
438
+ if (!tag) {
439
+ throw new Error(`Tag not found: ${tagName}`);
440
+ }
441
+ return {
442
+ name: tag.name || tagName,
443
+ description: tag.snippet || `Tag: ${tagName}`,
444
+ cardCount: tag.taggedCards?.length || 0,
445
+ created: tag.created,
446
+ author: tag.author,
447
+ metadata: tag.metadata
448
+ };
449
+ } catch (error) {
450
+ console.error(`Error fetching tag ${tagName}:`, error);
451
+ throw new Error(`Failed to fetch tag ${tagName}: ${error instanceof Error ? error.message : "Unknown error"}`);
452
+ }
453
+ }
454
+ async function handleTagsUnionResource(courseDB, tagsParam) {
455
+ try {
456
+ const tags = tagsParam.split("+").map((tag) => tag.trim());
457
+ if (tags.length === 0) {
458
+ throw new Error("No tags specified for union operation");
459
+ }
460
+ for (const tagName of tags) {
461
+ try {
462
+ await courseDB.getTag(tagName);
463
+ } catch {
464
+ throw new Error(`Tag not found: ${tagName}`);
465
+ }
466
+ }
467
+ return {
468
+ cardIds: [],
469
+ // Placeholder
470
+ tags,
471
+ operation: "union"
472
+ };
473
+ } catch (error) {
474
+ console.error(`Error performing tags union for ${tagsParam}:`, error);
475
+ throw new Error(`Failed to perform tags union: ${error instanceof Error ? error.message : "Unknown error"}`);
476
+ }
477
+ }
478
+ async function handleTagsIntersectResource(courseDB, tagsParam) {
479
+ try {
480
+ const tags = tagsParam.split("+").map((tag) => tag.trim());
481
+ if (tags.length === 0) {
482
+ throw new Error("No tags specified for intersect operation");
483
+ }
484
+ for (const tagName of tags) {
485
+ try {
486
+ await courseDB.getTag(tagName);
487
+ } catch {
488
+ throw new Error(`Tag not found: ${tagName}`);
489
+ }
490
+ }
491
+ return {
492
+ cardIds: [],
493
+ // Placeholder
494
+ tags,
495
+ operation: "intersect"
496
+ };
497
+ } catch (error) {
498
+ console.error(`Error performing tags intersect for ${tagsParam}:`, error);
499
+ throw new Error(`Failed to perform tags intersect: ${error instanceof Error ? error.message : "Unknown error"}`);
500
+ }
501
+ }
502
+ async function handleTagsExclusiveResource(courseDB, tagsParam) {
503
+ try {
504
+ const [includeTag, excludeTag] = tagsParam.split("-").map((tag) => tag.trim());
505
+ if (!includeTag || !excludeTag) {
506
+ throw new Error("Both include and exclude tags must be specified (format: tag1-tag2)");
507
+ }
508
+ try {
509
+ await courseDB.getTag(includeTag);
510
+ await courseDB.getTag(excludeTag);
511
+ } catch (error) {
512
+ throw new Error(`One or both tags not found: ${includeTag}, ${excludeTag}`);
513
+ }
514
+ return {
515
+ cardIds: [],
516
+ // Placeholder
517
+ includeTag,
518
+ excludeTag,
519
+ operation: "exclusive"
520
+ };
521
+ } catch (error) {
522
+ console.error(`Error performing tags exclusive for ${tagsParam}:`, error);
523
+ throw new Error(`Failed to perform tags exclusive: ${error instanceof Error ? error.message : "Unknown error"}`);
524
+ }
525
+ }
526
+ async function handleTagsDistributionResource(courseDB) {
527
+ try {
528
+ const tagStubsResponse = await courseDB.getCourseTagStubs();
529
+ const tags = tagStubsResponse.rows || [];
530
+ const courseInfo = await courseDB.getCourseInfo();
531
+ const distribution = tags.map((stub) => ({
532
+ tagName: stub.doc?.name || stub.id,
533
+ frequency: 0,
534
+ // Placeholder
535
+ percentage: 0,
536
+ // Placeholder
537
+ cardIds: []
538
+ // Placeholder
539
+ }));
540
+ return {
541
+ distribution,
542
+ totalTags: tags.length,
543
+ totalCards: courseInfo.cardCount
544
+ };
545
+ } catch (error) {
546
+ console.error("Error calculating tag distribution:", error);
547
+ throw new Error(`Failed to calculate tag distribution: ${error instanceof Error ? error.message : "Unknown error"}`);
548
+ }
549
+ }
550
+
551
+ // src/resources/index.ts
552
+ var RESOURCE_PATTERNS = {
553
+ COURSE_CONFIG: "course://config",
554
+ CARDS_ALL: "cards://all",
555
+ CARDS_TAG: "cards://tag/{tagName}",
556
+ CARDS_SHAPE: "cards://shape/{shapeName}",
557
+ CARDS_ELO: "cards://elo/{eloRange}",
558
+ SHAPES_ALL: "shapes://all",
559
+ SHAPES_SPECIFIC: "shapes://{shapeName}",
560
+ TAGS_ALL: "tags://all",
561
+ TAGS_STATS: "tags://stats",
562
+ TAGS_SPECIFIC: "tags://{tagName}",
563
+ TAGS_UNION: "tags://union/{tags}",
564
+ TAGS_INTERSECT: "tags://intersect/{tags}",
565
+ TAGS_EXCLUSIVE: "tags://exclusive/{tags}",
566
+ TAGS_DISTRIBUTION: "tags://distribution"
567
+ };
568
+
569
+ // src/types/tools.ts
570
+ var import_zod4 = require("zod");
571
+ var CreateCardInputSchema = import_zod4.z.object({
572
+ datashape: import_zod4.z.string(),
573
+ // individual datashapes define their own schema
574
+ data: import_zod4.z.any(),
575
+ tags: import_zod4.z.array(import_zod4.z.string()).optional(),
576
+ elo: import_zod4.z.number().optional(),
577
+ sourceRef: import_zod4.z.string().optional()
578
+ });
579
+ var UpdateCardInputSchema = import_zod4.z.object({
580
+ cardId: import_zod4.z.string(),
581
+ data: import_zod4.z.any().optional(),
582
+ tags: import_zod4.z.array(import_zod4.z.string()).optional(),
583
+ elo: import_zod4.z.number().optional(),
584
+ sourceRef: import_zod4.z.string().optional()
585
+ });
586
+ var TagCardInputSchema = import_zod4.z.object({
587
+ cardId: import_zod4.z.string(),
588
+ action: import_zod4.z.enum(["add", "remove"]),
589
+ tags: import_zod4.z.array(import_zod4.z.string()),
590
+ updateELO: import_zod4.z.boolean().optional().default(false)
591
+ });
592
+ var DeleteCardInputSchema = import_zod4.z.object({
593
+ cardId: import_zod4.z.string(),
594
+ confirm: import_zod4.z.boolean().default(false),
595
+ reason: import_zod4.z.string().optional()
596
+ });
597
+
598
+ // src/tools/create-card.ts
599
+ var import_common = require("@vue-skuilder/common");
600
+ async function handleCreateCard(courseDB, input) {
601
+ const TOOL_NAME = "create_card";
602
+ try {
603
+ const validatedInput = CreateCardInputSchema.parse(input);
604
+ logToolStart(TOOL_NAME, validatedInput);
605
+ const courseConfig = await courseDB.getCourseConfig();
606
+ const availableDataShapes = courseConfig.dataShapes.map((ds) => ds.name);
607
+ if (!availableDataShapes.includes(validatedInput.datashape)) {
608
+ const errorMsg = `Invalid datashape: ${validatedInput.datashape}. Available: ${availableDataShapes.join(", ")}`;
609
+ logToolWarning(TOOL_NAME, errorMsg);
610
+ throw new Error(errorMsg);
611
+ }
612
+ const result = await courseDB.addNote(
613
+ courseConfig.courseID,
614
+ { name: validatedInput.datashape, fields: [] },
615
+ validatedInput.data,
616
+ MCP_AGENT_AUTHOR,
617
+ validatedInput.tags || [],
618
+ void 0,
619
+ // No uploads for now
620
+ validatedInput.elo ? (0, import_common.toCourseElo)(validatedInput.elo) : void 0
621
+ );
622
+ if (result.status !== "ok") {
623
+ const errorMsg = `Failed to create card: ${result.message || "Unknown error"}`;
624
+ throw new Error(errorMsg);
625
+ }
626
+ const initialElo = validatedInput.elo || 1500;
627
+ const output = {
628
+ cardId: result.id || "unknown",
629
+ initialElo,
630
+ created: true
631
+ };
632
+ logToolSuccess(TOOL_NAME, output);
633
+ return output;
634
+ } catch (error) {
635
+ handleToolError(error, TOOL_NAME);
636
+ }
637
+ }
638
+
639
+ // src/tools/update-card.ts
640
+ var import_common2 = require("@vue-skuilder/common");
641
+ async function handleUpdateCard(courseDB, input) {
642
+ const TOOL_NAME = "update_card";
643
+ try {
644
+ const validatedInput = UpdateCardInputSchema.parse(input);
645
+ logToolStart(TOOL_NAME, validatedInput);
646
+ const changes = {
647
+ data: false,
648
+ tags: false,
649
+ elo: false,
650
+ sourceRef: false
651
+ };
652
+ try {
653
+ await courseDB.getCourseDoc(validatedInput.cardId);
654
+ } catch (error) {
655
+ throw new Error(`Card not found: ${validatedInput.cardId}`);
656
+ }
657
+ if (validatedInput.elo !== void 0) {
658
+ try {
659
+ const courseElo = (0, import_common2.toCourseElo)(validatedInput.elo);
660
+ await courseDB.updateCardElo(validatedInput.cardId, courseElo);
661
+ changes.elo = true;
662
+ } catch (error) {
663
+ logToolWarning(TOOL_NAME, `Failed to update ELO for card ${validatedInput.cardId}`, error);
664
+ throw new Error(`Failed to update ELO: ${error instanceof Error ? error.message : "Unknown error"}`);
665
+ }
666
+ }
667
+ if (validatedInput.tags !== void 0) {
668
+ try {
669
+ const currentTagsResponse = await courseDB.getAppliedTags(validatedInput.cardId);
670
+ const currentTagNames = currentTagsResponse.rows.map((row) => row.doc?.name).filter(Boolean);
671
+ const newTags = validatedInput.tags;
672
+ const tagsToAdd = newTags.filter((tag) => !currentTagNames.includes(tag));
673
+ const tagsToRemove = currentTagNames.filter((tag) => !newTags.includes(tag));
674
+ for (const tagName of tagsToAdd) {
675
+ try {
676
+ try {
677
+ await courseDB.getTag(tagName);
678
+ } catch {
679
+ await courseDB.createTag(tagName, MCP_AGENT_AUTHOR);
680
+ }
681
+ await courseDB.addTagToCard(validatedInput.cardId, tagName);
682
+ } catch (error) {
683
+ logToolWarning(TOOL_NAME, `Failed to add tag ${tagName} to card`, error);
684
+ }
685
+ }
686
+ for (const tagName of tagsToRemove) {
687
+ try {
688
+ await courseDB.removeTagFromCard(validatedInput.cardId, tagName);
689
+ } catch (error) {
690
+ logToolWarning(TOOL_NAME, `Failed to remove tag ${tagName} from card`, error);
691
+ }
692
+ }
693
+ if (tagsToAdd.length > 0 || tagsToRemove.length > 0) {
694
+ changes.tags = true;
695
+ }
696
+ } catch (error) {
697
+ logToolWarning(TOOL_NAME, `Failed to update tags for card ${validatedInput.cardId}`, error);
698
+ throw new Error(`Failed to update tags: ${error instanceof Error ? error.message : "Unknown error"}`);
699
+ }
700
+ }
701
+ if (validatedInput.data !== void 0 || validatedInput.sourceRef !== void 0) {
702
+ logToolWarning(TOOL_NAME, "Data and sourceRef updates are not yet implemented - require direct PouchDB operations");
703
+ }
704
+ const updated = changes.elo || changes.tags || changes.data || changes.sourceRef;
705
+ const output = {
706
+ cardId: validatedInput.cardId,
707
+ updated,
708
+ changes
709
+ };
710
+ logToolSuccess(TOOL_NAME, output);
711
+ return output;
712
+ } catch (error) {
713
+ handleToolError(error, TOOL_NAME);
714
+ }
715
+ }
716
+
717
+ // src/tools/tag-card.ts
718
+ async function handleTagCard(courseDB, input) {
719
+ const TOOL_NAME = "tag_card";
720
+ try {
721
+ const validatedInput = TagCardInputSchema.parse(input);
722
+ logToolStart(TOOL_NAME, validatedInput);
723
+ try {
724
+ await courseDB.getCourseDoc(validatedInput.cardId);
725
+ } catch (error) {
726
+ throw new Error(`Card not found: ${validatedInput.cardId}`);
727
+ }
728
+ const tagsProcessed = [];
729
+ let success = true;
730
+ for (const tagName of validatedInput.tags) {
731
+ try {
732
+ if (validatedInput.action === "add") {
733
+ try {
734
+ await courseDB.getTag(tagName);
735
+ } catch {
736
+ await courseDB.createTag(tagName, MCP_AGENT_AUTHOR);
737
+ }
738
+ await courseDB.addTagToCard(
739
+ validatedInput.cardId,
740
+ tagName,
741
+ validatedInput.updateELO
742
+ );
743
+ tagsProcessed.push(tagName);
744
+ } else if (validatedInput.action === "remove") {
745
+ await courseDB.removeTagFromCard(validatedInput.cardId, tagName);
746
+ tagsProcessed.push(tagName);
747
+ }
748
+ } catch (error) {
749
+ logToolWarning(TOOL_NAME, `Failed to ${validatedInput.action} tag ${tagName}`, error);
750
+ success = false;
751
+ }
752
+ }
753
+ let currentTags = [];
754
+ try {
755
+ const currentTagsResponse = await courseDB.getAppliedTags(validatedInput.cardId);
756
+ currentTags = currentTagsResponse.rows.map((row) => row.doc?.name).filter(Boolean);
757
+ } catch (error) {
758
+ logToolWarning(TOOL_NAME, "Failed to fetch current tags", error);
759
+ }
760
+ const output = {
761
+ cardId: validatedInput.cardId,
762
+ action: validatedInput.action,
763
+ tagsProcessed,
764
+ success: success && tagsProcessed.length === validatedInput.tags.length,
765
+ currentTags
766
+ };
767
+ logToolSuccess(TOOL_NAME, output);
768
+ return output;
769
+ } catch (error) {
770
+ handleToolError(error, TOOL_NAME);
771
+ }
772
+ }
773
+
774
+ // src/tools/delete-card.ts
775
+ async function handleDeleteCard(courseDB, input) {
776
+ const TOOL_NAME = "delete_card";
777
+ try {
778
+ const validatedInput = DeleteCardInputSchema.parse(input);
779
+ logToolStart(TOOL_NAME, validatedInput);
780
+ if (!validatedInput.confirm) {
781
+ return {
782
+ cardId: validatedInput.cardId,
783
+ deleted: false,
784
+ message: "Card deletion requires explicit confirmation. Set confirm: true to proceed."
785
+ };
786
+ }
787
+ let cardExists = true;
788
+ try {
789
+ await courseDB.getCourseDoc(validatedInput.cardId);
790
+ } catch (error) {
791
+ cardExists = false;
792
+ }
793
+ if (!cardExists) {
794
+ return {
795
+ cardId: validatedInput.cardId,
796
+ deleted: false,
797
+ message: `Card not found: ${validatedInput.cardId}`
798
+ };
799
+ }
800
+ let cardInfo = "";
801
+ try {
802
+ const cardDoc = await courseDB.getCourseDoc(validatedInput.cardId);
803
+ const cardData = cardDoc;
804
+ cardInfo = `Card: ${cardData.shape?.name || "unknown shape"}`;
805
+ } catch (error) {
806
+ logToolWarning(TOOL_NAME, "Could not fetch card info before deletion", error);
807
+ }
808
+ try {
809
+ const result = await courseDB.removeCard(validatedInput.cardId);
810
+ if (result.ok) {
811
+ const reason = validatedInput.reason ? ` Reason: ${validatedInput.reason}` : "";
812
+ const message = `Card successfully deleted. ${cardInfo}${reason}`;
813
+ logToolSuccess(TOOL_NAME, `Card deleted: ${validatedInput.cardId}${reason}`);
814
+ return {
815
+ cardId: validatedInput.cardId,
816
+ deleted: true,
817
+ message
818
+ };
819
+ } else {
820
+ return {
821
+ cardId: validatedInput.cardId,
822
+ deleted: false,
823
+ message: `Failed to delete card: ${result.rev || "Unknown error"}`
824
+ };
825
+ }
826
+ } catch (error) {
827
+ logToolError(TOOL_NAME, `Error deleting card ${validatedInput.cardId}`, error);
828
+ return {
829
+ cardId: validatedInput.cardId,
830
+ deleted: false,
831
+ message: `Failed to delete card: ${error instanceof Error ? error.message : "Unknown error"}`
832
+ };
833
+ }
834
+ } catch (error) {
835
+ handleToolError(error, TOOL_NAME);
836
+ }
837
+ }
838
+
839
+ // src/tools/index.ts
840
+ var TOOL_PATTERNS = {
841
+ CREATE_CARD: "create_card",
842
+ UPDATE_CARD: "update_card",
843
+ TAG_CARD: "tag_card",
844
+ DELETE_CARD: "delete_card"
845
+ };
846
+
847
+ // src/server.ts
848
+ var import_zod5 = require("zod");
849
+
850
+ // src/prompts/fill-in-card-authoring.ts
851
+ function createFillInCardAuthoringPrompt() {
852
+ return `# GUIDE: Authoring "fillIn" type Flashcards
853
+
854
+ This guide provides the technical specifications for authoring a "fillIn" type flashcard for the Vue-Skuilder platform. When you need to create a new card, you should use the 'create_card' tool and format the content as described below.
855
+
856
+ ## 1. Tool: 'create_card'
857
+
858
+ You should call the 'create_card' tool with the following parameters:
859
+
860
+ - 'datashape': Must be 'fillIn'.
861
+ - 'data': A markdown string containing the question and the interactive '{{...}}' elements.
862
+ - 'tags': (Optional) An array of strings for tagging, e.g., ['go', 'concurrency'].
863
+ - 'elo': (Optional) An integer representing the estimated difficulty (e.g., 1250).
864
+
865
+ **IMPORTANT**: Do NOT include 'tags:' or 'elo:' information inside the 'data' string itself. These are tool parameters.
866
+
867
+ ## 2. Content Formatting ('data' parameter)
868
+
869
+ The 'data' string is composed of a question and special interactive elements using '{{...}}' syntax.
870
+
871
+ ### A. Simple Fill-in-the-Blank
872
+ - For a single correct answer.
873
+ - **Syntax**: '{{answer}}'
874
+ - **Example**: 'The capital of France is {{Paris}}.'
875
+
876
+ ### B. Fill-in-the-Blank with Alternatives
877
+ - For when multiple text inputs are acceptable for the same blank.
878
+ - **Syntax**: '{{answer1|answer2}}'
879
+ - **Example**: 'A common way to declare a variable in JavaScript that can be reassigned is using the {{let|var}} keyword.'
880
+
881
+ ### C. Multiple Choice
882
+ - For presenting a question with several options.
883
+ - **Syntax**: '{{correct_answer1|correct_answer2||distractor1|distractor2|distractor3|...}}'
884
+ - **Details**:
885
+ - List one or more correct answers first, separated by '|'.
886
+ - Use a double pipe '||' to separate correct answers from distractors.
887
+ - List one or more incorrect answers (distractors) after the '||', separated by '|'.
888
+ - **Many distractors can be added** - the rendering layer will limit runtime display to a maximum of 5 rendered distractors.
889
+ - **Front-load the most relevant distractors** - they will be weighted more favorably in selection.
890
+ - **Example (Single Correct)**: 'What is the main gas found in the air we breathe? {{Nitrogen||Oxygen|Carbon Dioxide|Hydrogen}}'
891
+ - **Example (Multiple Correct)**: 'Which of the following are primary colors? {{Red|Blue|Yellow||Green|Orange|Purple}}' (The system will pick one correct answer to display with the distractors).
892
+ - **Example (Many Distractors)**: 'Which letter comes after M in the alphabet? {{N||O|L|P|Q|R|S|T|U|V|W|X|Y|Z|A|B|C|D|E|F|G|H|I|J|K}}' (Only 5 distractors will be shown, with O and L being most likely due to their position).
893
+
894
+ ### D. Overlapping Cards for Difficulty Scaling
895
+ - **It is normal and advisable** to present the same content in overlapping cards with different distractor sets to scale difficulty.
896
+ - **Progressive difficulty**: Start with dissimilar distractors, then increase similarity to make the question harder.
897
+ - **Examples (Spelling/Phonics)**: Given an image of a car:
898
+ - **Easy**: 'What word matches this image? {{car||airplane}}' (very different options)
899
+ - **Medium**: 'What word matches this image? {{car||cat}}' (similar sounds)
900
+ - **Hard**: 'What word matches this image? {{car||care}}' (very similar spelling)
901
+ - **Harder**: 'What word matches this image? {{car||care|cat|far}}' (multiple similar options)
902
+ - **Benefits**: This approach allows learners to build confidence with easier versions before tackling more challenging variants of the same concept.
903
+
904
+ ## 3. Best Practices & Tips
905
+
906
+ - **Clarity is Key**: Ensure your questions are clear and the blanks/choices are unambiguous.
907
+ - **Keep Answers Concise**: For fill-in-the-blanks, prefer short, specific answers (1-3 words is ideal). Long answers are hard for users to match. Use multiple-choice for more complex answers.
908
+ - **Code in Questions**: You can and should use markdown code blocks in the question body for context.
909
+ - **Example**:
910
+ Given the code:
911
+ \`\`\`go
912
+ func main() {
913
+ x := 10
914
+ fmt.Println(x)
915
+ }
916
+ \`\`\`
917
+ The above program will {{compile and run correctly||fail at compilation|fail at runtime}}.
918
+ - **Code in Answers**: Code blocks ARE allowed inside the '{{...}}' syntax.
919
+ - **Example**: '{{\`go\\nfunc main() {\\n fmt.Println("Hello")\\n}\\n\`}}'
920
+ - **CRITICAL**: Do NOT place the '{{...}}' syntax inside a markdown code block (e.g., \`\`\` ... {{...}} ... \`\`\`). The interactive element must be outside of any code fences.
921
+ - **Review Syntax**: Carefully check your use of '|' and '||' in multiple-choice questions to avoid parsing errors.
922
+
923
+ ## 4. Example Agent Workflow
924
+
925
+ This is an example of how an agent might use this guide.
926
+
927
+ **User Request:** "Make a card about Go channels."
928
+
929
+ **Agent's Thought Process:**
930
+ 1. I need to create a "fillIn" card about Go channels.
931
+ 2. A good multiple-choice question would be about the operator used to send a value to a channel.
932
+ 3. The question will be: "In Go, what operator is used to send a value to a channel?"
933
+ 4. The correct answer is '<-'. Some good distractors would be '->', '<<', and '>>'.
934
+ 5. The 'data' string for the tool call will be: 'In Go, what operator is used to send a value to a channel? {{<-||->|<<|>>}}'
935
+ 6. I'll tag it with 'go' and 'channels'.
936
+ 7. This is a fundamental concept, so I'll give it an ELO of 1100.
937
+ 8. Now I will call the 'create_card' tool with these parameters.
938
+
939
+ **Resulting Tool Call:**
940
+ <tool_code>
941
+ print(default_api.create_card(
942
+ datashape='fillIn',
943
+ data='In Go, what operator is used to send a value to a channel? {{<-||->|<<|>>}}',
944
+ tags=['go', 'channels'],
945
+ elo=1100
946
+ ))
947
+ </tool_code>`;
948
+ }
949
+
950
+ // src/prompts/elo-scoring-guidance.ts
951
+ function createEloScoringGuidancePrompt() {
952
+ return `# GUIDE: ELO Scoring for Content
953
+
954
+ This guide provides guidance for agents on assigning ELO ratings to content. The ELO system helps provide initial difficulty sorting for new content.
955
+
956
+ ## Understanding ELO Ratings
957
+
958
+ ELO ratings use a chess-like rating scale to indicate content difficulty:
959
+
960
+ - **100-500**: Beginner level, basic knowledge
961
+ - **500-1000**: Some knowledge, early understanding
962
+ - **1000-1500**: Intermediate knowledge
963
+ - **1500-2000**: Advanced understanding
964
+ - **2000-2400**: Expert level
965
+ - **2400+**: Elite/master level
966
+
967
+ ## Key Principles
968
+
969
+ ### 1. Rough Initial Sorting
970
+ - Your ELO assignments provide **rough, sane sorting** of new content
971
+ - Aim for reasonable relative difficulty between cards, not perfect precision
972
+ - The system will refine these ratings over time based on real-world performance
973
+
974
+ ### 2. Automated Refinement
975
+ - The platform performs **automated realtime adjustment** of ELO ratings based on actual user performance
976
+ - This convergence forcing function holds **most of the responsibility** for arranging relative difficulty
977
+ - Your initial ratings serve as a starting point, not the final word
978
+
979
+ ### 3. Content-Based Guidelines
980
+
981
+ #### Basic Knowledge (100-500)
982
+ - Simple facts and definitions
983
+ - Basic vocabulary
984
+ - Elementary concepts
985
+ - **Example**: "The capital of France is {{Paris}}"
986
+
987
+ #### Early Understanding (500-1000)
988
+ - Simple applications of concepts
989
+ - Basic problem-solving
990
+ - Common patterns and rules
991
+ - **Example**: "In JavaScript, use {{let|var}} to declare a reassignable variable"
992
+
993
+ #### Intermediate Knowledge (1000-1500)
994
+ - More complex applications
995
+ - Understanding relationships between concepts
996
+ - Moderate problem-solving skills
997
+ - **Example**: "Which sorting algorithm has O(n log n) average time complexity? {{mergesort|heapsort||bubblesort|insertionsort}}"
998
+
999
+ #### Advanced Understanding (1500-2000)
1000
+ - Complex problem-solving
1001
+ - Deep conceptual understanding
1002
+ - Advanced techniques and patterns
1003
+ - **Example**: "In Go, what happens when you close a channel that's already closed? {{runtime panic||returns false|blocks forever|does nothing}}"
1004
+
1005
+ #### Expert Level (2000-2400)
1006
+ - Specialized knowledge
1007
+ - Advanced edge cases
1008
+ - Professional-level understanding
1009
+ - **Example**: "What's the difference between TCP_NODELAY and TCP_CORK socket options?"
1010
+
1011
+ #### Elite/Master Level (2400+)
1012
+ - Cutting-edge knowledge
1013
+ - Rare edge cases
1014
+ - Research-level understanding
1015
+ - **Example**: "How does the Linux kernel's CFS scheduler handle nice values in relation to vruntime calculations?"
1016
+
1017
+ ## Practical Application
1018
+
1019
+ ### When Assigning ELO Ratings:
1020
+
1021
+ 1. **Consider the target audience**: Who would reasonably be expected to know this?
1022
+ 2. **Assess cognitive load**: How much thinking/reasoning is required?
1023
+ 3. **Evaluate prerequisite knowledge**: What must someone know first?
1024
+ 4. **Compare relatively**: Is this harder or easier than other cards on similar topics?
1025
+
1026
+ ### Common Patterns:
1027
+
1028
+ - **Definitions and facts**: Usually 100-800
1029
+ - **Basic syntax/usage**: Usually 400-1200
1030
+ - **Concept application**: Usually 800-1600
1031
+ - **Problem-solving**: Usually 1200-2000
1032
+ - **Advanced techniques**: Usually 1600-2400+
1033
+
1034
+ ## Establishing Goalposts for Agentic Runs
1035
+
1036
+ ### Reference Live Course Data
1037
+ When appending data to existing courses, try to use existing data to understand the baseline against which we create. ELO is relative and not absolute.
1038
+
1039
+ 1. **Query existing course statistics** using available MCP resources:
1040
+ - \`course://config\` - Get overall course ELO distribution
1041
+ - \`tags://stats\` - Understand tag usage patterns
1042
+ - \`cards://elo/{range}\` - Sample cards in different difficulty ranges
1043
+
1044
+ 2. **Establish relative ranges** based on existing content:
1045
+ - If the course has cards ranging 800-1600, aim for similar distribution
1046
+ - If creating beginner content for an advanced course, adjust baseline accordingly
1047
+ - Use tag-specific ELO patterns to inform ratings for similar topics
1048
+
1049
+ ### Example Goalpost Process
1050
+ \`\`\`
1051
+ 1. Query: cards://elo/1000-1500 (check intermediate range)
1052
+ 2. Observe: 23 cards average 1250 ELO on "javascript" tag
1053
+ 3. Target: New JavaScript cards should cluster around 1200-1300
1054
+ 4. Create: Cards with relative difficulty within established range
1055
+ \`\`\`
1056
+
1057
+ ## Remember
1058
+
1059
+ - **It's okay to be approximate** - the system will adjust based on real performance
1060
+ - **Consistency matters more than perfection** - try to be consistent within topic areas
1061
+ - **Focus on relative difficulty** - how does this compare to other cards you've created? re: factual content - specialization matters - more esoteric knowledge should be rated higher
1062
+ - **Use live data as your anchor** - let existing course content guide your rating decisions
1063
+
1064
+ Your ELO assignments provide the initial scaffolding that allows the platform's adaptive algorithms to efficiently converge on optimal difficulty ratings.`;
1065
+ }
1066
+
1067
+ // src/prompts/index.ts
1068
+ var PROMPT_PATTERNS = {
1069
+ FILL_IN_CARD_AUTHORING: "fill-in-card-authoring",
1070
+ ELO_SCORING_GUIDANCE: "elo-scoring-guidance"
1071
+ };
1072
+
1073
+ // src/server.ts
1074
+ var MCPServer = class {
1075
+ constructor(courseDB, options = {}) {
1076
+ this.courseDB = courseDB;
1077
+ this.options = options;
1078
+ this.mcpServer = new import_mcp.McpServer({
1079
+ name: "vue-skuilder-mcp",
1080
+ version: "0.1.0"
1081
+ });
1082
+ this.setupCapabilities();
1083
+ }
1084
+ mcpServer;
1085
+ transport;
1086
+ setupCapabilities() {
1087
+ this.mcpServer.registerResource(
1088
+ RESOURCE_PATTERNS.COURSE_CONFIG,
1089
+ RESOURCE_PATTERNS.COURSE_CONFIG,
1090
+ {
1091
+ title: "Course Configuration",
1092
+ description: "Course configuration with metadata and ELO statistics",
1093
+ mimeType: "application/json"
1094
+ },
1095
+ async (uri) => {
1096
+ const result = await handleCourseConfigResource(this.courseDB);
1097
+ return {
1098
+ contents: [
1099
+ {
1100
+ uri: uri.href,
1101
+ text: JSON.stringify(result, null, 2),
1102
+ mimeType: "application/json"
1103
+ }
1104
+ ]
1105
+ };
1106
+ }
1107
+ );
1108
+ this.mcpServer.registerResource(
1109
+ RESOURCE_PATTERNS.CARDS_ALL,
1110
+ RESOURCE_PATTERNS.CARDS_ALL,
1111
+ {
1112
+ title: "All Course Cards",
1113
+ description: "List all cards in the course with pagination support",
1114
+ mimeType: "application/json"
1115
+ },
1116
+ async (uri) => {
1117
+ const url = new URL(uri.href);
1118
+ const limit = parseInt(url.searchParams.get("limit") || "50", 10);
1119
+ const offset = parseInt(url.searchParams.get("offset") || "0", 10);
1120
+ const result = await handleCardsAllResource(this.courseDB, limit, offset);
1121
+ return {
1122
+ contents: [
1123
+ {
1124
+ uri: uri.href,
1125
+ text: JSON.stringify(result, null, 2),
1126
+ mimeType: "application/json"
1127
+ }
1128
+ ]
1129
+ };
1130
+ }
1131
+ );
1132
+ this.mcpServer.registerResource(
1133
+ RESOURCE_PATTERNS.CARDS_TAG,
1134
+ new import_mcp.ResourceTemplate("cards://tag/{tagName}", { list: void 0 }),
1135
+ {
1136
+ title: "Cards by Tag",
1137
+ description: "Filter cards by tag name with pagination support",
1138
+ mimeType: "application/json"
1139
+ },
1140
+ async (uri, { tagName }) => {
1141
+ const url = new URL(uri.href);
1142
+ const limit = parseInt(url.searchParams.get("limit") || "50", 10);
1143
+ const offset = parseInt(url.searchParams.get("offset") || "0", 10);
1144
+ const result = await handleCardsTagResource(
1145
+ this.courseDB,
1146
+ tagName,
1147
+ limit,
1148
+ offset
1149
+ );
1150
+ return {
1151
+ contents: [
1152
+ {
1153
+ uri: uri.href,
1154
+ text: JSON.stringify(result, null, 2),
1155
+ mimeType: "application/json"
1156
+ }
1157
+ ]
1158
+ };
1159
+ }
1160
+ );
1161
+ this.mcpServer.registerResource(
1162
+ RESOURCE_PATTERNS.CARDS_SHAPE,
1163
+ new import_mcp.ResourceTemplate("cards://shape/{shapeName}", { list: void 0 }),
1164
+ {
1165
+ title: "Cards by DataShape",
1166
+ description: "Filter cards by DataShape with pagination support",
1167
+ mimeType: "application/json"
1168
+ },
1169
+ async (uri, { shapeName }) => {
1170
+ const url = new URL(uri.href);
1171
+ const limit = parseInt(url.searchParams.get("limit") || "50", 10);
1172
+ const offset = parseInt(url.searchParams.get("offset") || "0", 10);
1173
+ const result = await handleCardsShapeResource(
1174
+ this.courseDB,
1175
+ shapeName,
1176
+ limit,
1177
+ offset
1178
+ );
1179
+ return {
1180
+ contents: [
1181
+ {
1182
+ uri: uri.href,
1183
+ text: JSON.stringify(result, null, 2),
1184
+ mimeType: "application/json"
1185
+ }
1186
+ ]
1187
+ };
1188
+ }
1189
+ );
1190
+ this.mcpServer.registerResource(
1191
+ RESOURCE_PATTERNS.CARDS_ELO,
1192
+ new import_mcp.ResourceTemplate("cards://elo/{eloRange}", { list: void 0 }),
1193
+ {
1194
+ title: "Cards by ELO Range",
1195
+ description: "Filter cards by ELO range (format: min-max, e.g., 1200-1800)",
1196
+ mimeType: "application/json"
1197
+ },
1198
+ async (uri, { eloRange }) => {
1199
+ const url = new URL(uri.href);
1200
+ const limit = parseInt(url.searchParams.get("limit") || "50", 10);
1201
+ const offset = parseInt(url.searchParams.get("offset") || "0", 10);
1202
+ const result = await handleCardsEloResource(
1203
+ this.courseDB,
1204
+ eloRange,
1205
+ limit,
1206
+ offset
1207
+ );
1208
+ return {
1209
+ contents: [
1210
+ {
1211
+ uri: uri.href,
1212
+ text: JSON.stringify(result, null, 2),
1213
+ mimeType: "application/json"
1214
+ }
1215
+ ]
1216
+ };
1217
+ }
1218
+ );
1219
+ this.mcpServer.registerResource(
1220
+ RESOURCE_PATTERNS.SHAPES_ALL,
1221
+ RESOURCE_PATTERNS.SHAPES_ALL,
1222
+ {
1223
+ title: "All DataShapes",
1224
+ description: "List all available DataShapes in the course",
1225
+ mimeType: "application/json"
1226
+ },
1227
+ async (uri) => {
1228
+ const result = await handleShapesAllResource(this.courseDB);
1229
+ return {
1230
+ contents: [
1231
+ {
1232
+ uri: uri.href,
1233
+ text: JSON.stringify(result, null, 2),
1234
+ mimeType: "application/json"
1235
+ }
1236
+ ]
1237
+ };
1238
+ }
1239
+ );
1240
+ this.mcpServer.registerResource(
1241
+ RESOURCE_PATTERNS.SHAPES_SPECIFIC,
1242
+ new import_mcp.ResourceTemplate("shapes://{shapeName}", { list: void 0 }),
1243
+ {
1244
+ title: "Specific DataShape",
1245
+ description: "Get detailed information about a specific DataShape",
1246
+ mimeType: "application/json"
1247
+ },
1248
+ async (uri, { shapeName }) => {
1249
+ const result = await handleShapeSpecificResource(this.courseDB, shapeName);
1250
+ return {
1251
+ contents: [
1252
+ {
1253
+ uri: uri.href,
1254
+ text: JSON.stringify(result, null, 2),
1255
+ mimeType: "application/json"
1256
+ }
1257
+ ]
1258
+ };
1259
+ }
1260
+ );
1261
+ this.mcpServer.registerResource(
1262
+ RESOURCE_PATTERNS.TAGS_ALL,
1263
+ RESOURCE_PATTERNS.TAGS_ALL,
1264
+ {
1265
+ title: "All Tags",
1266
+ description: "List all available tags in the course",
1267
+ mimeType: "application/json"
1268
+ },
1269
+ async (uri) => {
1270
+ const result = await handleTagsAllResource(this.courseDB);
1271
+ return {
1272
+ contents: [
1273
+ {
1274
+ uri: uri.href,
1275
+ text: JSON.stringify(result, null, 2),
1276
+ mimeType: "application/json"
1277
+ }
1278
+ ]
1279
+ };
1280
+ }
1281
+ );
1282
+ this.mcpServer.registerResource(
1283
+ RESOURCE_PATTERNS.TAGS_STATS,
1284
+ RESOURCE_PATTERNS.TAGS_STATS,
1285
+ {
1286
+ title: "Tag Statistics",
1287
+ description: "Get usage statistics for tags in the course",
1288
+ mimeType: "application/json"
1289
+ },
1290
+ async (uri) => {
1291
+ const result = await handleTagsStatsResource(this.courseDB);
1292
+ return {
1293
+ contents: [
1294
+ {
1295
+ uri: uri.href,
1296
+ text: JSON.stringify(result, null, 2),
1297
+ mimeType: "application/json"
1298
+ }
1299
+ ]
1300
+ };
1301
+ }
1302
+ );
1303
+ this.mcpServer.registerResource(
1304
+ RESOURCE_PATTERNS.TAGS_SPECIFIC,
1305
+ new import_mcp.ResourceTemplate("tags://{tagName}", { list: void 0 }),
1306
+ {
1307
+ title: "Specific Tag",
1308
+ description: "Get detailed information about a specific tag",
1309
+ mimeType: "application/json"
1310
+ },
1311
+ async (uri, { tagName }) => {
1312
+ const result = await handleTagSpecificResource(this.courseDB, tagName);
1313
+ return {
1314
+ contents: [
1315
+ {
1316
+ uri: uri.href,
1317
+ text: JSON.stringify(result, null, 2),
1318
+ mimeType: "application/json"
1319
+ }
1320
+ ]
1321
+ };
1322
+ }
1323
+ );
1324
+ this.mcpServer.registerResource(
1325
+ RESOURCE_PATTERNS.TAGS_UNION,
1326
+ new import_mcp.ResourceTemplate("tags://union/{tags}", { list: void 0 }),
1327
+ {
1328
+ title: "Tags Union",
1329
+ description: "Find cards with ANY of the specified tags (format: tag1+tag2)",
1330
+ mimeType: "application/json"
1331
+ },
1332
+ async (uri, { tags }) => {
1333
+ const result = await handleTagsUnionResource(this.courseDB, tags);
1334
+ return {
1335
+ contents: [
1336
+ {
1337
+ uri: uri.href,
1338
+ text: JSON.stringify(result, null, 2),
1339
+ mimeType: "application/json"
1340
+ }
1341
+ ]
1342
+ };
1343
+ }
1344
+ );
1345
+ this.mcpServer.registerResource(
1346
+ RESOURCE_PATTERNS.TAGS_INTERSECT,
1347
+ new import_mcp.ResourceTemplate("tags://intersect/{tags}", { list: void 0 }),
1348
+ {
1349
+ title: "Tags Intersect",
1350
+ description: "Find cards with ALL of the specified tags (format: tag1+tag2)",
1351
+ mimeType: "application/json"
1352
+ },
1353
+ async (uri, { tags }) => {
1354
+ const result = await handleTagsIntersectResource(this.courseDB, tags);
1355
+ return {
1356
+ contents: [
1357
+ {
1358
+ uri: uri.href,
1359
+ text: JSON.stringify(result, null, 2),
1360
+ mimeType: "application/json"
1361
+ }
1362
+ ]
1363
+ };
1364
+ }
1365
+ );
1366
+ this.mcpServer.registerResource(
1367
+ RESOURCE_PATTERNS.TAGS_EXCLUSIVE,
1368
+ new import_mcp.ResourceTemplate("tags://exclusive/{tags}", { list: void 0 }),
1369
+ {
1370
+ title: "Tags Exclusive",
1371
+ description: "Find cards with first tag but NOT second tag (format: tag1-tag2)",
1372
+ mimeType: "application/json"
1373
+ },
1374
+ async (uri, { tags }) => {
1375
+ const result = await handleTagsExclusiveResource(this.courseDB, tags);
1376
+ return {
1377
+ contents: [
1378
+ {
1379
+ uri: uri.href,
1380
+ text: JSON.stringify(result, null, 2),
1381
+ mimeType: "application/json"
1382
+ }
1383
+ ]
1384
+ };
1385
+ }
1386
+ );
1387
+ this.mcpServer.registerResource(
1388
+ RESOURCE_PATTERNS.TAGS_DISTRIBUTION,
1389
+ RESOURCE_PATTERNS.TAGS_DISTRIBUTION,
1390
+ {
1391
+ title: "Tag Distribution",
1392
+ description: "Get frequency distribution of all tags in the course",
1393
+ mimeType: "application/json"
1394
+ },
1395
+ async (uri) => {
1396
+ const result = await handleTagsDistributionResource(this.courseDB);
1397
+ return {
1398
+ contents: [
1399
+ {
1400
+ uri: uri.href,
1401
+ text: JSON.stringify(result, null, 2),
1402
+ mimeType: "application/json"
1403
+ }
1404
+ ]
1405
+ };
1406
+ }
1407
+ );
1408
+ this.mcpServer.registerTool(
1409
+ TOOL_PATTERNS.CREATE_CARD,
1410
+ {
1411
+ title: "Create Card",
1412
+ description: "Create a new course card with specified datashape and content",
1413
+ inputSchema: {
1414
+ datashape: import_zod5.z.string(),
1415
+ data: import_zod5.z.any(),
1416
+ tags: import_zod5.z.array(import_zod5.z.string()).optional(),
1417
+ elo: import_zod5.z.number().optional(),
1418
+ sourceRef: import_zod5.z.string().optional()
1419
+ }
1420
+ },
1421
+ async (input) => {
1422
+ const result = await handleCreateCard(this.courseDB, input);
1423
+ return {
1424
+ content: [
1425
+ {
1426
+ type: "text",
1427
+ text: JSON.stringify(result, null, 2)
1428
+ }
1429
+ ]
1430
+ };
1431
+ }
1432
+ );
1433
+ this.mcpServer.registerTool(
1434
+ TOOL_PATTERNS.UPDATE_CARD,
1435
+ {
1436
+ title: "Update Card",
1437
+ description: "Update an existing course card (data, tags, ELO, sourceRef)",
1438
+ inputSchema: {
1439
+ cardId: import_zod5.z.string(),
1440
+ data: import_zod5.z.any().optional(),
1441
+ tags: import_zod5.z.array(import_zod5.z.string()).optional(),
1442
+ elo: import_zod5.z.number().optional(),
1443
+ sourceRef: import_zod5.z.string().optional()
1444
+ }
1445
+ },
1446
+ async (input) => {
1447
+ const result = await handleUpdateCard(this.courseDB, input);
1448
+ return {
1449
+ content: [
1450
+ {
1451
+ type: "text",
1452
+ text: JSON.stringify(result, null, 2)
1453
+ }
1454
+ ]
1455
+ };
1456
+ }
1457
+ );
1458
+ this.mcpServer.registerTool(
1459
+ TOOL_PATTERNS.TAG_CARD,
1460
+ {
1461
+ title: "Tag Card",
1462
+ description: "Add or remove tags from a course card with optional ELO update",
1463
+ inputSchema: {
1464
+ cardId: import_zod5.z.string(),
1465
+ action: import_zod5.z.enum(["add", "remove"]),
1466
+ tags: import_zod5.z.array(import_zod5.z.string()),
1467
+ updateELO: import_zod5.z.boolean().optional().default(false)
1468
+ }
1469
+ },
1470
+ async (input) => {
1471
+ const result = await handleTagCard(this.courseDB, input);
1472
+ return {
1473
+ content: [
1474
+ {
1475
+ type: "text",
1476
+ text: JSON.stringify(result, null, 2)
1477
+ }
1478
+ ]
1479
+ };
1480
+ }
1481
+ );
1482
+ this.mcpServer.registerTool(
1483
+ TOOL_PATTERNS.DELETE_CARD,
1484
+ {
1485
+ title: "Delete Card",
1486
+ description: "Safely delete a course card with confirmation requirement",
1487
+ inputSchema: {
1488
+ cardId: import_zod5.z.string(),
1489
+ confirm: import_zod5.z.boolean().default(false),
1490
+ reason: import_zod5.z.string().optional()
1491
+ }
1492
+ },
1493
+ async (input) => {
1494
+ const result = await handleDeleteCard(this.courseDB, input);
1495
+ return {
1496
+ content: [
1497
+ {
1498
+ type: "text",
1499
+ text: JSON.stringify(result, null, 2)
1500
+ }
1501
+ ]
1502
+ };
1503
+ }
1504
+ );
1505
+ this.mcpServer.registerPrompt(
1506
+ PROMPT_PATTERNS.FILL_IN_CARD_AUTHORING,
1507
+ {
1508
+ title: "Author Fill-In Card",
1509
+ description: "Generate a fill-in-the-blank or multiple-choice card using Vue-Skuilder syntax.",
1510
+ argsSchema: {}
1511
+ },
1512
+ () => ({
1513
+ messages: [
1514
+ {
1515
+ role: "user",
1516
+ content: {
1517
+ type: "text",
1518
+ text: createFillInCardAuthoringPrompt()
1519
+ }
1520
+ }
1521
+ ]
1522
+ })
1523
+ );
1524
+ this.mcpServer.registerPrompt(
1525
+ PROMPT_PATTERNS.ELO_SCORING_GUIDANCE,
1526
+ {
1527
+ title: "ELO Scoring Guidance",
1528
+ description: "Guidance for assigning ELO difficulty ratings to flashcard content.",
1529
+ argsSchema: {}
1530
+ },
1531
+ () => ({
1532
+ messages: [
1533
+ {
1534
+ role: "user",
1535
+ content: {
1536
+ type: "text",
1537
+ text: createEloScoringGuidancePrompt()
1538
+ }
1539
+ }
1540
+ ]
1541
+ })
1542
+ );
1543
+ if (this.options.enableSourceLinking) {
1544
+ }
1545
+ }
1546
+ async start(transport) {
1547
+ this.transport = transport;
1548
+ await this.mcpServer.connect(transport);
1549
+ }
1550
+ async stop() {
1551
+ if (this.transport) {
1552
+ await this.transport.close();
1553
+ }
1554
+ await this.mcpServer.close();
1555
+ }
1556
+ // Getter for accessing the underlying MCP server (for testing)
1557
+ get server() {
1558
+ return this.mcpServer;
1559
+ }
1560
+ // Getter for accessing the course database
1561
+ get courseDatabase() {
1562
+ return this.courseDB;
1563
+ }
1564
+ };
1565
+
1566
+ // src/examples/local-dev.ts
1567
+ var import_db = require("@vue-skuilder/db");
1568
+ var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
1569
+ var TEST_COURSE_ID = "2aeb8315ef78f3e89ca386992d00825b";
1570
+ async function main() {
1571
+ try {
1572
+ console.error("Starting Vue-Skuilder MCP Server...");
1573
+ console.error(`Using test course: ${TEST_COURSE_ID}`);
1574
+ await (0, import_db.initializeDataLayer)({
1575
+ options: {
1576
+ COUCHDB_PASSWORD: "password",
1577
+ COUCHDB_USERNAME: "admin",
1578
+ COUCHDB_SERVER_PROTOCOL: "http",
1579
+ COUCHDB_SERVER_URL: "localhost:5984"
1580
+ },
1581
+ type: "couch"
1582
+ });
1583
+ const dataLayer = (0, import_db.getDataLayer)();
1584
+ await dataLayer.initialize();
1585
+ const courseDB = dataLayer.getCourseDB(TEST_COURSE_ID);
1586
+ const server = new MCPServer(courseDB, {
1587
+ enableSourceLinking: true,
1588
+ maxCardsPerQuery: 50
1589
+ });
1590
+ const transport = new import_stdio.StdioServerTransport();
1591
+ await server.start(transport);
1592
+ console.error("MCP Server started successfully!");
1593
+ console.error("Resources available: course://config");
1594
+ console.error("Tools available: create_card");
1595
+ } catch (error) {
1596
+ console.error("Failed to start MCP server:", error);
1597
+ process.exit(1);
1598
+ }
1599
+ }
1600
+ main().catch(console.error);
1601
+ // Annotate the CommonJS export names for ESM import in node:
1602
+ 0 && (module.exports = {
1603
+ CourseConfigUriSchema,
1604
+ CreateCardInputSchema,
1605
+ DeleteCardInputSchema,
1606
+ MCPServer,
1607
+ RESOURCE_PATTERNS,
1608
+ TOOL_PATTERNS,
1609
+ TagCardInputSchema,
1610
+ UpdateCardInputSchema,
1611
+ handleCardsAllResource,
1612
+ handleCardsEloResource,
1613
+ handleCardsShapeResource,
1614
+ handleCardsTagResource,
1615
+ handleCourseConfigResource,
1616
+ handleCreateCard,
1617
+ handleDeleteCard,
1618
+ handleShapeSpecificResource,
1619
+ handleShapesAllResource,
1620
+ handleTagCard,
1621
+ handleTagSpecificResource,
1622
+ handleTagsAllResource,
1623
+ handleTagsDistributionResource,
1624
+ handleTagsExclusiveResource,
1625
+ handleTagsIntersectResource,
1626
+ handleTagsStatsResource,
1627
+ handleTagsUnionResource,
1628
+ handleUpdateCard
1629
+ });
1630
+ //# sourceMappingURL=index.js.map