learnhouse-mcp-server 1.0.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 +564 -0
- package/package.json +35 -0
- package/src/apply_aesthetics.ts +46 -0
- package/src/client.ts +480 -0
- package/src/index.ts +532 -0
- package/src/test-api.ts +185 -0
- package/src/types.ts +243 -0
- package/tsconfig.json +17 -0
package/src/test-api.ts
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LearnHouse API Client Tests
|
|
3
|
+
*
|
|
4
|
+
* Run with: npm test
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { LearnHouseClient } from "./client.js";
|
|
8
|
+
|
|
9
|
+
const BASE_URL = process.env.LEARNHOUSE_URL || "http://localhost:3000";
|
|
10
|
+
const ORG_ID = parseInt(process.env.LEARNHOUSE_ORG_ID || "1");
|
|
11
|
+
const EMAIL = process.env.LEARNHOUSE_EMAIL || "admin@ai-automate.me";
|
|
12
|
+
const PASSWORD = process.env.LEARNHOUSE_PASSWORD || "";
|
|
13
|
+
|
|
14
|
+
async function runTests() {
|
|
15
|
+
console.log("๐งช LearnHouse API Client Tests\n");
|
|
16
|
+
console.log(` Base URL: ${BASE_URL}`);
|
|
17
|
+
console.log(` Org ID: ${ORG_ID}`);
|
|
18
|
+
console.log(` Email: ${EMAIL}\n`);
|
|
19
|
+
|
|
20
|
+
const client = new LearnHouseClient({
|
|
21
|
+
baseUrl: BASE_URL,
|
|
22
|
+
orgId: ORG_ID,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
let passed = 0;
|
|
26
|
+
let failed = 0;
|
|
27
|
+
|
|
28
|
+
async function test(name: string, fn: () => Promise<void>) {
|
|
29
|
+
try {
|
|
30
|
+
await fn();
|
|
31
|
+
console.log(` โ
${name}`);
|
|
32
|
+
passed++;
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.log(` โ ${name}`);
|
|
35
|
+
console.log(` Error: ${error instanceof Error ? error.message : error}`);
|
|
36
|
+
failed++;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ===== Authentication Tests =====
|
|
41
|
+
console.log("\n๐ Authentication Tests");
|
|
42
|
+
|
|
43
|
+
await test("Health check (no auth)", async () => {
|
|
44
|
+
const result = await client.healthCheck();
|
|
45
|
+
if (!result.status) throw new Error("No status in response");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if (!PASSWORD) {
|
|
49
|
+
console.log("\nโ ๏ธ Set LEARNHOUSE_PASSWORD to run authenticated tests\n");
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
await test("Login with credentials", async () => {
|
|
54
|
+
const result = await client.login(EMAIL, PASSWORD);
|
|
55
|
+
if (!result.tokens.access_token) throw new Error("No access token");
|
|
56
|
+
if (!result.user.email) throw new Error("No user email");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
await test("Get current user", async () => {
|
|
60
|
+
const user = await client.getCurrentUser();
|
|
61
|
+
if (!user.email) throw new Error("No email in user");
|
|
62
|
+
console.log(` User: ${user.username} (${user.email})`);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// ===== Organization Tests =====
|
|
66
|
+
console.log("\n๐ Organization Tests");
|
|
67
|
+
|
|
68
|
+
await test("List organizations", async () => {
|
|
69
|
+
const orgs = await client.listOrganizations();
|
|
70
|
+
if (!Array.isArray(orgs)) throw new Error("Expected array");
|
|
71
|
+
console.log(` Found ${orgs.length} organization(s)`);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
await test("Get organization by ID", async () => {
|
|
75
|
+
const org = await client.getOrganization(ORG_ID);
|
|
76
|
+
if (!org.name) throw new Error("No org name");
|
|
77
|
+
console.log(` Org: ${org.name} (${org.slug})`);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// ===== Course Tests =====
|
|
81
|
+
console.log("\n๐ Course Tests");
|
|
82
|
+
|
|
83
|
+
let testCourse: Awaited<ReturnType<typeof client.getCourse>> | null = null;
|
|
84
|
+
|
|
85
|
+
await test("List courses", async () => {
|
|
86
|
+
const courses = await client.listCourses(1, 10);
|
|
87
|
+
if (!Array.isArray(courses)) throw new Error("Expected array");
|
|
88
|
+
console.log(` Found ${courses.length} course(s)`);
|
|
89
|
+
if (courses.length > 0) {
|
|
90
|
+
testCourse = courses[0];
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
if (testCourse) {
|
|
95
|
+
await test("Get course by UUID", async () => {
|
|
96
|
+
const course = await client.getCourse(testCourse!.course_uuid);
|
|
97
|
+
if (course.id !== testCourse!.id) throw new Error("Course ID mismatch");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
await test("Get course metadata", async () => {
|
|
101
|
+
const meta = await client.getCourseMeta(testCourse!.course_uuid);
|
|
102
|
+
if (!meta.chapters) throw new Error("No chapters in metadata");
|
|
103
|
+
console.log(` Course "${meta.name}" has ${meta.chapters.length} chapters`);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ===== Chapter Tests =====
|
|
108
|
+
console.log("\n๐ Chapter Tests");
|
|
109
|
+
|
|
110
|
+
let testChapter: Awaited<ReturnType<typeof client.getChapter>> | null = null;
|
|
111
|
+
|
|
112
|
+
if (testCourse) {
|
|
113
|
+
await test("List chapters for course", async () => {
|
|
114
|
+
const chapters = await client.listChapters(testCourse!.id, 1, 10);
|
|
115
|
+
if (!Array.isArray(chapters)) throw new Error("Expected array");
|
|
116
|
+
console.log(` Found ${chapters.length} chapter(s)`);
|
|
117
|
+
if (chapters.length > 0) {
|
|
118
|
+
testChapter = chapters[0];
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
if (testChapter) {
|
|
123
|
+
await test("Get chapter by ID", async () => {
|
|
124
|
+
const chapter = await client.getChapter(testChapter!.id);
|
|
125
|
+
if (chapter.id !== testChapter!.id) throw new Error("Chapter ID mismatch");
|
|
126
|
+
console.log(` Chapter: ${chapter.name}`);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ===== Activity Tests =====
|
|
132
|
+
console.log("\n๐ Activity Tests");
|
|
133
|
+
|
|
134
|
+
let testActivity: Awaited<ReturnType<typeof client.getActivity>> | null = null;
|
|
135
|
+
|
|
136
|
+
if (testChapter) {
|
|
137
|
+
await test("List activities for chapter", async () => {
|
|
138
|
+
const activities = await client.listActivities(testChapter!.id);
|
|
139
|
+
if (!Array.isArray(activities)) throw new Error("Expected array");
|
|
140
|
+
console.log(` Found ${activities.length} activity(ies)`);
|
|
141
|
+
if (activities.length > 0) {
|
|
142
|
+
testActivity = activities[0];
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
if (testActivity) {
|
|
147
|
+
await test("Get activity by UUID", async () => {
|
|
148
|
+
const activity = await client.getActivity(testActivity!.activity_uuid);
|
|
149
|
+
if (activity.id !== testActivity!.id) throw new Error("Activity ID mismatch");
|
|
150
|
+
console.log(` Activity: ${activity.name}`);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ===== Collection Tests =====
|
|
156
|
+
console.log("\n๐ Collection Tests");
|
|
157
|
+
|
|
158
|
+
await test("List collections", async () => {
|
|
159
|
+
const collections = await client.listCollections(1, 10);
|
|
160
|
+
if (!Array.isArray(collections)) throw new Error("Expected array");
|
|
161
|
+
console.log(` Found ${collections.length} collection(s)`);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// ===== Search Tests =====
|
|
165
|
+
console.log("\n๐ Search Tests");
|
|
166
|
+
|
|
167
|
+
await test("Search for courses", async () => {
|
|
168
|
+
const results = await client.search("MCP", "courses");
|
|
169
|
+
console.log(` Found ${results.courses?.length || 0} course(s) matching "MCP"`);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// ===== Summary =====
|
|
173
|
+
console.log("\n" + "=".repeat(50));
|
|
174
|
+
console.log(`๐ Results: ${passed} passed, ${failed} failed`);
|
|
175
|
+
console.log("=".repeat(50) + "\n");
|
|
176
|
+
|
|
177
|
+
if (failed > 0) {
|
|
178
|
+
process.exit(1);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
runTests().catch((error) => {
|
|
183
|
+
console.error("Fatal error:", error);
|
|
184
|
+
process.exit(1);
|
|
185
|
+
});
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LearnHouse API Client Configuration
|
|
3
|
+
*/
|
|
4
|
+
export interface LearnHouseConfig {
|
|
5
|
+
baseUrl: string;
|
|
6
|
+
orgId: number;
|
|
7
|
+
accessToken?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Activity types supported by LearnHouse
|
|
12
|
+
*/
|
|
13
|
+
export enum ActivityType {
|
|
14
|
+
TYPE_DYNAMIC = "TYPE_DYNAMIC",
|
|
15
|
+
TYPE_VIDEO = "TYPE_VIDEO",
|
|
16
|
+
TYPE_DOCUMENT = "TYPE_DOCUMENT",
|
|
17
|
+
TYPE_ASSIGNMENT = "TYPE_ASSIGNMENT",
|
|
18
|
+
TYPE_CUSTOM = "TYPE_CUSTOM",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export enum ActivitySubType {
|
|
22
|
+
SUBTYPE_DYNAMIC_PAGE = "SUBTYPE_DYNAMIC_PAGE",
|
|
23
|
+
SUBTYPE_VIDEO_YOUTUBE = "SUBTYPE_VIDEO_YOUTUBE",
|
|
24
|
+
SUBTYPE_VIDEO_HOSTED = "SUBTYPE_VIDEO_HOSTED",
|
|
25
|
+
SUBTYPE_DOCUMENT_PDF = "SUBTYPE_DOCUMENT_PDF",
|
|
26
|
+
SUBTYPE_DOCUMENT_DOC = "SUBTYPE_DOCUMENT_DOC",
|
|
27
|
+
SUBTYPE_ASSIGNMENT_ANY = "SUBTYPE_ASSIGNMENT_ANY",
|
|
28
|
+
SUBTYPE_CUSTOM = "SUBTYPE_CUSTOM",
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* TipTap content node types
|
|
33
|
+
*/
|
|
34
|
+
export interface TipTapNode {
|
|
35
|
+
type: string;
|
|
36
|
+
attrs?: Record<string, unknown>;
|
|
37
|
+
content?: TipTapNode[];
|
|
38
|
+
text?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface TipTapDocument {
|
|
42
|
+
type: "doc";
|
|
43
|
+
content: TipTapNode[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* User types
|
|
48
|
+
*/
|
|
49
|
+
export interface User {
|
|
50
|
+
id: number;
|
|
51
|
+
user_uuid: string;
|
|
52
|
+
email: string;
|
|
53
|
+
username: string;
|
|
54
|
+
first_name?: string;
|
|
55
|
+
last_name?: string;
|
|
56
|
+
avatar_image?: string;
|
|
57
|
+
creation_date: string;
|
|
58
|
+
update_date: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface LoginResponse {
|
|
62
|
+
user: User;
|
|
63
|
+
tokens: {
|
|
64
|
+
access_token: string;
|
|
65
|
+
refresh_token: string;
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Organization types
|
|
71
|
+
*/
|
|
72
|
+
export interface Organization {
|
|
73
|
+
id: number;
|
|
74
|
+
org_uuid: string;
|
|
75
|
+
name: string;
|
|
76
|
+
slug: string;
|
|
77
|
+
description?: string;
|
|
78
|
+
logo_url?: string;
|
|
79
|
+
creation_date: string;
|
|
80
|
+
update_date: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Course types
|
|
85
|
+
*/
|
|
86
|
+
export interface Course {
|
|
87
|
+
id: number;
|
|
88
|
+
course_uuid: string;
|
|
89
|
+
name: string;
|
|
90
|
+
description: string;
|
|
91
|
+
about?: string;
|
|
92
|
+
public: boolean;
|
|
93
|
+
org_id: number;
|
|
94
|
+
thumbnail_image?: string;
|
|
95
|
+
thumbnail_video?: string;
|
|
96
|
+
thumbnail_type: "IMAGE" | "VIDEO";
|
|
97
|
+
learnings?: string;
|
|
98
|
+
tags?: string;
|
|
99
|
+
creation_date: string;
|
|
100
|
+
update_date: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface CourseCreate {
|
|
104
|
+
name: string;
|
|
105
|
+
description: string;
|
|
106
|
+
public?: boolean;
|
|
107
|
+
about?: string;
|
|
108
|
+
learnings?: string[];
|
|
109
|
+
tags?: string[];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface CourseUpdate {
|
|
113
|
+
name?: string;
|
|
114
|
+
description?: string;
|
|
115
|
+
public?: boolean;
|
|
116
|
+
about?: string;
|
|
117
|
+
learnings?: string;
|
|
118
|
+
tags?: string;
|
|
119
|
+
thumbnail_image?: string;
|
|
120
|
+
thumbnail_type?: "IMAGE" | "VIDEO";
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface FullCourse extends Course {
|
|
124
|
+
chapters: Chapter[];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Chapter types
|
|
129
|
+
*/
|
|
130
|
+
export interface Chapter {
|
|
131
|
+
id: number;
|
|
132
|
+
chapter_uuid: string;
|
|
133
|
+
name: string;
|
|
134
|
+
description?: string;
|
|
135
|
+
org_id: number;
|
|
136
|
+
course_id: number;
|
|
137
|
+
activities: Activity[];
|
|
138
|
+
creation_date: string;
|
|
139
|
+
update_date: string;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface ChapterCreate {
|
|
143
|
+
name: string;
|
|
144
|
+
description?: string;
|
|
145
|
+
org_id: number;
|
|
146
|
+
course_id: number;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface ChapterUpdate {
|
|
150
|
+
name?: string;
|
|
151
|
+
description?: string;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Activity types
|
|
156
|
+
*/
|
|
157
|
+
export interface Activity {
|
|
158
|
+
id: number;
|
|
159
|
+
activity_uuid: string;
|
|
160
|
+
name: string;
|
|
161
|
+
activity_type: ActivityType;
|
|
162
|
+
activity_sub_type: ActivitySubType;
|
|
163
|
+
content: TipTapDocument | Record<string, unknown>;
|
|
164
|
+
details?: Record<string, unknown>;
|
|
165
|
+
published: boolean;
|
|
166
|
+
org_id: number;
|
|
167
|
+
course_id: number;
|
|
168
|
+
creation_date: string;
|
|
169
|
+
update_date: string;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export interface ActivityCreate {
|
|
173
|
+
name: string;
|
|
174
|
+
chapter_id: number;
|
|
175
|
+
activity_type?: ActivityType;
|
|
176
|
+
activity_sub_type?: ActivitySubType;
|
|
177
|
+
content?: TipTapDocument | Record<string, unknown>;
|
|
178
|
+
published?: boolean;
|
|
179
|
+
details?: Record<string, unknown>;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export interface ActivityUpdate {
|
|
183
|
+
name?: string;
|
|
184
|
+
content?: TipTapDocument | Record<string, unknown>;
|
|
185
|
+
activity_type?: ActivityType;
|
|
186
|
+
activity_sub_type?: ActivitySubType;
|
|
187
|
+
published?: boolean;
|
|
188
|
+
details?: Record<string, unknown>;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Collection types
|
|
193
|
+
*/
|
|
194
|
+
export interface Collection {
|
|
195
|
+
id: number;
|
|
196
|
+
collection_uuid: string;
|
|
197
|
+
name: string;
|
|
198
|
+
description?: string;
|
|
199
|
+
org_id: number;
|
|
200
|
+
courses: Course[];
|
|
201
|
+
creation_date: string;
|
|
202
|
+
update_date: string;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export interface CollectionCreate {
|
|
206
|
+
name: string;
|
|
207
|
+
description?: string;
|
|
208
|
+
org_id: number;
|
|
209
|
+
courses?: number[];
|
|
210
|
+
public?: boolean;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Trail/Progress types
|
|
215
|
+
*/
|
|
216
|
+
export interface TrailProgress {
|
|
217
|
+
course_uuid: string;
|
|
218
|
+
completion_percentage: number;
|
|
219
|
+
completed_activities: number;
|
|
220
|
+
total_activities: number;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export interface ActivityStatus {
|
|
224
|
+
activity_uuid: string;
|
|
225
|
+
completed: boolean;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Search types
|
|
230
|
+
*/
|
|
231
|
+
export interface SearchResult {
|
|
232
|
+
courses?: Course[];
|
|
233
|
+
users?: User[];
|
|
234
|
+
collections?: Collection[];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* API Error
|
|
239
|
+
*/
|
|
240
|
+
export interface ApiError {
|
|
241
|
+
detail: string;
|
|
242
|
+
status?: number;
|
|
243
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"outDir": "./dist",
|
|
10
|
+
"rootDir": "./src",
|
|
11
|
+
"declaration": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"lib": ["ES2022"]
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*"],
|
|
16
|
+
"exclude": ["node_modules", "dist"]
|
|
17
|
+
}
|