learnhouse-mcp-server 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,489 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * LearnHouse MCP Server
4
+ *
5
+ * Model Context Protocol server for LearnHouse LMS.
6
+ * Provides tools for managing courses, chapters, activities, and users.
7
+ */
8
+ import { FastMCP } from "fastmcp";
9
+ import { z } from "zod";
10
+ import { LearnHouseClient } from "./client.js";
11
+ import fs from "node:fs";
12
+ import path from "node:path";
13
+ // ============================================================
14
+ // Configuration
15
+ // ============================================================
16
+ const config = {
17
+ baseUrl: process.env.LEARNHOUSE_URL || "http://localhost:3000",
18
+ email: process.env.LEARNHOUSE_EMAIL || "",
19
+ password: process.env.LEARNHOUSE_PASSWORD || "",
20
+ orgId: parseInt(process.env.LEARNHOUSE_ORG_ID || "1", 10),
21
+ };
22
+ // Singleton client instance
23
+ let client = null;
24
+ async function getClient() {
25
+ if (!client) {
26
+ client = new LearnHouseClient({
27
+ baseUrl: config.baseUrl,
28
+ orgId: config.orgId,
29
+ });
30
+ if (config.email && config.password) {
31
+ await client.login(config.email, config.password);
32
+ }
33
+ }
34
+ return client;
35
+ }
36
+ // ============================================================
37
+ // MCP Server
38
+ // ============================================================
39
+ const server = new FastMCP({
40
+ name: "LearnHouse MCP Server",
41
+ version: "1.0.0",
42
+ instructions: `
43
+ LearnHouse MCP Server provides tools to manage an LMS (Learning Management System).
44
+
45
+ Available capabilities:
46
+ - **Course Management**: List, create, update, delete courses
47
+ - **Chapter Management**: Organize chapters within courses
48
+ - **Activity Management**: Create and manage learning activities (documents, videos, PDFs)
49
+ - **Content Creation**: Set TipTap document content for activities
50
+ - **Progress Tracking**: Track user progress through courses
51
+ - **Search**: Search across courses and content
52
+
53
+ Organization ID: ${config.orgId}
54
+ API URL: ${config.baseUrl}
55
+ `.trim(),
56
+ });
57
+ server.addTool({
58
+ name: "ping",
59
+ description: "Ping the MCP server to verify it is running and identify version.",
60
+ parameters: z.object({}),
61
+ execute: async () => {
62
+ return "LearnHouse MCP Server is UP (Version 1.0.2 - Fixed PUT)";
63
+ },
64
+ });
65
+ // ============================================================
66
+ // Course Tools
67
+ // ============================================================
68
+ server.addTool({
69
+ name: "list_courses",
70
+ description: "List all courses in the organization",
71
+ parameters: z.object({
72
+ page: z.number().optional().default(1).describe("Page number"),
73
+ limit: z.number().optional().default(50).describe("Number of courses per page"),
74
+ }),
75
+ execute: async (args) => {
76
+ const api = await getClient();
77
+ const courses = await api.listCourses(args.page, args.limit);
78
+ return JSON.stringify(courses, null, 2);
79
+ },
80
+ });
81
+ server.addTool({
82
+ name: "get_course",
83
+ description: "Get detailed information about a specific course",
84
+ parameters: z.object({
85
+ course_uuid: z.string().describe("The UUID of the course"),
86
+ }),
87
+ execute: async (args) => {
88
+ const api = await getClient();
89
+ const course = await api.getCourse(args.course_uuid);
90
+ return JSON.stringify(course, null, 2);
91
+ },
92
+ });
93
+ server.addTool({
94
+ name: "create_course",
95
+ description: "Create a new course",
96
+ parameters: z.object({
97
+ name: z.string().describe("Course name"),
98
+ description: z.string().describe("Course description"),
99
+ public: z.boolean().optional().default(true).describe("Whether the course is public"),
100
+ }),
101
+ execute: async (args) => {
102
+ const api = await getClient();
103
+ const course = await api.createCourse({
104
+ name: args.name,
105
+ description: args.description,
106
+ public: args.public,
107
+ learnings: [],
108
+ });
109
+ return JSON.stringify(course, null, 2);
110
+ },
111
+ });
112
+ server.addTool({
113
+ name: "update_course",
114
+ description: "Update an existing course",
115
+ parameters: z.object({
116
+ course_uuid: z.string().describe("The UUID of the course to update"),
117
+ name: z.string().optional().describe("New course name"),
118
+ description: z.string().optional().describe("New course description"),
119
+ published: z.boolean().optional().describe("Publish/unpublish the course"),
120
+ thumbnail_image: z.string().optional().describe("URL of the thumbnail image"),
121
+ thumbnail_type: z.enum(["IMAGE", "VIDEO"]).optional().describe("Type of thumbnail"),
122
+ }),
123
+ execute: async (args) => {
124
+ const api = await getClient();
125
+ const update = {};
126
+ if (args.name)
127
+ update.name = args.name;
128
+ if (args.description)
129
+ update.description = args.description;
130
+ if (args.published !== undefined)
131
+ update.published = args.published;
132
+ if (args.thumbnail_image)
133
+ update.thumbnail_image = args.thumbnail_image;
134
+ if (args.thumbnail_type)
135
+ update.thumbnail_type = args.thumbnail_type;
136
+ const course = await api.updateCourse(args.course_uuid, update);
137
+ return JSON.stringify(course, null, 2);
138
+ },
139
+ });
140
+ server.addTool({
141
+ name: "delete_course",
142
+ description: "Delete a course (use with caution!)",
143
+ parameters: z.object({
144
+ course_uuid: z.string().describe("The UUID of the course to delete"),
145
+ }),
146
+ execute: async (args) => {
147
+ const api = await getClient();
148
+ await api.deleteCourse(args.course_uuid);
149
+ return `Course ${args.course_uuid} deleted successfully`;
150
+ },
151
+ });
152
+ server.addTool({
153
+ name: "upload_course_thumbnail",
154
+ description: "Upload a thumbnail image for a course from a local file path",
155
+ parameters: z.object({
156
+ course_uuid: z.string().describe("The UUID of the course"),
157
+ file_path: z.string().describe("Local absolute path to the image file"),
158
+ type: z.enum(["IMAGE", "VIDEO"]).optional().default("IMAGE").describe("Thumbnail type"),
159
+ }),
160
+ execute: async (args) => {
161
+ const api = await getClient();
162
+ if (!fs.existsSync(args.file_path)) {
163
+ throw new Error(`File not found: ${args.file_path}`);
164
+ }
165
+ const buffer = fs.readFileSync(args.file_path);
166
+ const fileName = path.basename(args.file_path);
167
+ const course = await api.uploadCourseThumbnail(args.course_uuid, buffer, fileName, args.type);
168
+ return JSON.stringify(course, null, 2);
169
+ },
170
+ });
171
+ // ============================================================
172
+ // Chapter Tools
173
+ // ============================================================
174
+ server.addTool({
175
+ name: "list_chapters",
176
+ description: "List all chapters in a course",
177
+ parameters: z.object({
178
+ course_id: z.number().describe("The numeric ID of the course"),
179
+ }),
180
+ execute: async (args) => {
181
+ const api = await getClient();
182
+ const chapters = await api.listChapters(args.course_id);
183
+ return JSON.stringify(chapters, null, 2);
184
+ },
185
+ });
186
+ server.addTool({
187
+ name: "get_chapter",
188
+ description: "Get details of a specific chapter",
189
+ parameters: z.object({
190
+ chapter_id: z.number().describe("The ID of the chapter"),
191
+ }),
192
+ execute: async (args) => {
193
+ const api = await getClient();
194
+ const chapter = await api.getChapter(args.chapter_id);
195
+ return JSON.stringify(chapter, null, 2);
196
+ },
197
+ });
198
+ server.addTool({
199
+ name: "create_chapter",
200
+ description: "Create a new chapter in a course",
201
+ parameters: z.object({
202
+ course_id: z.number().describe("The numeric ID of the course"),
203
+ org_id: z.number().optional().default(config.orgId).describe("Organization ID"),
204
+ name: z.string().describe("Chapter name"),
205
+ description: z.string().optional().default("").describe("Chapter description"),
206
+ }),
207
+ execute: async (args) => {
208
+ const api = await getClient();
209
+ const chapter = await api.createChapter({
210
+ name: args.name,
211
+ description: args.description || "",
212
+ course_id: args.course_id,
213
+ org_id: args.org_id,
214
+ });
215
+ return JSON.stringify(chapter, null, 2);
216
+ },
217
+ });
218
+ server.addTool({
219
+ name: "update_chapter",
220
+ description: "Update an existing chapter",
221
+ parameters: z.object({
222
+ chapter_id: z.number().describe("The ID of the chapter to update"),
223
+ name: z.string().optional().describe("New chapter name"),
224
+ description: z.string().optional().describe("New chapter description"),
225
+ }),
226
+ execute: async (args) => {
227
+ const api = await getClient();
228
+ const update = {};
229
+ if (args.name)
230
+ update.name = args.name;
231
+ if (args.description !== undefined)
232
+ update.description = args.description;
233
+ const chapter = await api.updateChapter(args.chapter_id, update);
234
+ return JSON.stringify(chapter, null, 2);
235
+ },
236
+ });
237
+ // ============================================================
238
+ // Activity Tools
239
+ // ============================================================
240
+ server.addTool({
241
+ name: "list_activities",
242
+ description: "List all activities in a chapter",
243
+ parameters: z.object({
244
+ chapter_id: z.number().describe("The ID of the chapter"),
245
+ }),
246
+ execute: async (args) => {
247
+ const api = await getClient();
248
+ const activities = await api.listActivities(args.chapter_id);
249
+ return JSON.stringify(activities, null, 2);
250
+ },
251
+ });
252
+ server.addTool({
253
+ name: "get_activity",
254
+ description: "Get details of a specific activity",
255
+ parameters: z.object({
256
+ activity_uuid: z.string().describe("The UUID of the activity"),
257
+ }),
258
+ execute: async (args) => {
259
+ const api = await getClient();
260
+ const activity = await api.getActivity(args.activity_uuid);
261
+ return JSON.stringify(activity, null, 2);
262
+ },
263
+ });
264
+ server.addTool({
265
+ name: "create_activity",
266
+ description: "Create a new activity in a chapter",
267
+ parameters: z.object({
268
+ chapter_id: z.number().describe("The ID of the chapter"),
269
+ name: z.string().describe("Activity name"),
270
+ activity_type: z.enum(["TYPE_DYNAMIC", "TYPE_VIDEO", "TYPE_PDF"]).optional().default("TYPE_DYNAMIC").describe("Type of activity"),
271
+ activity_sub_type: z.enum(["SUBTYPE_DYNAMIC_PAGE", "SUBTYPE_VIDEO_YOUTUBE", "SUBTYPE_VIDEO_HOSTED", "SUBTYPE_PDF"]).optional().default("SUBTYPE_DYNAMIC_PAGE").describe("Subtype of activity"),
272
+ published: z.boolean().optional().default(false).describe("Whether the activity is published"),
273
+ }),
274
+ execute: async (args) => {
275
+ const api = await getClient();
276
+ const activity = await api.createActivity({
277
+ name: args.name,
278
+ chapter_id: args.chapter_id,
279
+ activity_type: args.activity_type,
280
+ activity_sub_type: args.activity_sub_type,
281
+ published: args.published,
282
+ });
283
+ return JSON.stringify(activity, null, 2);
284
+ },
285
+ });
286
+ server.addTool({
287
+ name: "update_activity",
288
+ description: "Update an existing activity",
289
+ parameters: z.object({
290
+ activity_uuid: z.string().describe("The UUID of the activity to update"),
291
+ name: z.string().optional().describe("New activity name"),
292
+ published: z.boolean().optional().describe("Publish/unpublish the activity"),
293
+ }),
294
+ execute: async (args) => {
295
+ const api = await getClient();
296
+ const update = {};
297
+ if (args.name)
298
+ update.name = args.name;
299
+ if (args.published !== undefined)
300
+ update.published = args.published;
301
+ const activity = await api.updateActivity(args.activity_uuid, update);
302
+ return JSON.stringify(activity, null, 2);
303
+ },
304
+ });
305
+ server.addTool({
306
+ name: "publish_activity",
307
+ description: "Publish an activity to make it visible",
308
+ parameters: z.object({
309
+ activity_uuid: z.string().describe("The UUID of the activity to publish"),
310
+ }),
311
+ execute: async (args) => {
312
+ const api = await getClient();
313
+ const activity = await api.updateActivity(args.activity_uuid, { published: true });
314
+ return JSON.stringify(activity, null, 2);
315
+ },
316
+ });
317
+ // ============================================================
318
+ // Content Tools
319
+ // ============================================================
320
+ server.addTool({
321
+ name: "set_document_content",
322
+ description: "Set the TipTap document content for a document activity. Content must be valid TipTap JSON.",
323
+ parameters: z.object({
324
+ activity_uuid: z.string().describe("The UUID of the activity"),
325
+ content: z.string().describe("TipTap JSON content as a string"),
326
+ }),
327
+ execute: async (args) => {
328
+ const api = await getClient();
329
+ let content;
330
+ try {
331
+ content = JSON.parse(args.content);
332
+ }
333
+ catch {
334
+ throw new Error("Invalid JSON content. Please provide valid TipTap JSON.");
335
+ }
336
+ await api.updateActivity(args.activity_uuid, { content });
337
+ return `Document content updated for activity ${args.activity_uuid}`;
338
+ },
339
+ });
340
+ server.addTool({
341
+ name: "set_video_content",
342
+ description: "Set video URL for a video activity",
343
+ parameters: z.object({
344
+ activity_uuid: z.string().describe("The UUID of the activity"),
345
+ video_url: z.string().describe("YouTube or hosted video URL"),
346
+ }),
347
+ execute: async (args) => {
348
+ const api = await getClient();
349
+ const activity = await api.updateActivity(args.activity_uuid, {
350
+ content: { video_url: args.video_url },
351
+ });
352
+ return `Video URL set for activity ${args.activity_uuid}: ${args.video_url}`;
353
+ },
354
+ });
355
+ // ============================================================
356
+ // User & Organization Tools
357
+ // ============================================================
358
+ server.addTool({
359
+ name: "get_current_user",
360
+ description: "Get information about the currently authenticated user",
361
+ parameters: z.object({}),
362
+ execute: async () => {
363
+ const api = await getClient();
364
+ const user = await api.getCurrentUser();
365
+ return JSON.stringify(user, null, 2);
366
+ },
367
+ });
368
+ server.addTool({
369
+ name: "get_organization",
370
+ description: "Get organization details",
371
+ parameters: z.object({
372
+ org_id: z.number().optional().default(config.orgId).describe("Organization ID"),
373
+ }),
374
+ execute: async (args) => {
375
+ const api = await getClient();
376
+ const org = await api.getOrganization(args.org_id);
377
+ return JSON.stringify(org, null, 2);
378
+ },
379
+ });
380
+ // ============================================================
381
+ // Collection Tools
382
+ // ============================================================
383
+ server.addTool({
384
+ name: "list_collections",
385
+ description: "List all collections in the organization",
386
+ parameters: z.object({
387
+ page: z.number().optional().default(1).describe("Page number"),
388
+ limit: z.number().optional().default(50).describe("Number of collections per page"),
389
+ }),
390
+ execute: async (args) => {
391
+ const api = await getClient();
392
+ const collections = await api.listCollections(args.page, args.limit);
393
+ return JSON.stringify(collections, null, 2);
394
+ },
395
+ });
396
+ server.addTool({
397
+ name: "get_collection",
398
+ description: "Get detailed information about a specific collection",
399
+ parameters: z.object({
400
+ collection_uuid: z.string().describe("The UUID of the collection"),
401
+ }),
402
+ execute: async (args) => {
403
+ const api = await getClient();
404
+ const collection = await api.getCollection(args.collection_uuid);
405
+ return JSON.stringify(collection, null, 2);
406
+ },
407
+ });
408
+ server.addTool({
409
+ name: "create_collection",
410
+ description: "Create a new collection",
411
+ parameters: z.object({
412
+ name: z.string().describe("Collection name"),
413
+ description: z.string().optional().describe("Collection description"),
414
+ public: z.boolean().optional().default(true).describe("Whether the collection is public"),
415
+ courses: z.array(z.number()).optional().describe("List of course IDs to include"),
416
+ }),
417
+ execute: async (args) => {
418
+ const api = await getClient();
419
+ const collection = await api.createCollection({
420
+ name: args.name,
421
+ description: args.description,
422
+ public: args.public,
423
+ courses: args.courses,
424
+ });
425
+ return JSON.stringify(collection, null, 2);
426
+ },
427
+ });
428
+ server.addTool({
429
+ name: "delete_collection",
430
+ description: "Delete a collection",
431
+ parameters: z.object({
432
+ collection_uuid: z.string().describe("The UUID of the collection to delete"),
433
+ }),
434
+ execute: async (args) => {
435
+ const api = await getClient();
436
+ await api.deleteCollection(args.collection_uuid);
437
+ return `Collection ${args.collection_uuid} deleted successfully`;
438
+ },
439
+ });
440
+ // ============================================================
441
+ // Progress Tools
442
+ // ============================================================
443
+ server.addTool({
444
+ name: "get_course_progress",
445
+ description: "Get user's progress for a specific course",
446
+ parameters: z.object({
447
+ course_uuid: z.string().describe("The UUID of the course"),
448
+ }),
449
+ execute: async (args) => {
450
+ const api = await getClient();
451
+ const progress = await api.getCourseProgress(args.course_uuid);
452
+ return JSON.stringify(progress, null, 2);
453
+ },
454
+ });
455
+ server.addTool({
456
+ name: "mark_activity_complete",
457
+ description: "Mark an activity as completed for the current user",
458
+ parameters: z.object({
459
+ activity_uuid: z.string().describe("The UUID of the activity to mark complete"),
460
+ }),
461
+ execute: async (args) => {
462
+ const api = await getClient();
463
+ await api.markActivityComplete(args.activity_uuid);
464
+ return `Activity ${args.activity_uuid} marked as complete`;
465
+ },
466
+ });
467
+ // ============================================================
468
+ // Search Tools
469
+ // ============================================================
470
+ server.addTool({
471
+ name: "search",
472
+ description: "Search for courses and content in the organization",
473
+ parameters: z.object({
474
+ org_slug: z.string().optional().default("default").describe("Organization slug"),
475
+ query: z.string().describe("Search query"),
476
+ }),
477
+ execute: async (args) => {
478
+ const api = await getClient();
479
+ const results = await api.search(args.org_slug, args.query);
480
+ return JSON.stringify(results, null, 2);
481
+ },
482
+ });
483
+ // ============================================================
484
+ // Start Server
485
+ // ============================================================
486
+ server.start({
487
+ transportType: "stdio",
488
+ });
489
+ console.error("LearnHouse MCP Server started");
@@ -0,0 +1,6 @@
1
+ /**
2
+ * LearnHouse API Client Tests
3
+ *
4
+ * Run with: npm test
5
+ */
6
+ export {};
@@ -0,0 +1,166 @@
1
+ /**
2
+ * LearnHouse API Client Tests
3
+ *
4
+ * Run with: npm test
5
+ */
6
+ import { LearnHouseClient } from "./client.js";
7
+ const BASE_URL = process.env.LEARNHOUSE_URL || "http://localhost:3000";
8
+ const ORG_ID = parseInt(process.env.LEARNHOUSE_ORG_ID || "1");
9
+ const EMAIL = process.env.LEARNHOUSE_EMAIL || "admin@ai-automate.me";
10
+ const PASSWORD = process.env.LEARNHOUSE_PASSWORD || "";
11
+ async function runTests() {
12
+ console.log("🧪 LearnHouse API Client Tests\n");
13
+ console.log(` Base URL: ${BASE_URL}`);
14
+ console.log(` Org ID: ${ORG_ID}`);
15
+ console.log(` Email: ${EMAIL}\n`);
16
+ const client = new LearnHouseClient({
17
+ baseUrl: BASE_URL,
18
+ orgId: ORG_ID,
19
+ });
20
+ let passed = 0;
21
+ let failed = 0;
22
+ async function test(name, fn) {
23
+ try {
24
+ await fn();
25
+ console.log(` ✅ ${name}`);
26
+ passed++;
27
+ }
28
+ catch (error) {
29
+ console.log(` ❌ ${name}`);
30
+ console.log(` Error: ${error instanceof Error ? error.message : error}`);
31
+ failed++;
32
+ }
33
+ }
34
+ // ===== Authentication Tests =====
35
+ console.log("\n📋 Authentication Tests");
36
+ await test("Health check (no auth)", async () => {
37
+ const result = await client.healthCheck();
38
+ if (!result.status)
39
+ throw new Error("No status in response");
40
+ });
41
+ if (!PASSWORD) {
42
+ console.log("\n⚠️ Set LEARNHOUSE_PASSWORD to run authenticated tests\n");
43
+ return;
44
+ }
45
+ await test("Login with credentials", async () => {
46
+ const result = await client.login(EMAIL, PASSWORD);
47
+ if (!result.tokens.access_token)
48
+ throw new Error("No access token");
49
+ if (!result.user.email)
50
+ throw new Error("No user email");
51
+ });
52
+ await test("Get current user", async () => {
53
+ const user = await client.getCurrentUser();
54
+ if (!user.email)
55
+ throw new Error("No email in user");
56
+ console.log(` User: ${user.username} (${user.email})`);
57
+ });
58
+ // ===== Organization Tests =====
59
+ console.log("\n📋 Organization Tests");
60
+ await test("List organizations", async () => {
61
+ const orgs = await client.listOrganizations();
62
+ if (!Array.isArray(orgs))
63
+ throw new Error("Expected array");
64
+ console.log(` Found ${orgs.length} organization(s)`);
65
+ });
66
+ await test("Get organization by ID", async () => {
67
+ const org = await client.getOrganization(ORG_ID);
68
+ if (!org.name)
69
+ throw new Error("No org name");
70
+ console.log(` Org: ${org.name} (${org.slug})`);
71
+ });
72
+ // ===== Course Tests =====
73
+ console.log("\n📋 Course Tests");
74
+ let testCourse = null;
75
+ await test("List courses", async () => {
76
+ const courses = await client.listCourses(1, 10);
77
+ if (!Array.isArray(courses))
78
+ throw new Error("Expected array");
79
+ console.log(` Found ${courses.length} course(s)`);
80
+ if (courses.length > 0) {
81
+ testCourse = courses[0];
82
+ }
83
+ });
84
+ if (testCourse) {
85
+ await test("Get course by UUID", async () => {
86
+ const course = await client.getCourse(testCourse.course_uuid);
87
+ if (course.id !== testCourse.id)
88
+ throw new Error("Course ID mismatch");
89
+ });
90
+ await test("Get course metadata", async () => {
91
+ const meta = await client.getCourseMeta(testCourse.course_uuid);
92
+ if (!meta.chapters)
93
+ throw new Error("No chapters in metadata");
94
+ console.log(` Course "${meta.name}" has ${meta.chapters.length} chapters`);
95
+ });
96
+ }
97
+ // ===== Chapter Tests =====
98
+ console.log("\n📋 Chapter Tests");
99
+ let testChapter = null;
100
+ if (testCourse) {
101
+ await test("List chapters for course", async () => {
102
+ const chapters = await client.listChapters(testCourse.id, 1, 10);
103
+ if (!Array.isArray(chapters))
104
+ throw new Error("Expected array");
105
+ console.log(` Found ${chapters.length} chapter(s)`);
106
+ if (chapters.length > 0) {
107
+ testChapter = chapters[0];
108
+ }
109
+ });
110
+ if (testChapter) {
111
+ await test("Get chapter by ID", async () => {
112
+ const chapter = await client.getChapter(testChapter.id);
113
+ if (chapter.id !== testChapter.id)
114
+ throw new Error("Chapter ID mismatch");
115
+ console.log(` Chapter: ${chapter.name}`);
116
+ });
117
+ }
118
+ }
119
+ // ===== Activity Tests =====
120
+ console.log("\n📋 Activity Tests");
121
+ let testActivity = null;
122
+ if (testChapter) {
123
+ await test("List activities for chapter", async () => {
124
+ const activities = await client.listActivities(testChapter.id);
125
+ if (!Array.isArray(activities))
126
+ throw new Error("Expected array");
127
+ console.log(` Found ${activities.length} activity(ies)`);
128
+ if (activities.length > 0) {
129
+ testActivity = activities[0];
130
+ }
131
+ });
132
+ if (testActivity) {
133
+ await test("Get activity by UUID", async () => {
134
+ const activity = await client.getActivity(testActivity.activity_uuid);
135
+ if (activity.id !== testActivity.id)
136
+ throw new Error("Activity ID mismatch");
137
+ console.log(` Activity: ${activity.name}`);
138
+ });
139
+ }
140
+ }
141
+ // ===== Collection Tests =====
142
+ console.log("\n📋 Collection Tests");
143
+ await test("List collections", async () => {
144
+ const collections = await client.listCollections(1, 10);
145
+ if (!Array.isArray(collections))
146
+ throw new Error("Expected array");
147
+ console.log(` Found ${collections.length} collection(s)`);
148
+ });
149
+ // ===== Search Tests =====
150
+ console.log("\n📋 Search Tests");
151
+ await test("Search for courses", async () => {
152
+ const results = await client.search("MCP", "courses");
153
+ console.log(` Found ${results.courses?.length || 0} course(s) matching "MCP"`);
154
+ });
155
+ // ===== Summary =====
156
+ console.log("\n" + "=".repeat(50));
157
+ console.log(`📊 Results: ${passed} passed, ${failed} failed`);
158
+ console.log("=".repeat(50) + "\n");
159
+ if (failed > 0) {
160
+ process.exit(1);
161
+ }
162
+ }
163
+ runTests().catch((error) => {
164
+ console.error("Fatal error:", error);
165
+ process.exit(1);
166
+ });