@burdenoff/mcp-libs 2026.627.3 → 2026.627.5

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.
Files changed (61) hide show
  1. package/dist/modules/cosmicintersection/index.cjs +1984 -0
  2. package/dist/modules/cosmicintersection/index.cjs.map +1 -0
  3. package/dist/modules/cosmicintersection/index.d.cts +8 -0
  4. package/dist/modules/cosmicintersection/index.d.ts +8 -0
  5. package/dist/modules/cosmicintersection/index.js +1923 -0
  6. package/dist/modules/cosmicintersection/index.js.map +1 -0
  7. package/dist/modules/ecoimpacthub/index.cjs +2042 -0
  8. package/dist/modules/ecoimpacthub/index.cjs.map +1 -0
  9. package/dist/modules/ecoimpacthub/index.d.cts +8 -0
  10. package/dist/modules/ecoimpacthub/index.d.ts +8 -0
  11. package/dist/modules/ecoimpacthub/index.js +1981 -0
  12. package/dist/modules/ecoimpacthub/index.js.map +1 -0
  13. package/dist/modules/govcitizenhub/index.cjs +1686 -0
  14. package/dist/modules/govcitizenhub/index.cjs.map +1 -0
  15. package/dist/modules/govcitizenhub/index.d.cts +8 -0
  16. package/dist/modules/govcitizenhub/index.d.ts +8 -0
  17. package/dist/modules/govcitizenhub/index.js +1625 -0
  18. package/dist/modules/govcitizenhub/index.js.map +1 -0
  19. package/dist/modules/intelwatchtower/index.cjs +1919 -0
  20. package/dist/modules/intelwatchtower/index.cjs.map +1 -0
  21. package/dist/modules/intelwatchtower/index.d.cts +8 -0
  22. package/dist/modules/intelwatchtower/index.d.ts +8 -0
  23. package/dist/modules/intelwatchtower/index.js +1858 -0
  24. package/dist/modules/intelwatchtower/index.js.map +1 -0
  25. package/dist/modules/manufacturedops/index.cjs +1884 -0
  26. package/dist/modules/manufacturedops/index.cjs.map +1 -0
  27. package/dist/modules/manufacturedops/index.d.cts +8 -0
  28. package/dist/modules/manufacturedops/index.d.ts +8 -0
  29. package/dist/modules/manufacturedops/index.js +1823 -0
  30. package/dist/modules/manufacturedops/index.js.map +1 -0
  31. package/dist/modules/mybodyshield/index.cjs +1834 -0
  32. package/dist/modules/mybodyshield/index.cjs.map +1 -0
  33. package/dist/modules/mybodyshield/index.d.cts +8 -0
  34. package/dist/modules/mybodyshield/index.d.ts +8 -0
  35. package/dist/modules/mybodyshield/index.js +1773 -0
  36. package/dist/modules/mybodyshield/index.js.map +1 -0
  37. package/dist/modules/semanticfed/index.cjs +1846 -0
  38. package/dist/modules/semanticfed/index.cjs.map +1 -0
  39. package/dist/modules/semanticfed/index.d.cts +8 -0
  40. package/dist/modules/semanticfed/index.d.ts +8 -0
  41. package/dist/modules/semanticfed/index.js +1785 -0
  42. package/dist/modules/semanticfed/index.js.map +1 -0
  43. package/dist/modules/technospam/index.cjs +1962 -0
  44. package/dist/modules/technospam/index.cjs.map +1 -0
  45. package/dist/modules/technospam/index.d.cts +8 -0
  46. package/dist/modules/technospam/index.d.ts +8 -0
  47. package/dist/modules/technospam/index.js +1901 -0
  48. package/dist/modules/technospam/index.js.map +1 -0
  49. package/dist/modules/theculturepost/index.cjs +1587 -0
  50. package/dist/modules/theculturepost/index.cjs.map +1 -0
  51. package/dist/modules/theculturepost/index.d.cts +8 -0
  52. package/dist/modules/theculturepost/index.d.ts +8 -0
  53. package/dist/modules/theculturepost/index.js +1526 -0
  54. package/dist/modules/theculturepost/index.js.map +1 -0
  55. package/dist/modules/theglobalfuel/index.cjs +1678 -0
  56. package/dist/modules/theglobalfuel/index.cjs.map +1 -0
  57. package/dist/modules/theglobalfuel/index.d.cts +8 -0
  58. package/dist/modules/theglobalfuel/index.d.ts +8 -0
  59. package/dist/modules/theglobalfuel/index.js +1617 -0
  60. package/dist/modules/theglobalfuel/index.js.map +1 -0
  61. package/package.json +1 -1
@@ -0,0 +1,1901 @@
1
+ import { mcpToolName, errorResult, jsonResult, mcpResourceUri, mcpPromptName } from '../../chunk-H2OYWLOA.js';
2
+ import { z } from 'zod';
3
+
4
+ // src/modules/technospam/operations.ts
5
+ var COURSE_FIELDS = `
6
+ id slug title summary topic level status estimatedMinutes instructorId
7
+ tags publishedAt workspaceId createdAt updatedAt
8
+ cover { id }
9
+ `;
10
+ var LESSON_FIELDS = `
11
+ id courseId title kind content position estimatedMinutes isPreview
12
+ file { id } workspaceId createdAt updatedAt
13
+ `;
14
+ var ENROLLMENT_FIELDS = `
15
+ id courseId learnerId status progressPct lessonsCompleted
16
+ startedAt lastActiveAt completedAt workspaceId createdAt updatedAt
17
+ `;
18
+ var CERTIFICATE_FIELDS = `
19
+ id courseId learnerId serial score status issuedAt revokedAt revokeReason
20
+ workspaceId createdAt updatedAt
21
+ `;
22
+ var PROBLEM_FIELDS = `
23
+ id slug title statement topic difficulty status points languages tags
24
+ publishedAt workspaceId createdAt updatedAt
25
+ `;
26
+ var SUBMISSION_FIELDS = `
27
+ id problemId learnerId code language verdict pointsAwarded runtimeMs
28
+ judgedAt workspaceId createdAt updatedAt
29
+ `;
30
+ var MENTOR_FIELDS = `
31
+ id userId headline bio expertise hourlyRate currency status
32
+ isAcceptingMentees ratingAvg sessionsCount workspaceId createdAt updatedAt
33
+ `;
34
+ var SESSION_FIELDS = `
35
+ id mentorId menteeId topic agenda status scheduledAt durationMinutes
36
+ meetingUrl rating feedback cancelledAt completedAt workspaceId createdAt updatedAt
37
+ `;
38
+ var HACKATHON_FIELDS = `
39
+ id slug title theme description status startAt endAt submissionDeadline
40
+ prizePool currency maxTeamSize tags workspaceId createdAt updatedAt
41
+ `;
42
+ var TEAM_FIELDS = `
43
+ id hackathonId name captainId memberIds status score projectName projectUrl
44
+ repoUrl pitch submittedAt workspaceId createdAt updatedAt
45
+ `;
46
+ var PAGE_INFO_FIELDS = `cursor hasMore`;
47
+ var GET_COURSE = `
48
+ query TechnospamGetCourse($id: ID!) {
49
+ tecCourse(id: $id) {
50
+ ${COURSE_FIELDS}
51
+ lessons { ${LESSON_FIELDS} }
52
+ enrollments { ${ENROLLMENT_FIELDS} }
53
+ }
54
+ }
55
+ `;
56
+ var LIST_COURSES = `
57
+ query TechnospamListCourses(
58
+ $level: TecCourseLevel
59
+ $status: TecCourseStatus
60
+ $pagination: PaginationInput
61
+ ) {
62
+ tecListCourses(level: $level, status: $status, pagination: $pagination) {
63
+ items { ${COURSE_FIELDS} }
64
+ pageInfo { ${PAGE_INFO_FIELDS} }
65
+ }
66
+ }
67
+ `;
68
+ var CREATE_COURSE = `
69
+ mutation TechnospamCreateCourse($input: TecCreateCourseInput!) {
70
+ tecCreateCourse(input: $input) {
71
+ ${COURSE_FIELDS}
72
+ }
73
+ }
74
+ `;
75
+ var UPDATE_COURSE = `
76
+ mutation TechnospamUpdateCourse($id: ID!, $input: TecUpdateCourseInput!) {
77
+ tecUpdateCourse(id: $id, input: $input) {
78
+ ${COURSE_FIELDS}
79
+ }
80
+ }
81
+ `;
82
+ var PUBLISH_COURSE = `
83
+ mutation TechnospamPublishCourse($id: ID!) {
84
+ tecPublishCourse(id: $id) {
85
+ ${COURSE_FIELDS}
86
+ }
87
+ }
88
+ `;
89
+ var ARCHIVE_COURSE = `
90
+ mutation TechnospamArchiveCourse($id: ID!) {
91
+ tecArchiveCourse(id: $id)
92
+ }
93
+ `;
94
+ var LIST_LESSONS = `
95
+ query TechnospamListLessons($courseId: ID!) {
96
+ tecListLessons(courseId: $courseId) {
97
+ ${LESSON_FIELDS}
98
+ }
99
+ }
100
+ `;
101
+ var CREATE_LESSON = `
102
+ mutation TechnospamCreateLesson($input: TecCreateLessonInput!) {
103
+ tecCreateLesson(input: $input) {
104
+ ${LESSON_FIELDS}
105
+ }
106
+ }
107
+ `;
108
+ var UPDATE_LESSON = `
109
+ mutation TechnospamUpdateLesson($id: ID!, $input: TecUpdateLessonInput!) {
110
+ tecUpdateLesson(id: $id, input: $input) {
111
+ ${LESSON_FIELDS}
112
+ }
113
+ }
114
+ `;
115
+ var LIST_ENROLLMENTS = `
116
+ query TechnospamListEnrollments(
117
+ $courseId: ID
118
+ $learnerId: ID
119
+ $pagination: PaginationInput
120
+ ) {
121
+ tecListEnrollments(courseId: $courseId, learnerId: $learnerId, pagination: $pagination) {
122
+ items { ${ENROLLMENT_FIELDS} }
123
+ pageInfo { ${PAGE_INFO_FIELDS} }
124
+ }
125
+ }
126
+ `;
127
+ var ENROLL = `
128
+ mutation TechnospamEnroll($input: TecEnrollInput!) {
129
+ tecEnroll(input: $input) {
130
+ ${ENROLLMENT_FIELDS}
131
+ }
132
+ }
133
+ `;
134
+ var UPDATE_PROGRESS = `
135
+ mutation TechnospamUpdateProgress($input: TecUpdateProgressInput!) {
136
+ tecUpdateProgress(input: $input) {
137
+ ${ENROLLMENT_FIELDS}
138
+ }
139
+ }
140
+ `;
141
+ var LIST_CERTIFICATES = `
142
+ query TechnospamListCertificates($courseId: ID, $learnerId: ID) {
143
+ tecListCertificates(courseId: $courseId, learnerId: $learnerId) {
144
+ ${CERTIFICATE_FIELDS}
145
+ }
146
+ }
147
+ `;
148
+ var ISSUE_CERTIFICATE = `
149
+ mutation TechnospamIssueCertificate($courseId: ID!, $learnerId: ID!, $score: Int!) {
150
+ tecIssueCertificate(courseId: $courseId, learnerId: $learnerId, score: $score) {
151
+ ${CERTIFICATE_FIELDS}
152
+ }
153
+ }
154
+ `;
155
+ var REVOKE_CERTIFICATE = `
156
+ mutation TechnospamRevokeCertificate($id: ID!, $reason: String) {
157
+ tecRevokeCertificate(id: $id, reason: $reason) {
158
+ ${CERTIFICATE_FIELDS}
159
+ }
160
+ }
161
+ `;
162
+ var GET_PROBLEM = `
163
+ query TechnospamGetProblem($id: ID!) {
164
+ tecProblem(id: $id) {
165
+ ${PROBLEM_FIELDS}
166
+ submissions { ${SUBMISSION_FIELDS} }
167
+ }
168
+ }
169
+ `;
170
+ var LIST_PROBLEMS = `
171
+ query TechnospamListProblems(
172
+ $difficulty: TecProblemDifficulty
173
+ $status: TecProblemStatus
174
+ $topic: String
175
+ $pagination: PaginationInput
176
+ ) {
177
+ tecListProblems(
178
+ difficulty: $difficulty
179
+ status: $status
180
+ topic: $topic
181
+ pagination: $pagination
182
+ ) {
183
+ items { ${PROBLEM_FIELDS} }
184
+ pageInfo { ${PAGE_INFO_FIELDS} }
185
+ }
186
+ }
187
+ `;
188
+ var CREATE_PROBLEM = `
189
+ mutation TechnospamCreateProblem($input: TecCreateProblemInput!) {
190
+ tecCreateProblem(input: $input) {
191
+ ${PROBLEM_FIELDS}
192
+ }
193
+ }
194
+ `;
195
+ var UPDATE_PROBLEM = `
196
+ mutation TechnospamUpdateProblem($id: ID!, $input: TecUpdateProblemInput!) {
197
+ tecUpdateProblem(id: $id, input: $input) {
198
+ ${PROBLEM_FIELDS}
199
+ }
200
+ }
201
+ `;
202
+ var PUBLISH_PROBLEM = `
203
+ mutation TechnospamPublishProblem($id: ID!) {
204
+ tecPublishProblem(id: $id) {
205
+ ${PROBLEM_FIELDS}
206
+ }
207
+ }
208
+ `;
209
+ var LIST_SUBMISSIONS = `
210
+ query TechnospamListSubmissions(
211
+ $learnerId: ID
212
+ $problemId: ID
213
+ $pagination: PaginationInput
214
+ ) {
215
+ tecListSubmissions(learnerId: $learnerId, problemId: $problemId, pagination: $pagination) {
216
+ items { ${SUBMISSION_FIELDS} }
217
+ pageInfo { ${PAGE_INFO_FIELDS} }
218
+ }
219
+ }
220
+ `;
221
+ var SUBMIT_SOLUTION = `
222
+ mutation TechnospamSubmitSolution($input: TecSubmitSolutionInput!) {
223
+ tecSubmitSolution(input: $input) {
224
+ ${SUBMISSION_FIELDS}
225
+ }
226
+ }
227
+ `;
228
+ var GRADE_SUBMISSION = `
229
+ mutation TechnospamGradeSubmission(
230
+ $id: ID!
231
+ $verdict: TecSubmissionVerdict!
232
+ $runtimeMs: Int
233
+ ) {
234
+ tecGradeSubmission(id: $id, verdict: $verdict, runtimeMs: $runtimeMs) {
235
+ ${SUBMISSION_FIELDS}
236
+ }
237
+ }
238
+ `;
239
+ var GET_MENTOR = `
240
+ query TechnospamGetMentor($id: ID!) {
241
+ tecMentor(id: $id) {
242
+ ${MENTOR_FIELDS}
243
+ sessions { ${SESSION_FIELDS} }
244
+ }
245
+ }
246
+ `;
247
+ var LIST_MENTORS = `
248
+ query TechnospamListMentors($status: TecMentorStatus, $pagination: PaginationInput) {
249
+ tecListMentors(status: $status, pagination: $pagination) {
250
+ items { ${MENTOR_FIELDS} }
251
+ pageInfo { ${PAGE_INFO_FIELDS} }
252
+ }
253
+ }
254
+ `;
255
+ var BECOME_MENTOR = `
256
+ mutation TechnospamBecomeMentor($input: TecCreateMentorInput!) {
257
+ tecBecomeMentor(input: $input) {
258
+ ${MENTOR_FIELDS}
259
+ }
260
+ }
261
+ `;
262
+ var UPDATE_MENTOR = `
263
+ mutation TechnospamUpdateMentor($id: ID!, $input: TecUpdateMentorInput!) {
264
+ tecUpdateMentor(id: $id, input: $input) {
265
+ ${MENTOR_FIELDS}
266
+ }
267
+ }
268
+ `;
269
+ var APPROVE_MENTOR = `
270
+ mutation TechnospamApproveMentor($id: ID!) {
271
+ tecApproveMentor(id: $id) {
272
+ ${MENTOR_FIELDS}
273
+ }
274
+ }
275
+ `;
276
+ var LIST_SESSIONS = `
277
+ query TechnospamListSessions($mentorId: ID, $menteeId: ID, $status: TecSessionStatus) {
278
+ tecListSessions(mentorId: $mentorId, menteeId: $menteeId, status: $status) {
279
+ ${SESSION_FIELDS}
280
+ }
281
+ }
282
+ `;
283
+ var BOOK_SESSION = `
284
+ mutation TechnospamBookSession($input: TecBookSessionInput!) {
285
+ tecBookSession(input: $input) {
286
+ ${SESSION_FIELDS}
287
+ }
288
+ }
289
+ `;
290
+ var CONFIRM_SESSION = `
291
+ mutation TechnospamConfirmSession($id: ID!, $meetingUrl: String) {
292
+ tecConfirmSession(id: $id, meetingUrl: $meetingUrl) {
293
+ ${SESSION_FIELDS}
294
+ }
295
+ }
296
+ `;
297
+ var CANCEL_SESSION = `
298
+ mutation TechnospamCancelSession($id: ID!) {
299
+ tecCancelSession(id: $id) {
300
+ ${SESSION_FIELDS}
301
+ }
302
+ }
303
+ `;
304
+ var COMPLETE_SESSION = `
305
+ mutation TechnospamCompleteSession($id: ID!, $rating: Int, $feedback: String) {
306
+ tecCompleteSession(id: $id, rating: $rating, feedback: $feedback) {
307
+ ${SESSION_FIELDS}
308
+ }
309
+ }
310
+ `;
311
+ var GET_HACKATHON = `
312
+ query TechnospamGetHackathon($id: ID!) {
313
+ tecHackathon(id: $id) {
314
+ ${HACKATHON_FIELDS}
315
+ teams { ${TEAM_FIELDS} }
316
+ }
317
+ }
318
+ `;
319
+ var LIST_HACKATHONS = `
320
+ query TechnospamListHackathons($status: TecHackathonStatus, $pagination: PaginationInput) {
321
+ tecListHackathons(status: $status, pagination: $pagination) {
322
+ items { ${HACKATHON_FIELDS} }
323
+ pageInfo { ${PAGE_INFO_FIELDS} }
324
+ }
325
+ }
326
+ `;
327
+ var CREATE_HACKATHON = `
328
+ mutation TechnospamCreateHackathon($input: TecCreateHackathonInput!) {
329
+ tecCreateHackathon(input: $input) {
330
+ ${HACKATHON_FIELDS}
331
+ }
332
+ }
333
+ `;
334
+ var UPDATE_HACKATHON = `
335
+ mutation TechnospamUpdateHackathon($id: ID!, $input: TecUpdateHackathonInput!) {
336
+ tecUpdateHackathon(id: $id, input: $input) {
337
+ ${HACKATHON_FIELDS}
338
+ }
339
+ }
340
+ `;
341
+ var OPEN_REGISTRATION = `
342
+ mutation TechnospamOpenRegistration($id: ID!) {
343
+ tecOpenRegistration(id: $id) {
344
+ ${HACKATHON_FIELDS}
345
+ }
346
+ }
347
+ `;
348
+ var LIST_TEAMS = `
349
+ query TechnospamListTeams($hackathonId: ID!) {
350
+ tecListTeams(hackathonId: $hackathonId) {
351
+ ${TEAM_FIELDS}
352
+ }
353
+ }
354
+ `;
355
+ var REGISTER_TEAM = `
356
+ mutation TechnospamRegisterTeam($input: TecRegisterTeamInput!) {
357
+ tecRegisterTeam(input: $input) {
358
+ ${TEAM_FIELDS}
359
+ }
360
+ }
361
+ `;
362
+ var SUBMIT_PROJECT = `
363
+ mutation TechnospamSubmitProject($input: TecSubmitProjectInput!) {
364
+ tecSubmitProject(input: $input) {
365
+ ${TEAM_FIELDS}
366
+ }
367
+ }
368
+ `;
369
+ var SCORE_TEAM = `
370
+ mutation TechnospamScoreTeam($id: ID!, $score: Int!, $winner: Boolean) {
371
+ tecScoreTeam(id: $id, score: $score, winner: $winner) {
372
+ ${TEAM_FIELDS}
373
+ }
374
+ }
375
+ `;
376
+ var HEALTH = `
377
+ query TechnospamHealth {
378
+ tecHealth
379
+ }
380
+ `;
381
+ var CourseLevelEnum = z.enum(["ADVANCED", "BEGINNER", "EXPERT", "INTERMEDIATE"]).describe("Difficulty level of a course");
382
+ var CourseStatusEnum = z.enum(["ARCHIVED", "DRAFT", "PUBLISHED"]).describe("Lifecycle status of a course");
383
+ var LessonKindEnum = z.enum(["ARTICLE", "INTERACTIVE", "PROJECT", "QUIZ", "VIDEO"]).describe("Content type of a lesson");
384
+ z.enum(["COMPLETED", "DROPPED", "ENROLLED", "IN_PROGRESS"]).describe("Lifecycle status of a learner enrollment");
385
+ var ProblemDifficultyEnum = z.enum(["EASY", "HARD", "MEDIUM"]).describe("Difficulty of a practice problem");
386
+ var ProblemStatusEnum = z.enum(["ARCHIVED", "DRAFT", "PUBLISHED"]).describe("Lifecycle status of a practice problem");
387
+ var SubmissionVerdictEnum = z.enum([
388
+ "ACCEPTED",
389
+ "COMPILE_ERROR",
390
+ "PENDING",
391
+ "RUNTIME_ERROR",
392
+ "TIME_LIMIT_EXCEEDED",
393
+ "WRONG_ANSWER"
394
+ ]).describe("Grader verdict for a submission");
395
+ var MentorStatusEnum = z.enum(["ACTIVE", "INACTIVE", "PAUSED", "PENDING"]).describe("Lifecycle/moderation status of a mentor profile");
396
+ var SessionStatusEnum = z.enum(["CANCELLED", "COMPLETED", "CONFIRMED", "NO_SHOW", "REQUESTED"]).describe("Status of a 1:1 mentor session");
397
+ var HackathonStatusEnum = z.enum(["CANCELLED", "COMPLETED", "DRAFT", "IN_PROGRESS", "JUDGING", "REGISTRATION_OPEN"]).describe("Lifecycle status of a hackathon");
398
+ function buildPagination(limit, offset, cursor) {
399
+ const pagination = {};
400
+ if (limit !== void 0) pagination.limit = limit;
401
+ if (offset !== void 0) pagination.offset = offset;
402
+ if (cursor !== void 0) pagination.cursor = cursor;
403
+ return Object.keys(pagination).length > 0 ? pagination : void 0;
404
+ }
405
+
406
+ // src/modules/technospam/tools/courses.ts
407
+ function registerCourseTools(server, ctx, track) {
408
+ server.registerTool(
409
+ track(mcpToolName(ctx.productName, "courses_get")),
410
+ {
411
+ title: "Get Course",
412
+ description: "Fetch a single Technospam course by ID, including its lessons and enrollments.",
413
+ inputSchema: { id: z.string().describe("Course ID") }
414
+ },
415
+ async ({ id }) => {
416
+ try {
417
+ const data = await ctx.gql.workspace.request(GET_COURSE, { id });
418
+ if (!data.tecCourse) return errorResult(`Course ${id} not found`);
419
+ return jsonResult(data.tecCourse);
420
+ } catch (error) {
421
+ ctx.logger.error("technospam courses_get failed", { error: String(error) });
422
+ return errorResult(
423
+ `Failed to get course: ${error instanceof Error ? error.message : String(error)}`
424
+ );
425
+ }
426
+ }
427
+ );
428
+ server.registerTool(
429
+ track(mcpToolName(ctx.productName, "courses_list")),
430
+ {
431
+ title: "List Courses",
432
+ description: "List courses in the bound workspace with optional level/status filters and pagination. Returns { items, pageInfo }.",
433
+ inputSchema: {
434
+ level: CourseLevelEnum.optional().describe("Filter by difficulty level"),
435
+ status: CourseStatusEnum.optional().describe("Filter by lifecycle status"),
436
+ limit: z.number().int().optional().describe("Page size (default 50)"),
437
+ offset: z.number().int().optional().describe("Pagination offset (default 0)"),
438
+ cursor: z.string().optional().describe("Opaque pagination cursor")
439
+ }
440
+ },
441
+ async ({ level, status, limit, offset, cursor }) => {
442
+ try {
443
+ const data = await ctx.gql.workspace.request(LIST_COURSES, {
444
+ level,
445
+ status,
446
+ pagination: buildPagination(limit, offset, cursor)
447
+ });
448
+ return jsonResult(data.tecListCourses);
449
+ } catch (error) {
450
+ ctx.logger.error("technospam courses_list failed", { error: String(error) });
451
+ return errorResult(
452
+ `Failed to list courses: ${error instanceof Error ? error.message : String(error)}`
453
+ );
454
+ }
455
+ }
456
+ );
457
+ server.registerTool(
458
+ track(mcpToolName(ctx.productName, "courses_create")),
459
+ {
460
+ title: "Create Course",
461
+ description: "Create a course (DRAFT). `slug` must be unique within the workspace; `title` is required. Defaults level to BEGINNER.",
462
+ inputSchema: {
463
+ slug: z.string().min(1).describe("URL slug, unique within the workspace"),
464
+ title: z.string().min(1).describe("Course title"),
465
+ summary: z.string().optional().describe("Short course summary"),
466
+ topic: z.string().optional().describe("Primary topic/subject"),
467
+ level: CourseLevelEnum.optional().describe("Difficulty level (default BEGINNER)"),
468
+ estimatedMinutes: z.number().int().optional().describe("Estimated total completion time in minutes"),
469
+ instructorId: z.string().optional().describe("User ID of the instructor"),
470
+ coverFileId: z.string().optional().describe("wspace-files File ID for the cover image"),
471
+ tags: z.array(z.string()).optional().describe("Discovery tags")
472
+ }
473
+ },
474
+ async ({
475
+ slug,
476
+ title,
477
+ summary,
478
+ topic,
479
+ level,
480
+ estimatedMinutes,
481
+ instructorId,
482
+ coverFileId,
483
+ tags
484
+ }) => {
485
+ try {
486
+ const input = { slug, title };
487
+ if (summary !== void 0) input.summary = summary;
488
+ if (topic !== void 0) input.topic = topic;
489
+ if (level !== void 0) input.level = level;
490
+ if (estimatedMinutes !== void 0) input.estimatedMinutes = estimatedMinutes;
491
+ if (instructorId !== void 0) input.instructorId = instructorId;
492
+ if (coverFileId !== void 0) input.coverFileId = coverFileId;
493
+ if (tags !== void 0) input.tags = tags;
494
+ const data = await ctx.gql.workspace.request(CREATE_COURSE, { input });
495
+ return jsonResult(data.tecCreateCourse);
496
+ } catch (error) {
497
+ ctx.logger.error("technospam courses_create failed", { error: String(error) });
498
+ return errorResult(
499
+ `Failed to create course: ${error instanceof Error ? error.message : String(error)}`
500
+ );
501
+ }
502
+ }
503
+ );
504
+ server.registerTool(
505
+ track(mcpToolName(ctx.productName, "courses_update")),
506
+ {
507
+ title: "Update Course",
508
+ description: "Update a course. At least one mutable field must be provided. Use courses_publish/courses_archive for lifecycle transitions.",
509
+ inputSchema: {
510
+ id: z.string().describe("Course ID"),
511
+ title: z.string().optional().describe("Updated title"),
512
+ summary: z.string().optional().describe("Updated summary"),
513
+ topic: z.string().optional().describe("Updated topic"),
514
+ level: CourseLevelEnum.optional().describe("Updated difficulty level"),
515
+ estimatedMinutes: z.number().int().optional().describe("Updated estimated minutes"),
516
+ instructorId: z.string().optional().describe("Updated instructor user ID"),
517
+ coverFileId: z.string().optional().describe("Updated cover File ID"),
518
+ tags: z.array(z.string()).optional().describe("Updated discovery tags")
519
+ }
520
+ },
521
+ async ({
522
+ id,
523
+ title,
524
+ summary,
525
+ topic,
526
+ level,
527
+ estimatedMinutes,
528
+ instructorId,
529
+ coverFileId,
530
+ tags
531
+ }) => {
532
+ try {
533
+ const input = {};
534
+ if (title !== void 0) input.title = title;
535
+ if (summary !== void 0) input.summary = summary;
536
+ if (topic !== void 0) input.topic = topic;
537
+ if (level !== void 0) input.level = level;
538
+ if (estimatedMinutes !== void 0) input.estimatedMinutes = estimatedMinutes;
539
+ if (instructorId !== void 0) input.instructorId = instructorId;
540
+ if (coverFileId !== void 0) input.coverFileId = coverFileId;
541
+ if (tags !== void 0) input.tags = tags;
542
+ if (Object.keys(input).length === 0) {
543
+ return errorResult("No updates specified \u2014 provide at least one mutable course field");
544
+ }
545
+ const data = await ctx.gql.workspace.request(UPDATE_COURSE, { id, input });
546
+ return jsonResult(data.tecUpdateCourse);
547
+ } catch (error) {
548
+ ctx.logger.error("technospam courses_update failed", { error: String(error) });
549
+ return errorResult(
550
+ `Failed to update course: ${error instanceof Error ? error.message : String(error)}`
551
+ );
552
+ }
553
+ }
554
+ );
555
+ server.registerTool(
556
+ track(mcpToolName(ctx.productName, "courses_publish")),
557
+ {
558
+ title: "Publish Course",
559
+ description: "Publish a course (DRAFT -> PUBLISHED), making it visible to learners.",
560
+ inputSchema: { id: z.string().describe("Course ID") }
561
+ },
562
+ async ({ id }) => {
563
+ try {
564
+ const data = await ctx.gql.workspace.request(PUBLISH_COURSE, { id });
565
+ return jsonResult(data.tecPublishCourse);
566
+ } catch (error) {
567
+ ctx.logger.error("technospam courses_publish failed", { error: String(error) });
568
+ return errorResult(
569
+ `Failed to publish course: ${error instanceof Error ? error.message : String(error)}`
570
+ );
571
+ }
572
+ }
573
+ );
574
+ server.registerTool(
575
+ track(mcpToolName(ctx.productName, "courses_archive")),
576
+ {
577
+ title: "Archive Course",
578
+ description: "Archive a course (-> ARCHIVED). Returns true on success.",
579
+ inputSchema: { id: z.string().describe("Course ID") }
580
+ },
581
+ async ({ id }) => {
582
+ try {
583
+ const data = await ctx.gql.workspace.request(
584
+ ARCHIVE_COURSE,
585
+ { id }
586
+ );
587
+ return jsonResult({ archived: data.tecArchiveCourse });
588
+ } catch (error) {
589
+ ctx.logger.error("technospam courses_archive failed", { error: String(error) });
590
+ return errorResult(
591
+ `Failed to archive course: ${error instanceof Error ? error.message : String(error)}`
592
+ );
593
+ }
594
+ }
595
+ );
596
+ }
597
+ function registerLessonTools(server, ctx, track) {
598
+ server.registerTool(
599
+ track(mcpToolName(ctx.productName, "lessons_list")),
600
+ {
601
+ title: "List Lessons",
602
+ description: "List all lessons for a course, ordered by position.",
603
+ inputSchema: { courseId: z.string().describe("Course ID") }
604
+ },
605
+ async ({ courseId }) => {
606
+ try {
607
+ const data = await ctx.gql.workspace.request(LIST_LESSONS, { courseId });
608
+ return jsonResult(data.tecListLessons);
609
+ } catch (error) {
610
+ ctx.logger.error("technospam lessons_list failed", { error: String(error) });
611
+ return errorResult(
612
+ `Failed to list lessons: ${error instanceof Error ? error.message : String(error)}`
613
+ );
614
+ }
615
+ }
616
+ );
617
+ server.registerTool(
618
+ track(mcpToolName(ctx.productName, "lessons_create")),
619
+ {
620
+ title: "Create Lesson",
621
+ description: "Add a lesson to a course. Defaults kind to ARTICLE and isPreview to false. Use `fileId` to attach a video/file from wspace-files.",
622
+ inputSchema: {
623
+ courseId: z.string().min(1).describe("Owning course ID"),
624
+ title: z.string().min(1).describe("Lesson title"),
625
+ kind: LessonKindEnum.optional().describe("Lesson content type (default ARTICLE)"),
626
+ content: z.string().optional().describe("Lesson body / article content"),
627
+ position: z.number().int().optional().describe("Ordering position within the course"),
628
+ estimatedMinutes: z.number().int().optional().describe("Estimated completion time"),
629
+ isPreview: z.boolean().optional().describe("Free preview lesson (default false)"),
630
+ fileId: z.string().optional().describe("wspace-files File ID (video/attachment)")
631
+ }
632
+ },
633
+ async ({ courseId, title, kind, content, position, estimatedMinutes, isPreview, fileId }) => {
634
+ try {
635
+ const input = { courseId, title };
636
+ if (kind !== void 0) input.kind = kind;
637
+ if (content !== void 0) input.content = content;
638
+ if (position !== void 0) input.position = position;
639
+ if (estimatedMinutes !== void 0) input.estimatedMinutes = estimatedMinutes;
640
+ if (isPreview !== void 0) input.isPreview = isPreview;
641
+ if (fileId !== void 0) input.fileId = fileId;
642
+ const data = await ctx.gql.workspace.request(CREATE_LESSON, { input });
643
+ return jsonResult(data.tecCreateLesson);
644
+ } catch (error) {
645
+ ctx.logger.error("technospam lessons_create failed", { error: String(error) });
646
+ return errorResult(
647
+ `Failed to create lesson: ${error instanceof Error ? error.message : String(error)}`
648
+ );
649
+ }
650
+ }
651
+ );
652
+ server.registerTool(
653
+ track(mcpToolName(ctx.productName, "lessons_update")),
654
+ {
655
+ title: "Update Lesson",
656
+ description: "Update a lesson. At least one mutable field must be provided.",
657
+ inputSchema: {
658
+ id: z.string().describe("Lesson ID"),
659
+ title: z.string().optional().describe("Updated title"),
660
+ kind: LessonKindEnum.optional().describe("Updated content type"),
661
+ content: z.string().optional().describe("Updated body content"),
662
+ position: z.number().int().optional().describe("Updated ordering position"),
663
+ estimatedMinutes: z.number().int().optional().describe("Updated estimated minutes"),
664
+ isPreview: z.boolean().optional().describe("Toggle free-preview flag"),
665
+ fileId: z.string().optional().describe("Updated attached File ID")
666
+ }
667
+ },
668
+ async ({ id, title, kind, content, position, estimatedMinutes, isPreview, fileId }) => {
669
+ try {
670
+ const input = {};
671
+ if (title !== void 0) input.title = title;
672
+ if (kind !== void 0) input.kind = kind;
673
+ if (content !== void 0) input.content = content;
674
+ if (position !== void 0) input.position = position;
675
+ if (estimatedMinutes !== void 0) input.estimatedMinutes = estimatedMinutes;
676
+ if (isPreview !== void 0) input.isPreview = isPreview;
677
+ if (fileId !== void 0) input.fileId = fileId;
678
+ if (Object.keys(input).length === 0) {
679
+ return errorResult("No updates specified \u2014 provide at least one mutable lesson field");
680
+ }
681
+ const data = await ctx.gql.workspace.request(UPDATE_LESSON, { id, input });
682
+ return jsonResult(data.tecUpdateLesson);
683
+ } catch (error) {
684
+ ctx.logger.error("technospam lessons_update failed", { error: String(error) });
685
+ return errorResult(
686
+ `Failed to update lesson: ${error instanceof Error ? error.message : String(error)}`
687
+ );
688
+ }
689
+ }
690
+ );
691
+ }
692
+ function registerEnrollmentTools(server, ctx, track) {
693
+ server.registerTool(
694
+ track(mcpToolName(ctx.productName, "enrollments_list")),
695
+ {
696
+ title: "List Enrollments",
697
+ description: "List enrollments, optionally filtered by course and/or learner, with pagination. Returns { items, pageInfo }.",
698
+ inputSchema: {
699
+ courseId: z.string().optional().describe("Filter by course ID"),
700
+ learnerId: z.string().optional().describe("Filter by learner (user) ID"),
701
+ limit: z.number().int().optional().describe("Page size (default 50)"),
702
+ offset: z.number().int().optional().describe("Pagination offset (default 0)"),
703
+ cursor: z.string().optional().describe("Opaque pagination cursor")
704
+ }
705
+ },
706
+ async ({ courseId, learnerId, limit, offset, cursor }) => {
707
+ try {
708
+ const data = await ctx.gql.workspace.request(LIST_ENROLLMENTS, {
709
+ courseId,
710
+ learnerId,
711
+ pagination: buildPagination(limit, offset, cursor)
712
+ });
713
+ return jsonResult(data.tecListEnrollments);
714
+ } catch (error) {
715
+ ctx.logger.error("technospam enrollments_list failed", { error: String(error) });
716
+ return errorResult(
717
+ `Failed to list enrollments: ${error instanceof Error ? error.message : String(error)}`
718
+ );
719
+ }
720
+ }
721
+ );
722
+ server.registerTool(
723
+ track(mcpToolName(ctx.productName, "enrollments_enroll")),
724
+ {
725
+ title: "Enroll In Course",
726
+ description: "Enroll the calling user in a course. The learner identity comes from the gateway-issued actor \u2014 only courseId is supplied.",
727
+ inputSchema: { courseId: z.string().min(1).describe("Course ID to enroll in") }
728
+ },
729
+ async ({ courseId }) => {
730
+ try {
731
+ const data = await ctx.gql.workspace.request(ENROLL, { input: { courseId } });
732
+ return jsonResult(data.tecEnroll);
733
+ } catch (error) {
734
+ ctx.logger.error("technospam enrollments_enroll failed", { error: String(error) });
735
+ return errorResult(
736
+ `Failed to enroll: ${error instanceof Error ? error.message : String(error)}`
737
+ );
738
+ }
739
+ }
740
+ );
741
+ server.registerTool(
742
+ track(mcpToolName(ctx.productName, "enrollments_update_progress")),
743
+ {
744
+ title: "Update Enrollment Progress",
745
+ description: "Update a learner's enrollment progress. Auto-completes the enrollment at progressPct 100.",
746
+ inputSchema: {
747
+ enrollmentId: z.string().min(1).describe("Enrollment ID"),
748
+ progressPct: z.number().int().min(0).max(100).describe("Progress percentage (0-100)"),
749
+ lessonsCompleted: z.number().int().optional().describe("Count of lessons completed")
750
+ }
751
+ },
752
+ async ({ enrollmentId, progressPct, lessonsCompleted }) => {
753
+ try {
754
+ const input = { enrollmentId, progressPct };
755
+ if (lessonsCompleted !== void 0) input.lessonsCompleted = lessonsCompleted;
756
+ const data = await ctx.gql.workspace.request(UPDATE_PROGRESS, { input });
757
+ return jsonResult(data.tecUpdateProgress);
758
+ } catch (error) {
759
+ ctx.logger.error("technospam enrollments_update_progress failed", { error: String(error) });
760
+ return errorResult(
761
+ `Failed to update progress: ${error instanceof Error ? error.message : String(error)}`
762
+ );
763
+ }
764
+ }
765
+ );
766
+ }
767
+ function registerCertificateTools(server, ctx, track) {
768
+ server.registerTool(
769
+ track(mcpToolName(ctx.productName, "certificates_list")),
770
+ {
771
+ title: "List Certificates",
772
+ description: "List issued certificates, optionally filtered by course and/or learner.",
773
+ inputSchema: {
774
+ courseId: z.string().optional().describe("Filter by course ID"),
775
+ learnerId: z.string().optional().describe("Filter by learner (user) ID")
776
+ }
777
+ },
778
+ async ({ courseId, learnerId }) => {
779
+ try {
780
+ const data = await ctx.gql.workspace.request(LIST_CERTIFICATES, { courseId, learnerId });
781
+ return jsonResult(data.tecListCertificates);
782
+ } catch (error) {
783
+ ctx.logger.error("technospam certificates_list failed", { error: String(error) });
784
+ return errorResult(
785
+ `Failed to list certificates: ${error instanceof Error ? error.message : String(error)}`
786
+ );
787
+ }
788
+ }
789
+ );
790
+ server.registerTool(
791
+ track(mcpToolName(ctx.productName, "certificates_issue")),
792
+ {
793
+ title: "Issue Certificate",
794
+ description: "Issue a verifiable completion certificate for a learner on a course, with a score.",
795
+ inputSchema: {
796
+ courseId: z.string().min(1).describe("Course ID"),
797
+ learnerId: z.string().min(1).describe("Learner (user) ID"),
798
+ score: z.number().int().min(0).max(100).describe("Final score (0-100)")
799
+ }
800
+ },
801
+ async ({ courseId, learnerId, score }) => {
802
+ try {
803
+ const data = await ctx.gql.workspace.request(ISSUE_CERTIFICATE, { courseId, learnerId, score });
804
+ return jsonResult(data.tecIssueCertificate);
805
+ } catch (error) {
806
+ ctx.logger.error("technospam certificates_issue failed", { error: String(error) });
807
+ return errorResult(
808
+ `Failed to issue certificate: ${error instanceof Error ? error.message : String(error)}`
809
+ );
810
+ }
811
+ }
812
+ );
813
+ server.registerTool(
814
+ track(mcpToolName(ctx.productName, "certificates_revoke")),
815
+ {
816
+ title: "Revoke Certificate",
817
+ description: "Revoke a previously-issued certificate, with an optional reason.",
818
+ inputSchema: {
819
+ id: z.string().describe("Certificate ID"),
820
+ reason: z.string().optional().describe("Reason for revocation")
821
+ }
822
+ },
823
+ async ({ id, reason }) => {
824
+ try {
825
+ const data = await ctx.gql.workspace.request(REVOKE_CERTIFICATE, { id, reason });
826
+ return jsonResult(data.tecRevokeCertificate);
827
+ } catch (error) {
828
+ ctx.logger.error("technospam certificates_revoke failed", { error: String(error) });
829
+ return errorResult(
830
+ `Failed to revoke certificate: ${error instanceof Error ? error.message : String(error)}`
831
+ );
832
+ }
833
+ }
834
+ );
835
+ }
836
+ function registerProblemTools(server, ctx, track) {
837
+ server.registerTool(
838
+ track(mcpToolName(ctx.productName, "problems_get")),
839
+ {
840
+ title: "Get Problem",
841
+ description: "Fetch a single practice problem by ID, including its submissions.",
842
+ inputSchema: { id: z.string().describe("Problem ID") }
843
+ },
844
+ async ({ id }) => {
845
+ try {
846
+ const data = await ctx.gql.workspace.request(GET_PROBLEM, { id });
847
+ if (!data.tecProblem) return errorResult(`Problem ${id} not found`);
848
+ return jsonResult(data.tecProblem);
849
+ } catch (error) {
850
+ ctx.logger.error("technospam problems_get failed", { error: String(error) });
851
+ return errorResult(
852
+ `Failed to get problem: ${error instanceof Error ? error.message : String(error)}`
853
+ );
854
+ }
855
+ }
856
+ );
857
+ server.registerTool(
858
+ track(mcpToolName(ctx.productName, "problems_list")),
859
+ {
860
+ title: "List Problems",
861
+ description: "List practice problems with optional difficulty/status/topic filters and pagination. Returns { items, pageInfo }.",
862
+ inputSchema: {
863
+ difficulty: ProblemDifficultyEnum.optional().describe("Filter by difficulty"),
864
+ status: ProblemStatusEnum.optional().describe("Filter by lifecycle status"),
865
+ topic: z.string().optional().describe("Filter by topic"),
866
+ limit: z.number().int().optional().describe("Page size (default 50)"),
867
+ offset: z.number().int().optional().describe("Pagination offset (default 0)"),
868
+ cursor: z.string().optional().describe("Opaque pagination cursor")
869
+ }
870
+ },
871
+ async ({ difficulty, status, topic, limit, offset, cursor }) => {
872
+ try {
873
+ const data = await ctx.gql.workspace.request(LIST_PROBLEMS, {
874
+ difficulty,
875
+ status,
876
+ topic,
877
+ pagination: buildPagination(limit, offset, cursor)
878
+ });
879
+ return jsonResult(data.tecListProblems);
880
+ } catch (error) {
881
+ ctx.logger.error("technospam problems_list failed", { error: String(error) });
882
+ return errorResult(
883
+ `Failed to list problems: ${error instanceof Error ? error.message : String(error)}`
884
+ );
885
+ }
886
+ }
887
+ );
888
+ server.registerTool(
889
+ track(mcpToolName(ctx.productName, "problems_create")),
890
+ {
891
+ title: "Create Problem",
892
+ description: "Create a practice problem (DRAFT). `slug` is unique within the workspace; `title` is required. Defaults difficulty to EASY.",
893
+ inputSchema: {
894
+ slug: z.string().min(1).describe("URL slug, unique within the workspace"),
895
+ title: z.string().min(1).describe("Problem title"),
896
+ statement: z.string().optional().describe("Problem statement / description"),
897
+ topic: z.string().optional().describe("Topic/tag area"),
898
+ difficulty: ProblemDifficultyEnum.optional().describe("Difficulty (default EASY)"),
899
+ points: z.number().int().optional().describe("Points awarded on acceptance"),
900
+ languages: z.array(z.string()).optional().describe("Allowed languages"),
901
+ starterCode: z.string().optional().describe("Starter code template"),
902
+ tags: z.array(z.string()).optional().describe("Discovery tags")
903
+ }
904
+ },
905
+ async ({ slug, title, statement, topic, difficulty, points, languages, starterCode, tags }) => {
906
+ try {
907
+ const input = { slug, title };
908
+ if (statement !== void 0) input.statement = statement;
909
+ if (topic !== void 0) input.topic = topic;
910
+ if (difficulty !== void 0) input.difficulty = difficulty;
911
+ if (points !== void 0) input.points = points;
912
+ if (languages !== void 0) input.languages = languages;
913
+ if (starterCode !== void 0) input.starterCode = starterCode;
914
+ if (tags !== void 0) input.tags = tags;
915
+ const data = await ctx.gql.workspace.request(CREATE_PROBLEM, { input });
916
+ return jsonResult(data.tecCreateProblem);
917
+ } catch (error) {
918
+ ctx.logger.error("technospam problems_create failed", { error: String(error) });
919
+ return errorResult(
920
+ `Failed to create problem: ${error instanceof Error ? error.message : String(error)}`
921
+ );
922
+ }
923
+ }
924
+ );
925
+ server.registerTool(
926
+ track(mcpToolName(ctx.productName, "problems_update")),
927
+ {
928
+ title: "Update Problem",
929
+ description: "Update a practice problem. At least one mutable field must be provided.",
930
+ inputSchema: {
931
+ id: z.string().describe("Problem ID"),
932
+ title: z.string().optional().describe("Updated title"),
933
+ statement: z.string().optional().describe("Updated statement"),
934
+ topic: z.string().optional().describe("Updated topic"),
935
+ difficulty: ProblemDifficultyEnum.optional().describe("Updated difficulty"),
936
+ points: z.number().int().optional().describe("Updated points"),
937
+ languages: z.array(z.string()).optional().describe("Updated allowed languages"),
938
+ starterCode: z.string().optional().describe("Updated starter code"),
939
+ tags: z.array(z.string()).optional().describe("Updated tags")
940
+ }
941
+ },
942
+ async ({ id, title, statement, topic, difficulty, points, languages, starterCode, tags }) => {
943
+ try {
944
+ const input = {};
945
+ if (title !== void 0) input.title = title;
946
+ if (statement !== void 0) input.statement = statement;
947
+ if (topic !== void 0) input.topic = topic;
948
+ if (difficulty !== void 0) input.difficulty = difficulty;
949
+ if (points !== void 0) input.points = points;
950
+ if (languages !== void 0) input.languages = languages;
951
+ if (starterCode !== void 0) input.starterCode = starterCode;
952
+ if (tags !== void 0) input.tags = tags;
953
+ if (Object.keys(input).length === 0) {
954
+ return errorResult("No updates specified \u2014 provide at least one mutable problem field");
955
+ }
956
+ const data = await ctx.gql.workspace.request(UPDATE_PROBLEM, { id, input });
957
+ return jsonResult(data.tecUpdateProblem);
958
+ } catch (error) {
959
+ ctx.logger.error("technospam problems_update failed", { error: String(error) });
960
+ return errorResult(
961
+ `Failed to update problem: ${error instanceof Error ? error.message : String(error)}`
962
+ );
963
+ }
964
+ }
965
+ );
966
+ server.registerTool(
967
+ track(mcpToolName(ctx.productName, "problems_publish")),
968
+ {
969
+ title: "Publish Problem",
970
+ description: "Publish a practice problem (DRAFT -> PUBLISHED).",
971
+ inputSchema: { id: z.string().describe("Problem ID") }
972
+ },
973
+ async ({ id }) => {
974
+ try {
975
+ const data = await ctx.gql.workspace.request(PUBLISH_PROBLEM, { id });
976
+ return jsonResult(data.tecPublishProblem);
977
+ } catch (error) {
978
+ ctx.logger.error("technospam problems_publish failed", { error: String(error) });
979
+ return errorResult(
980
+ `Failed to publish problem: ${error instanceof Error ? error.message : String(error)}`
981
+ );
982
+ }
983
+ }
984
+ );
985
+ }
986
+ function registerSubmissionTools(server, ctx, track) {
987
+ server.registerTool(
988
+ track(mcpToolName(ctx.productName, "submissions_list")),
989
+ {
990
+ title: "List Submissions",
991
+ description: "List problem submissions, optionally filtered by learner and/or problem, with pagination. Returns { items, pageInfo }.",
992
+ inputSchema: {
993
+ learnerId: z.string().optional().describe("Filter by learner (user) ID"),
994
+ problemId: z.string().optional().describe("Filter by problem ID"),
995
+ limit: z.number().int().optional().describe("Page size (default 50)"),
996
+ offset: z.number().int().optional().describe("Pagination offset (default 0)"),
997
+ cursor: z.string().optional().describe("Opaque pagination cursor")
998
+ }
999
+ },
1000
+ async ({ learnerId, problemId, limit, offset, cursor }) => {
1001
+ try {
1002
+ const data = await ctx.gql.workspace.request(LIST_SUBMISSIONS, {
1003
+ learnerId,
1004
+ problemId,
1005
+ pagination: buildPagination(limit, offset, cursor)
1006
+ });
1007
+ return jsonResult(data.tecListSubmissions);
1008
+ } catch (error) {
1009
+ ctx.logger.error("technospam submissions_list failed", { error: String(error) });
1010
+ return errorResult(
1011
+ `Failed to list submissions: ${error instanceof Error ? error.message : String(error)}`
1012
+ );
1013
+ }
1014
+ }
1015
+ );
1016
+ server.registerTool(
1017
+ track(mcpToolName(ctx.productName, "submissions_submit")),
1018
+ {
1019
+ title: "Submit Solution",
1020
+ description: "Submit a solution to a problem. Created PENDING then graded into a verdict. The learner identity comes from the gateway-issued actor.",
1021
+ inputSchema: {
1022
+ problemId: z.string().min(1).describe("Problem ID"),
1023
+ language: z.string().min(1).describe("Solution language"),
1024
+ code: z.string().min(1).describe("Solution source code")
1025
+ }
1026
+ },
1027
+ async ({ problemId, language, code }) => {
1028
+ try {
1029
+ const data = await ctx.gql.workspace.request(SUBMIT_SOLUTION, { input: { problemId, language, code } });
1030
+ return jsonResult(data.tecSubmitSolution);
1031
+ } catch (error) {
1032
+ ctx.logger.error("technospam submissions_submit failed", { error: String(error) });
1033
+ return errorResult(
1034
+ `Failed to submit solution: ${error instanceof Error ? error.message : String(error)}`
1035
+ );
1036
+ }
1037
+ }
1038
+ );
1039
+ server.registerTool(
1040
+ track(mcpToolName(ctx.productName, "submissions_grade")),
1041
+ {
1042
+ title: "Grade Submission",
1043
+ description: "Record the grader verdict for a submission (awards points if ACCEPTED). Optionally record runtimeMs.",
1044
+ inputSchema: {
1045
+ id: z.string().describe("Submission ID"),
1046
+ verdict: SubmissionVerdictEnum.describe("Grader verdict"),
1047
+ runtimeMs: z.number().int().optional().describe("Execution runtime in milliseconds")
1048
+ }
1049
+ },
1050
+ async ({ id, verdict, runtimeMs }) => {
1051
+ try {
1052
+ const data = await ctx.gql.workspace.request(GRADE_SUBMISSION, { id, verdict, runtimeMs });
1053
+ return jsonResult(data.tecGradeSubmission);
1054
+ } catch (error) {
1055
+ ctx.logger.error("technospam submissions_grade failed", { error: String(error) });
1056
+ return errorResult(
1057
+ `Failed to grade submission: ${error instanceof Error ? error.message : String(error)}`
1058
+ );
1059
+ }
1060
+ }
1061
+ );
1062
+ }
1063
+ function registerMentorTools(server, ctx, track) {
1064
+ server.registerTool(
1065
+ track(mcpToolName(ctx.productName, "mentors_get")),
1066
+ {
1067
+ title: "Get Mentor",
1068
+ description: "Fetch a single mentor profile by ID, including its sessions.",
1069
+ inputSchema: { id: z.string().describe("Mentor ID") }
1070
+ },
1071
+ async ({ id }) => {
1072
+ try {
1073
+ const data = await ctx.gql.workspace.request(GET_MENTOR, { id });
1074
+ if (!data.tecMentor) return errorResult(`Mentor ${id} not found`);
1075
+ return jsonResult(data.tecMentor);
1076
+ } catch (error) {
1077
+ ctx.logger.error("technospam mentors_get failed", { error: String(error) });
1078
+ return errorResult(
1079
+ `Failed to get mentor: ${error instanceof Error ? error.message : String(error)}`
1080
+ );
1081
+ }
1082
+ }
1083
+ );
1084
+ server.registerTool(
1085
+ track(mcpToolName(ctx.productName, "mentors_list")),
1086
+ {
1087
+ title: "List Mentors",
1088
+ description: "List mentor profiles with optional status filter and pagination. Returns { items, pageInfo }.",
1089
+ inputSchema: {
1090
+ status: MentorStatusEnum.optional().describe("Filter by lifecycle status"),
1091
+ limit: z.number().int().optional().describe("Page size (default 50)"),
1092
+ offset: z.number().int().optional().describe("Pagination offset (default 0)"),
1093
+ cursor: z.string().optional().describe("Opaque pagination cursor")
1094
+ }
1095
+ },
1096
+ async ({ status, limit, offset, cursor }) => {
1097
+ try {
1098
+ const data = await ctx.gql.workspace.request(LIST_MENTORS, {
1099
+ status,
1100
+ pagination: buildPagination(limit, offset, cursor)
1101
+ });
1102
+ return jsonResult(data.tecListMentors);
1103
+ } catch (error) {
1104
+ ctx.logger.error("technospam mentors_list failed", { error: String(error) });
1105
+ return errorResult(
1106
+ `Failed to list mentors: ${error instanceof Error ? error.message : String(error)}`
1107
+ );
1108
+ }
1109
+ }
1110
+ );
1111
+ server.registerTool(
1112
+ track(mcpToolName(ctx.productName, "mentors_become")),
1113
+ {
1114
+ title: "Become Mentor",
1115
+ description: "Create the calling user's mentor profile (one per user per workspace). Starts PENDING until approved.",
1116
+ inputSchema: {
1117
+ headline: z.string().min(1).describe("Short professional headline"),
1118
+ bio: z.string().optional().describe("Longer mentor bio"),
1119
+ expertise: z.array(z.string()).optional().describe("Areas of expertise"),
1120
+ hourlyRate: z.number().int().optional().describe("Hourly rate (minor units / whole)"),
1121
+ currency: z.string().optional().describe("Currency code for the hourly rate")
1122
+ }
1123
+ },
1124
+ async ({ headline, bio, expertise, hourlyRate, currency }) => {
1125
+ try {
1126
+ const input = { headline };
1127
+ if (bio !== void 0) input.bio = bio;
1128
+ if (expertise !== void 0) input.expertise = expertise;
1129
+ if (hourlyRate !== void 0) input.hourlyRate = hourlyRate;
1130
+ if (currency !== void 0) input.currency = currency;
1131
+ const data = await ctx.gql.workspace.request(BECOME_MENTOR, { input });
1132
+ return jsonResult(data.tecBecomeMentor);
1133
+ } catch (error) {
1134
+ ctx.logger.error("technospam mentors_become failed", { error: String(error) });
1135
+ return errorResult(
1136
+ `Failed to become mentor: ${error instanceof Error ? error.message : String(error)}`
1137
+ );
1138
+ }
1139
+ }
1140
+ );
1141
+ server.registerTool(
1142
+ track(mcpToolName(ctx.productName, "mentors_update")),
1143
+ {
1144
+ title: "Update Mentor",
1145
+ description: "Update a mentor profile. At least one mutable field must be provided. Use `isAcceptingMentees` to toggle availability.",
1146
+ inputSchema: {
1147
+ id: z.string().describe("Mentor ID"),
1148
+ headline: z.string().optional().describe("Updated headline"),
1149
+ bio: z.string().optional().describe("Updated bio"),
1150
+ expertise: z.array(z.string()).optional().describe("Updated expertise areas"),
1151
+ hourlyRate: z.number().int().optional().describe("Updated hourly rate"),
1152
+ currency: z.string().optional().describe("Updated currency code"),
1153
+ isAcceptingMentees: z.boolean().optional().describe("Toggle accepting new mentees")
1154
+ }
1155
+ },
1156
+ async ({ id, headline, bio, expertise, hourlyRate, currency, isAcceptingMentees }) => {
1157
+ try {
1158
+ const input = {};
1159
+ if (headline !== void 0) input.headline = headline;
1160
+ if (bio !== void 0) input.bio = bio;
1161
+ if (expertise !== void 0) input.expertise = expertise;
1162
+ if (hourlyRate !== void 0) input.hourlyRate = hourlyRate;
1163
+ if (currency !== void 0) input.currency = currency;
1164
+ if (isAcceptingMentees !== void 0) input.isAcceptingMentees = isAcceptingMentees;
1165
+ if (Object.keys(input).length === 0) {
1166
+ return errorResult("No updates specified \u2014 provide at least one mutable mentor field");
1167
+ }
1168
+ const data = await ctx.gql.workspace.request(UPDATE_MENTOR, { id, input });
1169
+ return jsonResult(data.tecUpdateMentor);
1170
+ } catch (error) {
1171
+ ctx.logger.error("technospam mentors_update failed", { error: String(error) });
1172
+ return errorResult(
1173
+ `Failed to update mentor: ${error instanceof Error ? error.message : String(error)}`
1174
+ );
1175
+ }
1176
+ }
1177
+ );
1178
+ server.registerTool(
1179
+ track(mcpToolName(ctx.productName, "mentors_approve")),
1180
+ {
1181
+ title: "Approve Mentor",
1182
+ description: "Approve a pending mentor profile (PENDING -> ACTIVE).",
1183
+ inputSchema: { id: z.string().describe("Mentor ID") }
1184
+ },
1185
+ async ({ id }) => {
1186
+ try {
1187
+ const data = await ctx.gql.workspace.request(APPROVE_MENTOR, { id });
1188
+ return jsonResult(data.tecApproveMentor);
1189
+ } catch (error) {
1190
+ ctx.logger.error("technospam mentors_approve failed", { error: String(error) });
1191
+ return errorResult(
1192
+ `Failed to approve mentor: ${error instanceof Error ? error.message : String(error)}`
1193
+ );
1194
+ }
1195
+ }
1196
+ );
1197
+ }
1198
+ function registerSessionTools(server, ctx, track) {
1199
+ server.registerTool(
1200
+ track(mcpToolName(ctx.productName, "sessions_list")),
1201
+ {
1202
+ title: "List Sessions",
1203
+ description: "List mentor sessions, optionally filtered by mentor, mentee, and/or status.",
1204
+ inputSchema: {
1205
+ mentorId: z.string().optional().describe("Filter by mentor ID"),
1206
+ menteeId: z.string().optional().describe("Filter by mentee (user) ID"),
1207
+ status: SessionStatusEnum.optional().describe("Filter by session status")
1208
+ }
1209
+ },
1210
+ async ({ mentorId, menteeId, status }) => {
1211
+ try {
1212
+ const data = await ctx.gql.workspace.request(LIST_SESSIONS, { mentorId, menteeId, status });
1213
+ return jsonResult(data.tecListSessions);
1214
+ } catch (error) {
1215
+ ctx.logger.error("technospam sessions_list failed", { error: String(error) });
1216
+ return errorResult(
1217
+ `Failed to list sessions: ${error instanceof Error ? error.message : String(error)}`
1218
+ );
1219
+ }
1220
+ }
1221
+ );
1222
+ server.registerTool(
1223
+ track(mcpToolName(ctx.productName, "sessions_book")),
1224
+ {
1225
+ title: "Book Session",
1226
+ description: "Book a 1:1 session with a mentor as the calling mentee. `scheduledAt` is an ISO-8601 DateTime.",
1227
+ inputSchema: {
1228
+ mentorId: z.string().min(1).describe("Mentor ID"),
1229
+ topic: z.string().min(1).describe("Session topic"),
1230
+ scheduledAt: z.string().min(1).describe("ISO-8601 DateTime of the session start"),
1231
+ agenda: z.string().optional().describe("Session agenda / goals"),
1232
+ durationMinutes: z.number().int().optional().describe("Session duration in minutes")
1233
+ }
1234
+ },
1235
+ async ({ mentorId, topic, scheduledAt, agenda, durationMinutes }) => {
1236
+ try {
1237
+ const input = { mentorId, topic, scheduledAt };
1238
+ if (agenda !== void 0) input.agenda = agenda;
1239
+ if (durationMinutes !== void 0) input.durationMinutes = durationMinutes;
1240
+ const data = await ctx.gql.workspace.request(BOOK_SESSION, { input });
1241
+ return jsonResult(data.tecBookSession);
1242
+ } catch (error) {
1243
+ ctx.logger.error("technospam sessions_book failed", { error: String(error) });
1244
+ return errorResult(
1245
+ `Failed to book session: ${error instanceof Error ? error.message : String(error)}`
1246
+ );
1247
+ }
1248
+ }
1249
+ );
1250
+ server.registerTool(
1251
+ track(mcpToolName(ctx.productName, "sessions_confirm")),
1252
+ {
1253
+ title: "Confirm Session",
1254
+ description: "Confirm a requested session, optionally attaching a meeting URL.",
1255
+ inputSchema: {
1256
+ id: z.string().describe("Session ID"),
1257
+ meetingUrl: z.string().optional().describe("Meeting/video-call URL")
1258
+ }
1259
+ },
1260
+ async ({ id, meetingUrl }) => {
1261
+ try {
1262
+ const data = await ctx.gql.workspace.request(CONFIRM_SESSION, { id, meetingUrl });
1263
+ return jsonResult(data.tecConfirmSession);
1264
+ } catch (error) {
1265
+ ctx.logger.error("technospam sessions_confirm failed", { error: String(error) });
1266
+ return errorResult(
1267
+ `Failed to confirm session: ${error instanceof Error ? error.message : String(error)}`
1268
+ );
1269
+ }
1270
+ }
1271
+ );
1272
+ server.registerTool(
1273
+ track(mcpToolName(ctx.productName, "sessions_cancel")),
1274
+ {
1275
+ title: "Cancel Session",
1276
+ description: "Cancel a session.",
1277
+ inputSchema: { id: z.string().describe("Session ID") }
1278
+ },
1279
+ async ({ id }) => {
1280
+ try {
1281
+ const data = await ctx.gql.workspace.request(CANCEL_SESSION, { id });
1282
+ return jsonResult(data.tecCancelSession);
1283
+ } catch (error) {
1284
+ ctx.logger.error("technospam sessions_cancel failed", { error: String(error) });
1285
+ return errorResult(
1286
+ `Failed to cancel session: ${error instanceof Error ? error.message : String(error)}`
1287
+ );
1288
+ }
1289
+ }
1290
+ );
1291
+ server.registerTool(
1292
+ track(mcpToolName(ctx.productName, "sessions_complete")),
1293
+ {
1294
+ title: "Complete Session",
1295
+ description: "Mark a session complete, optionally recording a rating (1-5) and feedback from the mentee.",
1296
+ inputSchema: {
1297
+ id: z.string().describe("Session ID"),
1298
+ rating: z.number().int().min(1).max(5).optional().describe("Mentee rating (1-5)"),
1299
+ feedback: z.string().optional().describe("Mentee feedback")
1300
+ }
1301
+ },
1302
+ async ({ id, rating, feedback }) => {
1303
+ try {
1304
+ const data = await ctx.gql.workspace.request(COMPLETE_SESSION, { id, rating, feedback });
1305
+ return jsonResult(data.tecCompleteSession);
1306
+ } catch (error) {
1307
+ ctx.logger.error("technospam sessions_complete failed", { error: String(error) });
1308
+ return errorResult(
1309
+ `Failed to complete session: ${error instanceof Error ? error.message : String(error)}`
1310
+ );
1311
+ }
1312
+ }
1313
+ );
1314
+ }
1315
+ function registerHackathonTools(server, ctx, track) {
1316
+ server.registerTool(
1317
+ track(mcpToolName(ctx.productName, "hackathons_get")),
1318
+ {
1319
+ title: "Get Hackathon",
1320
+ description: "Fetch a single hackathon by ID, including its registered teams.",
1321
+ inputSchema: { id: z.string().describe("Hackathon ID") }
1322
+ },
1323
+ async ({ id }) => {
1324
+ try {
1325
+ const data = await ctx.gql.workspace.request(GET_HACKATHON, { id });
1326
+ if (!data.tecHackathon) return errorResult(`Hackathon ${id} not found`);
1327
+ return jsonResult(data.tecHackathon);
1328
+ } catch (error) {
1329
+ ctx.logger.error("technospam hackathons_get failed", { error: String(error) });
1330
+ return errorResult(
1331
+ `Failed to get hackathon: ${error instanceof Error ? error.message : String(error)}`
1332
+ );
1333
+ }
1334
+ }
1335
+ );
1336
+ server.registerTool(
1337
+ track(mcpToolName(ctx.productName, "hackathons_list")),
1338
+ {
1339
+ title: "List Hackathons",
1340
+ description: "List hackathons with optional status filter and pagination. Returns { items, pageInfo }.",
1341
+ inputSchema: {
1342
+ status: HackathonStatusEnum.optional().describe("Filter by lifecycle status"),
1343
+ limit: z.number().int().optional().describe("Page size (default 50)"),
1344
+ offset: z.number().int().optional().describe("Pagination offset (default 0)"),
1345
+ cursor: z.string().optional().describe("Opaque pagination cursor")
1346
+ }
1347
+ },
1348
+ async ({ status, limit, offset, cursor }) => {
1349
+ try {
1350
+ const data = await ctx.gql.workspace.request(LIST_HACKATHONS, {
1351
+ status,
1352
+ pagination: buildPagination(limit, offset, cursor)
1353
+ });
1354
+ return jsonResult(data.tecListHackathons);
1355
+ } catch (error) {
1356
+ ctx.logger.error("technospam hackathons_list failed", { error: String(error) });
1357
+ return errorResult(
1358
+ `Failed to list hackathons: ${error instanceof Error ? error.message : String(error)}`
1359
+ );
1360
+ }
1361
+ }
1362
+ );
1363
+ server.registerTool(
1364
+ track(mcpToolName(ctx.productName, "hackathons_create")),
1365
+ {
1366
+ title: "Create Hackathon",
1367
+ description: "Create a hackathon (DRAFT). `slug`/`title` required; `startAt`/`endAt` are ISO-8601 DateTimes.",
1368
+ inputSchema: {
1369
+ slug: z.string().min(1).describe("URL slug, unique within the workspace"),
1370
+ title: z.string().min(1).describe("Hackathon title"),
1371
+ startAt: z.string().min(1).describe("ISO-8601 DateTime start"),
1372
+ endAt: z.string().min(1).describe("ISO-8601 DateTime end"),
1373
+ theme: z.string().optional().describe("Hackathon theme"),
1374
+ description: z.string().optional().describe("Hackathon description"),
1375
+ submissionDeadline: z.string().optional().describe("ISO-8601 DateTime submission deadline"),
1376
+ prizePool: z.number().int().optional().describe("Total prize pool amount"),
1377
+ currency: z.string().optional().describe("Prize-pool currency code"),
1378
+ maxTeamSize: z.number().int().optional().describe("Maximum team size"),
1379
+ tags: z.array(z.string()).optional().describe("Discovery tags")
1380
+ }
1381
+ },
1382
+ async ({
1383
+ slug,
1384
+ title,
1385
+ startAt,
1386
+ endAt,
1387
+ theme,
1388
+ description,
1389
+ submissionDeadline,
1390
+ prizePool,
1391
+ currency,
1392
+ maxTeamSize,
1393
+ tags
1394
+ }) => {
1395
+ try {
1396
+ const input = { slug, title, startAt, endAt };
1397
+ if (theme !== void 0) input.theme = theme;
1398
+ if (description !== void 0) input.description = description;
1399
+ if (submissionDeadline !== void 0) input.submissionDeadline = submissionDeadline;
1400
+ if (prizePool !== void 0) input.prizePool = prizePool;
1401
+ if (currency !== void 0) input.currency = currency;
1402
+ if (maxTeamSize !== void 0) input.maxTeamSize = maxTeamSize;
1403
+ if (tags !== void 0) input.tags = tags;
1404
+ const data = await ctx.gql.workspace.request(CREATE_HACKATHON, { input });
1405
+ return jsonResult(data.tecCreateHackathon);
1406
+ } catch (error) {
1407
+ ctx.logger.error("technospam hackathons_create failed", { error: String(error) });
1408
+ return errorResult(
1409
+ `Failed to create hackathon: ${error instanceof Error ? error.message : String(error)}`
1410
+ );
1411
+ }
1412
+ }
1413
+ );
1414
+ server.registerTool(
1415
+ track(mcpToolName(ctx.productName, "hackathons_update")),
1416
+ {
1417
+ title: "Update Hackathon",
1418
+ description: "Update a hackathon. At least one mutable field must be provided.",
1419
+ inputSchema: {
1420
+ id: z.string().describe("Hackathon ID"),
1421
+ title: z.string().optional().describe("Updated title"),
1422
+ theme: z.string().optional().describe("Updated theme"),
1423
+ description: z.string().optional().describe("Updated description"),
1424
+ startAt: z.string().optional().describe("Updated ISO-8601 DateTime start"),
1425
+ endAt: z.string().optional().describe("Updated ISO-8601 DateTime end"),
1426
+ submissionDeadline: z.string().optional().describe("Updated submission deadline"),
1427
+ prizePool: z.number().int().optional().describe("Updated prize pool"),
1428
+ currency: z.string().optional().describe("Updated currency code"),
1429
+ maxTeamSize: z.number().int().optional().describe("Updated max team size"),
1430
+ tags: z.array(z.string()).optional().describe("Updated tags")
1431
+ }
1432
+ },
1433
+ async ({
1434
+ id,
1435
+ title,
1436
+ theme,
1437
+ description,
1438
+ startAt,
1439
+ endAt,
1440
+ submissionDeadline,
1441
+ prizePool,
1442
+ currency,
1443
+ maxTeamSize,
1444
+ tags
1445
+ }) => {
1446
+ try {
1447
+ const input = {};
1448
+ if (title !== void 0) input.title = title;
1449
+ if (theme !== void 0) input.theme = theme;
1450
+ if (description !== void 0) input.description = description;
1451
+ if (startAt !== void 0) input.startAt = startAt;
1452
+ if (endAt !== void 0) input.endAt = endAt;
1453
+ if (submissionDeadline !== void 0) input.submissionDeadline = submissionDeadline;
1454
+ if (prizePool !== void 0) input.prizePool = prizePool;
1455
+ if (currency !== void 0) input.currency = currency;
1456
+ if (maxTeamSize !== void 0) input.maxTeamSize = maxTeamSize;
1457
+ if (tags !== void 0) input.tags = tags;
1458
+ if (Object.keys(input).length === 0) {
1459
+ return errorResult("No updates specified \u2014 provide at least one mutable hackathon field");
1460
+ }
1461
+ const data = await ctx.gql.workspace.request(UPDATE_HACKATHON, { id, input });
1462
+ return jsonResult(data.tecUpdateHackathon);
1463
+ } catch (error) {
1464
+ ctx.logger.error("technospam hackathons_update failed", { error: String(error) });
1465
+ return errorResult(
1466
+ `Failed to update hackathon: ${error instanceof Error ? error.message : String(error)}`
1467
+ );
1468
+ }
1469
+ }
1470
+ );
1471
+ server.registerTool(
1472
+ track(mcpToolName(ctx.productName, "hackathons_open_registration")),
1473
+ {
1474
+ title: "Open Hackathon Registration",
1475
+ description: "Open registration for a hackathon (DRAFT -> REGISTRATION_OPEN).",
1476
+ inputSchema: { id: z.string().describe("Hackathon ID") }
1477
+ },
1478
+ async ({ id }) => {
1479
+ try {
1480
+ const data = await ctx.gql.workspace.request(OPEN_REGISTRATION, { id });
1481
+ return jsonResult(data.tecOpenRegistration);
1482
+ } catch (error) {
1483
+ ctx.logger.error("technospam hackathons_open_registration failed", {
1484
+ error: String(error)
1485
+ });
1486
+ return errorResult(
1487
+ `Failed to open registration: ${error instanceof Error ? error.message : String(error)}`
1488
+ );
1489
+ }
1490
+ }
1491
+ );
1492
+ }
1493
+ function registerTeamTools(server, ctx, track) {
1494
+ server.registerTool(
1495
+ track(mcpToolName(ctx.productName, "teams_list")),
1496
+ {
1497
+ title: "List Teams",
1498
+ description: "List all teams registered for a hackathon.",
1499
+ inputSchema: { hackathonId: z.string().describe("Hackathon ID") }
1500
+ },
1501
+ async ({ hackathonId }) => {
1502
+ try {
1503
+ const data = await ctx.gql.workspace.request(LIST_TEAMS, { hackathonId });
1504
+ return jsonResult(data.tecListTeams);
1505
+ } catch (error) {
1506
+ ctx.logger.error("technospam teams_list failed", { error: String(error) });
1507
+ return errorResult(
1508
+ `Failed to list teams: ${error instanceof Error ? error.message : String(error)}`
1509
+ );
1510
+ }
1511
+ }
1512
+ );
1513
+ server.registerTool(
1514
+ track(mcpToolName(ctx.productName, "teams_register")),
1515
+ {
1516
+ title: "Register Team",
1517
+ description: "Register a team for a hackathon with the calling user as captain. `memberIds` are additional team member user IDs.",
1518
+ inputSchema: {
1519
+ hackathonId: z.string().min(1).describe("Hackathon ID"),
1520
+ name: z.string().min(1).describe("Team name"),
1521
+ memberIds: z.array(z.string()).optional().describe("Additional member user IDs")
1522
+ }
1523
+ },
1524
+ async ({ hackathonId, name, memberIds }) => {
1525
+ try {
1526
+ const input = { hackathonId, name };
1527
+ if (memberIds !== void 0) input.memberIds = memberIds;
1528
+ const data = await ctx.gql.workspace.request(REGISTER_TEAM, { input });
1529
+ return jsonResult(data.tecRegisterTeam);
1530
+ } catch (error) {
1531
+ ctx.logger.error("technospam teams_register failed", { error: String(error) });
1532
+ return errorResult(
1533
+ `Failed to register team: ${error instanceof Error ? error.message : String(error)}`
1534
+ );
1535
+ }
1536
+ }
1537
+ );
1538
+ server.registerTool(
1539
+ track(mcpToolName(ctx.productName, "teams_submit_project")),
1540
+ {
1541
+ title: "Submit Team Project",
1542
+ description: "Submit a team's project for judging (sets status SUBMITTED).",
1543
+ inputSchema: {
1544
+ teamId: z.string().min(1).describe("Team ID"),
1545
+ projectName: z.string().min(1).describe("Project name"),
1546
+ projectUrl: z.string().optional().describe("Live project / demo URL"),
1547
+ repoUrl: z.string().optional().describe("Source repository URL"),
1548
+ pitch: z.string().optional().describe("Project pitch / description")
1549
+ }
1550
+ },
1551
+ async ({ teamId, projectName, projectUrl, repoUrl, pitch }) => {
1552
+ try {
1553
+ const input = { teamId, projectName };
1554
+ if (projectUrl !== void 0) input.projectUrl = projectUrl;
1555
+ if (repoUrl !== void 0) input.repoUrl = repoUrl;
1556
+ if (pitch !== void 0) input.pitch = pitch;
1557
+ const data = await ctx.gql.workspace.request(SUBMIT_PROJECT, { input });
1558
+ return jsonResult(data.tecSubmitProject);
1559
+ } catch (error) {
1560
+ ctx.logger.error("technospam teams_submit_project failed", { error: String(error) });
1561
+ return errorResult(
1562
+ `Failed to submit project: ${error instanceof Error ? error.message : String(error)}`
1563
+ );
1564
+ }
1565
+ }
1566
+ );
1567
+ server.registerTool(
1568
+ track(mcpToolName(ctx.productName, "teams_score")),
1569
+ {
1570
+ title: "Score Team",
1571
+ description: "Record a judge score for a team and optionally mark it WINNER.",
1572
+ inputSchema: {
1573
+ id: z.string().describe("Team ID"),
1574
+ score: z.number().int().describe("Judge score"),
1575
+ winner: z.boolean().optional().describe("Mark the team as a WINNER")
1576
+ }
1577
+ },
1578
+ async ({ id, score, winner }) => {
1579
+ try {
1580
+ const data = await ctx.gql.workspace.request(SCORE_TEAM, { id, score, winner });
1581
+ return jsonResult(data.tecScoreTeam);
1582
+ } catch (error) {
1583
+ ctx.logger.error("technospam teams_score failed", { error: String(error) });
1584
+ return errorResult(
1585
+ `Failed to score team: ${error instanceof Error ? error.message : String(error)}`
1586
+ );
1587
+ }
1588
+ }
1589
+ );
1590
+ }
1591
+
1592
+ // src/modules/technospam/tools/health.ts
1593
+ function registerHealthTools(server, ctx, track) {
1594
+ server.registerTool(
1595
+ track(mcpToolName(ctx.productName, "health")),
1596
+ {
1597
+ title: "Technospam Health Check",
1598
+ description: "Public health probe for the Technospam subgraph (tecHealth). Requires no authentication.",
1599
+ inputSchema: {}
1600
+ },
1601
+ async () => {
1602
+ try {
1603
+ const data = await ctx.gql.workspace.request(HEALTH, {});
1604
+ return jsonResult({ ok: true, status: data.tecHealth });
1605
+ } catch (error) {
1606
+ ctx.logger.error("technospam health failed", { error: String(error) });
1607
+ return errorResult(
1608
+ `Technospam health check failed: ${error instanceof Error ? error.message : String(error)}`
1609
+ );
1610
+ }
1611
+ }
1612
+ );
1613
+ }
1614
+
1615
+ // src/modules/technospam/resources.ts
1616
+ function registerTechnospamResources(server, ctx, track) {
1617
+ const schemaUri = track(mcpResourceUri(ctx.productName, "schema"));
1618
+ const coursesUri = track(mcpResourceUri(ctx.productName, "courses"));
1619
+ const hackathonsUri = track(mcpResourceUri(ctx.productName, "hackathons"));
1620
+ const mentorsUri = track(mcpResourceUri(ctx.productName, "mentors"));
1621
+ server.registerResource(
1622
+ "technospam-schema",
1623
+ schemaUri,
1624
+ {
1625
+ title: "Technospam Domain Schema",
1626
+ description: "Overview of the Technospam learning-platform domain \u2014 entities, enums, and the per-entity tool surface.",
1627
+ mimeType: "text/plain"
1628
+ },
1629
+ async () => {
1630
+ ctx.logger.debug(`Resource ${schemaUri} requested`);
1631
+ const schema = `# Technospam Domain (Overview)
1632
+
1633
+ Technical-learning platform: courses, competitive-programming practice,
1634
+ 1:1 mentorship, and hackathons.
1635
+
1636
+ ## Entities
1637
+ - TecCourse (+TecLesson) \u2014 a course and its ordered lessons
1638
+ - TecEnrollment \u2014 a learner's progress through a course
1639
+ - TecCertificate \u2014 a verifiable completion certificate
1640
+ - TecProblem (+TecSubmission) \u2014 a practice problem and its graded submissions
1641
+ - TecMentor (+TecMentorSession) \u2014 a mentor profile and its 1:1 sessions
1642
+ - TecHackathon (+TecHackathonTeam) \u2014 a hackathon event and its teams
1643
+
1644
+ ## Enums
1645
+ - TecCourseLevel: BEGINNER, INTERMEDIATE, ADVANCED, EXPERT
1646
+ - TecCourseStatus: DRAFT, PUBLISHED, ARCHIVED
1647
+ - TecLessonKind: ARTICLE, VIDEO, QUIZ, PROJECT, INTERACTIVE
1648
+ - TecEnrollmentStatus: ENROLLED, IN_PROGRESS, COMPLETED, DROPPED
1649
+ - TecCertificateStatus: ISSUED, REVOKED
1650
+ - TecProblemDifficulty: EASY, MEDIUM, HARD
1651
+ - TecProblemStatus: DRAFT, PUBLISHED, ARCHIVED
1652
+ - TecSubmissionVerdict: PENDING, ACCEPTED, WRONG_ANSWER, TIME_LIMIT_EXCEEDED, RUNTIME_ERROR, COMPILE_ERROR
1653
+ - TecMentorStatus: PENDING, ACTIVE, PAUSED, INACTIVE
1654
+ - TecSessionStatus: REQUESTED, CONFIRMED, COMPLETED, CANCELLED, NO_SHOW
1655
+ - TecHackathonStatus: DRAFT, REGISTRATION_OPEN, IN_PROGRESS, JUDGING, COMPLETED, CANCELLED
1656
+ - TecTeamSubmissionStatus: DRAFT, SUBMITTED, WINNER, DISQUALIFIED
1657
+
1658
+ ## Tool groups
1659
+ courses, lessons, enrollments, certificates, problems, submissions, mentors,
1660
+ sessions, hackathons, teams. All run against the WORKSPACE gateway
1661
+ (workspace-scoped) except the public health probe.
1662
+ `;
1663
+ return {
1664
+ contents: [{ uri: schemaUri, text: schema, mimeType: "text/plain" }]
1665
+ };
1666
+ }
1667
+ );
1668
+ server.registerResource(
1669
+ "technospam-courses",
1670
+ coursesUri,
1671
+ {
1672
+ title: "Technospam Course Catalog",
1673
+ description: "The first 50 courses in the bound workspace.",
1674
+ mimeType: "application/json"
1675
+ },
1676
+ async () => {
1677
+ ctx.logger.debug(`Resource ${coursesUri} requested`);
1678
+ try {
1679
+ const data = await ctx.gql.workspace.request(LIST_COURSES, {
1680
+ pagination: { limit: 50, offset: 0 }
1681
+ });
1682
+ return {
1683
+ contents: [
1684
+ {
1685
+ uri: coursesUri,
1686
+ text: JSON.stringify(data.tecListCourses, null, 2),
1687
+ mimeType: "application/json"
1688
+ }
1689
+ ]
1690
+ };
1691
+ } catch (error) {
1692
+ return {
1693
+ contents: [
1694
+ {
1695
+ uri: coursesUri,
1696
+ text: JSON.stringify({
1697
+ error: error instanceof Error ? error.message : String(error)
1698
+ }),
1699
+ mimeType: "application/json"
1700
+ }
1701
+ ]
1702
+ };
1703
+ }
1704
+ }
1705
+ );
1706
+ server.registerResource(
1707
+ "technospam-hackathons",
1708
+ hackathonsUri,
1709
+ {
1710
+ title: "Technospam Hackathon Directory",
1711
+ description: "The first 50 hackathons in the bound workspace.",
1712
+ mimeType: "application/json"
1713
+ },
1714
+ async () => {
1715
+ ctx.logger.debug(`Resource ${hackathonsUri} requested`);
1716
+ try {
1717
+ const data = await ctx.gql.workspace.request(
1718
+ LIST_HACKATHONS,
1719
+ { pagination: { limit: 50, offset: 0 } }
1720
+ );
1721
+ return {
1722
+ contents: [
1723
+ {
1724
+ uri: hackathonsUri,
1725
+ text: JSON.stringify(data.tecListHackathons, null, 2),
1726
+ mimeType: "application/json"
1727
+ }
1728
+ ]
1729
+ };
1730
+ } catch (error) {
1731
+ return {
1732
+ contents: [
1733
+ {
1734
+ uri: hackathonsUri,
1735
+ text: JSON.stringify({
1736
+ error: error instanceof Error ? error.message : String(error)
1737
+ }),
1738
+ mimeType: "application/json"
1739
+ }
1740
+ ]
1741
+ };
1742
+ }
1743
+ }
1744
+ );
1745
+ server.registerResource(
1746
+ "technospam-mentors",
1747
+ mentorsUri,
1748
+ {
1749
+ title: "Technospam Mentor Directory",
1750
+ description: "The first 50 mentor profiles in the bound workspace.",
1751
+ mimeType: "application/json"
1752
+ },
1753
+ async () => {
1754
+ ctx.logger.debug(`Resource ${mentorsUri} requested`);
1755
+ try {
1756
+ const data = await ctx.gql.workspace.request(LIST_MENTORS, {
1757
+ pagination: { limit: 50, offset: 0 }
1758
+ });
1759
+ return {
1760
+ contents: [
1761
+ {
1762
+ uri: mentorsUri,
1763
+ text: JSON.stringify(data.tecListMentors, null, 2),
1764
+ mimeType: "application/json"
1765
+ }
1766
+ ]
1767
+ };
1768
+ } catch (error) {
1769
+ return {
1770
+ contents: [
1771
+ {
1772
+ uri: mentorsUri,
1773
+ text: JSON.stringify({
1774
+ error: error instanceof Error ? error.message : String(error)
1775
+ }),
1776
+ mimeType: "application/json"
1777
+ }
1778
+ ]
1779
+ };
1780
+ }
1781
+ }
1782
+ );
1783
+ }
1784
+ function registerTechnospamPrompts(server, ctx, track) {
1785
+ const p = ctx.productName;
1786
+ const authoringName = track(mcpPromptName(p, "course-authoring"));
1787
+ const opsName = track(mcpPromptName(p, "learning-ops"));
1788
+ server.registerPrompt(
1789
+ authoringName,
1790
+ {
1791
+ title: "Course Authoring",
1792
+ description: "Guide for authoring a new Technospam course end to end: create the course, add lessons, publish it, and (optionally) issue completion certificates.",
1793
+ argsSchema: {
1794
+ title: z.string().optional().describe("Course title"),
1795
+ topic: z.string().optional().describe("Course topic/subject")
1796
+ }
1797
+ },
1798
+ ({ title, topic }) => {
1799
+ ctx.logger.debug(`Prompt ${authoringName} called`, { title, topic });
1800
+ const titleNote = title ? `Use the title "${title}".` : "Ask the user for the course title.";
1801
+ const topicNote = topic ? `Use topic "${topic}".` : "Help the user pick a topic.";
1802
+ return {
1803
+ messages: [
1804
+ {
1805
+ role: "user",
1806
+ content: {
1807
+ type: "text",
1808
+ text: `Help me author a new Technospam course.
1809
+
1810
+ 1. **Create Course**: Use ${p}_courses_create. ${titleNote} ${topicNote} Provide a unique slug, a level (BEGINNER/INTERMEDIATE/ADVANCED/EXPERT), and optional estimatedMinutes.
1811
+ 2. **Add Lessons**: Use ${p}_lessons_create for each lesson \u2014 set kind (ARTICLE/VIDEO/QUIZ/PROJECT/INTERACTIVE), position, and optional content or a fileId (video/attachment).
1812
+ 3. **Verify Outline**: Use ${p}_lessons_list for the new courseId to confirm ordering.
1813
+ 4. **Publish**: Use ${p}_courses_publish when the outline is complete (DRAFT -> PUBLISHED).
1814
+ 5. **(Optional) Certificates**: After learners finish, use ${p}_certificates_issue (courseId, learnerId, score).
1815
+
1816
+ Show results at each step and confirm before publishing.`
1817
+ }
1818
+ }
1819
+ ]
1820
+ };
1821
+ }
1822
+ );
1823
+ server.registerPrompt(
1824
+ opsName,
1825
+ {
1826
+ title: "Learning Operations",
1827
+ description: "Operations workflow across the Technospam learning surfaces: triage practice submissions, manage mentor sessions, and run hackathon judging.",
1828
+ argsSchema: {
1829
+ problemId: z.string().optional().describe("Problem ID to triage submissions for"),
1830
+ hackathonId: z.string().optional().describe("Hackathon ID to operate")
1831
+ }
1832
+ },
1833
+ ({ problemId, hackathonId }) => {
1834
+ ctx.logger.debug(`Prompt ${opsName} called`, { problemId, hackathonId });
1835
+ const problemNote = problemId ? `Triage submissions for problem "${problemId}".` : `First list problems with ${p}_problems_list and pick one.`;
1836
+ const hackathonNote = hackathonId ? `Operate hackathon "${hackathonId}".` : `List hackathons with ${p}_hackathons_list and pick one.`;
1837
+ return {
1838
+ messages: [
1839
+ {
1840
+ role: "user",
1841
+ content: {
1842
+ type: "text",
1843
+ text: `Run Technospam learning operations.
1844
+
1845
+ 1. **Practice Queue**: ${problemNote} Use ${p}_submissions_list filtered by problemId and grade PENDING ones with ${p}_submissions_grade (verdict + runtimeMs).
1846
+ 2. **Mentorship**: Use ${p}_mentors_list (status=PENDING) and approve qualified mentors with ${p}_mentors_approve. Use ${p}_sessions_list (status=REQUESTED) and confirm them with ${p}_sessions_confirm; complete finished sessions with ${p}_sessions_complete.
1847
+ 3. **Hackathon Judging**: ${hackathonNote} Use ${p}_teams_list for the hackathon; for each SUBMITTED team record a score with ${p}_teams_score (set winner=true for the winner). Move the hackathon through its lifecycle with ${p}_hackathons_update / ${p}_hackathons_open_registration as needed.
1848
+
1849
+ Summarize what needs attention after each step.`
1850
+ }
1851
+ }
1852
+ ]
1853
+ };
1854
+ }
1855
+ );
1856
+ }
1857
+
1858
+ // src/modules/technospam/index.ts
1859
+ var technospamModule = {
1860
+ id: "technospam",
1861
+ version: "0.0.1",
1862
+ displayName: "Technospam",
1863
+ dependsOn: ["auth"],
1864
+ capabilities: { tools: {}, resources: {}, prompts: {} },
1865
+ register(server, ctx) {
1866
+ const tools = [];
1867
+ const resources = [];
1868
+ const prompts = [];
1869
+ const trackTool = (n) => {
1870
+ tools.push(n);
1871
+ return n;
1872
+ };
1873
+ const trackResource = (uri) => {
1874
+ resources.push(uri);
1875
+ return uri;
1876
+ };
1877
+ const trackPrompt = (n) => {
1878
+ prompts.push(n);
1879
+ return n;
1880
+ };
1881
+ registerCourseTools(server, ctx, trackTool);
1882
+ registerLessonTools(server, ctx, trackTool);
1883
+ registerEnrollmentTools(server, ctx, trackTool);
1884
+ registerCertificateTools(server, ctx, trackTool);
1885
+ registerProblemTools(server, ctx, trackTool);
1886
+ registerSubmissionTools(server, ctx, trackTool);
1887
+ registerMentorTools(server, ctx, trackTool);
1888
+ registerSessionTools(server, ctx, trackTool);
1889
+ registerHackathonTools(server, ctx, trackTool);
1890
+ registerTeamTools(server, ctx, trackTool);
1891
+ registerHealthTools(server, ctx, trackTool);
1892
+ registerTechnospamResources(server, ctx, trackResource);
1893
+ registerTechnospamPrompts(server, ctx, trackPrompt);
1894
+ return { tools, resources, prompts };
1895
+ }
1896
+ };
1897
+ var technospam_default = technospamModule;
1898
+
1899
+ export { technospam_default as default };
1900
+ //# sourceMappingURL=index.js.map
1901
+ //# sourceMappingURL=index.js.map