@charlie.act7/canvas-mcp-server 1.1.8 → 1.2.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/.mcp.json +8 -6
- package/LICENSE +21 -0
- package/README.md +49 -22
- package/dist/http-server.js +139 -5
- package/dist/index.js +35 -4
- package/dist/services/agent-runner.js +214 -0
- package/dist/services/canvas-client.js +248 -5
- package/dist/services/gemini-runner.js +124 -0
- package/dist/tools/analytics-tools.js +118 -0
- package/dist/tools/assignment-tools.js +77 -1
- package/dist/tools/communication-tools.js +101 -0
- package/dist/tools/conversation-tools.js +130 -0
- package/dist/tools/course-tools.js +142 -0
- package/dist/tools/enrollment-tools.js +202 -0
- package/dist/tools/new-quiz-tools.js +357 -0
- package/dist/tools/peer-review-tools.js +130 -0
- package/dist/tools/quiz-tools.js +312 -3
- package/dist/tools/rubric-tools.js +159 -0
- package/package.json +70 -68
- package/dist/tools/question-bank-tools.js +0 -238
package/dist/tools/quiz-tools.js
CHANGED
|
@@ -142,11 +142,306 @@ export const quizTools = [
|
|
|
142
142
|
return { content: [{ type: "text", text: JSON.stringify(quiz, null, 2) }] };
|
|
143
143
|
}
|
|
144
144
|
},
|
|
145
|
+
{
|
|
146
|
+
name: "canvas_list_quiz_questions",
|
|
147
|
+
tool: {
|
|
148
|
+
name: "canvas_list_quiz_questions",
|
|
149
|
+
description: "List all questions in a specific quiz",
|
|
150
|
+
inputSchema: {
|
|
151
|
+
type: "object",
|
|
152
|
+
properties: {
|
|
153
|
+
course_id: { anyOf: [{ type: "number" }, { type: "string" }], description: "The ID or name of the course" },
|
|
154
|
+
quiz_id: { type: "number", description: "The quiz ID" }
|
|
155
|
+
},
|
|
156
|
+
required: ["course_id", "quiz_id"]
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
handler: async (client, args) => {
|
|
160
|
+
const input = z.object({
|
|
161
|
+
course_id: z.union([z.number(), z.string()]),
|
|
162
|
+
quiz_id: z.number()
|
|
163
|
+
}).parse(args);
|
|
164
|
+
const courseId = await resolveCourseId(client, input.course_id);
|
|
165
|
+
const questions = await client.listQuizQuestions(courseId, input.quiz_id);
|
|
166
|
+
return { content: [{ type: "text", text: JSON.stringify(questions, null, 2) }] };
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
name: "canvas_get_quiz_question",
|
|
171
|
+
tool: {
|
|
172
|
+
name: "canvas_get_quiz_question",
|
|
173
|
+
description: "Get details of a single quiz question",
|
|
174
|
+
inputSchema: {
|
|
175
|
+
type: "object",
|
|
176
|
+
properties: {
|
|
177
|
+
course_id: { anyOf: [{ type: "number" }, { type: "string" }], description: "The ID or name of the course" },
|
|
178
|
+
quiz_id: { type: "number", description: "The quiz ID" },
|
|
179
|
+
question_id: { type: "number", description: "The question ID" }
|
|
180
|
+
},
|
|
181
|
+
required: ["course_id", "quiz_id", "question_id"]
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
handler: async (client, args) => {
|
|
185
|
+
const input = z.object({
|
|
186
|
+
course_id: z.union([z.number(), z.string()]),
|
|
187
|
+
quiz_id: z.number(),
|
|
188
|
+
question_id: z.number()
|
|
189
|
+
}).parse(args);
|
|
190
|
+
const courseId = await resolveCourseId(client, input.course_id);
|
|
191
|
+
const question = await client.getQuizQuestion(courseId, input.quiz_id, input.question_id);
|
|
192
|
+
return { content: [{ type: "text", text: JSON.stringify(question, null, 2) }] };
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
name: "canvas_create_quiz_question",
|
|
197
|
+
tool: {
|
|
198
|
+
name: "canvas_create_quiz_question",
|
|
199
|
+
description: `Create a new question in a quiz. Supports all 12 Canvas question types:
|
|
200
|
+
- multiple_choice_question: answers with answer_text + answer_weight (100=correct, 0=wrong)
|
|
201
|
+
- true_false_question: two answers ("True"/"False") with answer_weight
|
|
202
|
+
- short_answer_question: one or more correct answer_text values, all with answer_weight 100
|
|
203
|
+
- fill_in_multiple_blanks_question: answers with blank_id + answer_text + answer_weight 100
|
|
204
|
+
- multiple_answers_question: checkboxes; correct answers get answer_weight 100, wrong get 0
|
|
205
|
+
- multiple_dropdowns_question: answers with blank_id + answer_text + answer_weight
|
|
206
|
+
- matching_question: answers with answer_match_left + answer_match_right; add distractors via matching_answer_incorrect_matches on any one answer
|
|
207
|
+
- numerical_question: answers with numerical_answer_type (exact_answer: exact+margin; range_answer: start+end; precision_answer: approximate+precision)
|
|
208
|
+
- calculated_question: answers with variables and formulas (advanced)
|
|
209
|
+
- essay_question: no answers needed
|
|
210
|
+
- file_upload_question: no answers needed
|
|
211
|
+
- text_only_question: no answers needed, just displays text`,
|
|
212
|
+
inputSchema: {
|
|
213
|
+
type: "object",
|
|
214
|
+
properties: {
|
|
215
|
+
course_id: { anyOf: [{ type: "number" }, { type: "string" }], description: "The ID or name of the course" },
|
|
216
|
+
quiz_id: { type: "number", description: "The quiz ID" },
|
|
217
|
+
question_name: { type: "string", description: "Short label for the question (e.g. 'Question 1')" },
|
|
218
|
+
question_type: {
|
|
219
|
+
type: "string",
|
|
220
|
+
enum: [
|
|
221
|
+
"multiple_choice_question",
|
|
222
|
+
"true_false_question",
|
|
223
|
+
"short_answer_question",
|
|
224
|
+
"fill_in_multiple_blanks_question",
|
|
225
|
+
"multiple_answers_question",
|
|
226
|
+
"multiple_dropdowns_question",
|
|
227
|
+
"matching_question",
|
|
228
|
+
"numerical_question",
|
|
229
|
+
"calculated_question",
|
|
230
|
+
"essay_question",
|
|
231
|
+
"file_upload_question",
|
|
232
|
+
"text_only_question"
|
|
233
|
+
],
|
|
234
|
+
description: "The type of question"
|
|
235
|
+
},
|
|
236
|
+
question_text: { type: "string", description: "HTML or plain text of the question. For fill_in_multiple_blanks/multiple_dropdowns use [blank_id] placeholders." },
|
|
237
|
+
points_possible: { type: "number", description: "Points this question is worth" },
|
|
238
|
+
position: { type: "number", description: "Display order position within the quiz" },
|
|
239
|
+
quiz_group_id: { type: "number", description: "Assign to a quiz group/bank (optional)" },
|
|
240
|
+
correct_comments: { type: "string", description: "Feedback shown when answer is correct" },
|
|
241
|
+
incorrect_comments: { type: "string", description: "Feedback shown when answer is incorrect" },
|
|
242
|
+
neutral_comments: { type: "string", description: "Feedback always shown after answering" },
|
|
243
|
+
text_after_answers: { type: "string", description: "Text displayed after the answers (used in missing word questions)" },
|
|
244
|
+
answers: {
|
|
245
|
+
type: "array",
|
|
246
|
+
description: "Answer objects. Fields used depend on question_type.",
|
|
247
|
+
items: {
|
|
248
|
+
type: "object",
|
|
249
|
+
properties: {
|
|
250
|
+
answer_text: { type: "string", description: "The answer text (most question types)" },
|
|
251
|
+
answer_weight: { type: "number", description: "100 = correct, 0 = incorrect. For multiple_answers each correct option is 100." },
|
|
252
|
+
answer_comments: { type: "string", description: "Per-answer feedback shown after selection" },
|
|
253
|
+
blank_id: { type: "string", description: "For fill_in_multiple_blanks / multiple_dropdowns: which blank this answer belongs to" },
|
|
254
|
+
answer_match_left: { type: "string", description: "For matching: left-side item (the prompt)" },
|
|
255
|
+
answer_match_right: { type: "string", description: "For matching: right-side correct match" },
|
|
256
|
+
matching_answer_incorrect_matches: { type: "string", description: "For matching: comma-separated distractor values for the right column" },
|
|
257
|
+
numerical_answer_type: { type: "string", enum: ["exact_answer", "range_answer", "precision_answer"], description: "For numerical questions: accepted answer format" },
|
|
258
|
+
exact: { type: "number", description: "For numerical exact_answer: the exact correct value" },
|
|
259
|
+
margin: { type: "number", description: "For numerical exact_answer: allowed margin of error" },
|
|
260
|
+
approximate: { type: "number", description: "For numerical precision_answer: the approximate value" },
|
|
261
|
+
precision: { type: "number", description: "For numerical precision_answer: significant figures required" },
|
|
262
|
+
start: { type: "number", description: "For numerical range_answer: start of accepted range" },
|
|
263
|
+
end: { type: "number", description: "For numerical range_answer: end of accepted range" }
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
required: ["course_id", "quiz_id", "question_type", "question_text", "points_possible"]
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
handler: async (client, args) => {
|
|
272
|
+
const answerSchema = z.object({
|
|
273
|
+
answer_text: z.string().optional(),
|
|
274
|
+
answer_weight: z.number().optional(),
|
|
275
|
+
answer_comments: z.string().optional(),
|
|
276
|
+
blank_id: z.string().optional(),
|
|
277
|
+
answer_match_left: z.string().optional(),
|
|
278
|
+
answer_match_right: z.string().optional(),
|
|
279
|
+
matching_answer_incorrect_matches: z.string().optional(),
|
|
280
|
+
numerical_answer_type: z.enum(["exact_answer", "range_answer", "precision_answer"]).optional(),
|
|
281
|
+
exact: z.number().optional(),
|
|
282
|
+
margin: z.number().optional(),
|
|
283
|
+
approximate: z.number().optional(),
|
|
284
|
+
precision: z.number().optional(),
|
|
285
|
+
start: z.number().optional(),
|
|
286
|
+
end: z.number().optional()
|
|
287
|
+
});
|
|
288
|
+
const input = z.object({
|
|
289
|
+
course_id: z.union([z.number(), z.string()]),
|
|
290
|
+
quiz_id: z.number(),
|
|
291
|
+
question_name: z.string().optional(),
|
|
292
|
+
question_type: z.enum([
|
|
293
|
+
"multiple_choice_question", "true_false_question", "short_answer_question",
|
|
294
|
+
"fill_in_multiple_blanks_question", "multiple_answers_question", "multiple_dropdowns_question",
|
|
295
|
+
"matching_question", "numerical_question", "calculated_question",
|
|
296
|
+
"essay_question", "file_upload_question", "text_only_question"
|
|
297
|
+
]),
|
|
298
|
+
question_text: z.string(),
|
|
299
|
+
points_possible: z.number(),
|
|
300
|
+
position: z.number().optional(),
|
|
301
|
+
quiz_group_id: z.number().optional(),
|
|
302
|
+
correct_comments: z.string().optional(),
|
|
303
|
+
incorrect_comments: z.string().optional(),
|
|
304
|
+
neutral_comments: z.string().optional(),
|
|
305
|
+
text_after_answers: z.string().optional(),
|
|
306
|
+
answers: z.array(answerSchema).optional()
|
|
307
|
+
}).parse(args);
|
|
308
|
+
const courseId = await resolveCourseId(client, input.course_id);
|
|
309
|
+
const { course_id: _, quiz_id: __, ...questionData } = input;
|
|
310
|
+
const question = await client.createQuizQuestion(courseId, input.quiz_id, questionData);
|
|
311
|
+
return { content: [{ type: "text", text: JSON.stringify(question, null, 2) }] };
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
name: "canvas_update_quiz_question",
|
|
316
|
+
tool: {
|
|
317
|
+
name: "canvas_update_quiz_question",
|
|
318
|
+
description: "Update an existing quiz question. Supports all 12 question types. Provide only the fields you want to change.",
|
|
319
|
+
inputSchema: {
|
|
320
|
+
type: "object",
|
|
321
|
+
properties: {
|
|
322
|
+
course_id: { anyOf: [{ type: "number" }, { type: "string" }], description: "The ID or name of the course" },
|
|
323
|
+
quiz_id: { type: "number", description: "The quiz ID" },
|
|
324
|
+
question_id: { type: "number", description: "The question ID to update" },
|
|
325
|
+
question_name: { type: "string" },
|
|
326
|
+
question_type: {
|
|
327
|
+
type: "string",
|
|
328
|
+
enum: [
|
|
329
|
+
"multiple_choice_question", "true_false_question", "short_answer_question",
|
|
330
|
+
"fill_in_multiple_blanks_question", "multiple_answers_question", "multiple_dropdowns_question",
|
|
331
|
+
"matching_question", "numerical_question", "calculated_question",
|
|
332
|
+
"essay_question", "file_upload_question", "text_only_question"
|
|
333
|
+
]
|
|
334
|
+
},
|
|
335
|
+
question_text: { type: "string" },
|
|
336
|
+
points_possible: { type: "number" },
|
|
337
|
+
position: { type: "number" },
|
|
338
|
+
quiz_group_id: { type: "number", nullable: true },
|
|
339
|
+
correct_comments: { type: "string" },
|
|
340
|
+
incorrect_comments: { type: "string" },
|
|
341
|
+
neutral_comments: { type: "string" },
|
|
342
|
+
text_after_answers: { type: "string" },
|
|
343
|
+
answers: {
|
|
344
|
+
type: "array",
|
|
345
|
+
description: "Full replacement of all answers. See canvas_create_quiz_question for field descriptions.",
|
|
346
|
+
items: {
|
|
347
|
+
type: "object",
|
|
348
|
+
properties: {
|
|
349
|
+
answer_text: { type: "string" },
|
|
350
|
+
answer_weight: { type: "number" },
|
|
351
|
+
answer_comments: { type: "string" },
|
|
352
|
+
blank_id: { type: "string" },
|
|
353
|
+
answer_match_left: { type: "string" },
|
|
354
|
+
answer_match_right: { type: "string" },
|
|
355
|
+
matching_answer_incorrect_matches: { type: "string" },
|
|
356
|
+
numerical_answer_type: { type: "string", enum: ["exact_answer", "range_answer", "precision_answer"] },
|
|
357
|
+
exact: { type: "number" },
|
|
358
|
+
margin: { type: "number" },
|
|
359
|
+
approximate: { type: "number" },
|
|
360
|
+
precision: { type: "number" },
|
|
361
|
+
start: { type: "number" },
|
|
362
|
+
end: { type: "number" }
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
},
|
|
367
|
+
required: ["course_id", "quiz_id", "question_id"]
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
handler: async (client, args) => {
|
|
371
|
+
const answerSchema = z.object({
|
|
372
|
+
answer_text: z.string().optional(),
|
|
373
|
+
answer_weight: z.number().optional(),
|
|
374
|
+
answer_comments: z.string().optional(),
|
|
375
|
+
blank_id: z.string().optional(),
|
|
376
|
+
answer_match_left: z.string().optional(),
|
|
377
|
+
answer_match_right: z.string().optional(),
|
|
378
|
+
matching_answer_incorrect_matches: z.string().optional(),
|
|
379
|
+
numerical_answer_type: z.enum(["exact_answer", "range_answer", "precision_answer"]).optional(),
|
|
380
|
+
exact: z.number().optional(),
|
|
381
|
+
margin: z.number().optional(),
|
|
382
|
+
approximate: z.number().optional(),
|
|
383
|
+
precision: z.number().optional(),
|
|
384
|
+
start: z.number().optional(),
|
|
385
|
+
end: z.number().optional()
|
|
386
|
+
});
|
|
387
|
+
const input = z.object({
|
|
388
|
+
course_id: z.union([z.number(), z.string()]),
|
|
389
|
+
quiz_id: z.number(),
|
|
390
|
+
question_id: z.number(),
|
|
391
|
+
question_name: z.string().optional(),
|
|
392
|
+
question_type: z.enum([
|
|
393
|
+
"multiple_choice_question", "true_false_question", "short_answer_question",
|
|
394
|
+
"fill_in_multiple_blanks_question", "multiple_answers_question", "multiple_dropdowns_question",
|
|
395
|
+
"matching_question", "numerical_question", "calculated_question",
|
|
396
|
+
"essay_question", "file_upload_question", "text_only_question"
|
|
397
|
+
]).optional(),
|
|
398
|
+
question_text: z.string().optional(),
|
|
399
|
+
points_possible: z.number().optional(),
|
|
400
|
+
position: z.number().optional(),
|
|
401
|
+
quiz_group_id: z.number().nullable().optional(),
|
|
402
|
+
correct_comments: z.string().optional(),
|
|
403
|
+
incorrect_comments: z.string().optional(),
|
|
404
|
+
neutral_comments: z.string().optional(),
|
|
405
|
+
text_after_answers: z.string().optional(),
|
|
406
|
+
answers: z.array(answerSchema).optional()
|
|
407
|
+
}).parse(args);
|
|
408
|
+
const courseId = await resolveCourseId(client, input.course_id);
|
|
409
|
+
const { course_id: _, quiz_id: __, question_id, ...questionData } = input;
|
|
410
|
+
const question = await client.updateQuizQuestion(courseId, input.quiz_id, question_id, questionData);
|
|
411
|
+
return { content: [{ type: "text", text: JSON.stringify(question, null, 2) }] };
|
|
412
|
+
}
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
name: "canvas_delete_quiz_question",
|
|
416
|
+
tool: {
|
|
417
|
+
name: "canvas_delete_quiz_question",
|
|
418
|
+
description: "Delete a question from a quiz",
|
|
419
|
+
inputSchema: {
|
|
420
|
+
type: "object",
|
|
421
|
+
properties: {
|
|
422
|
+
course_id: { anyOf: [{ type: "number" }, { type: "string" }], description: "The ID or name of the course" },
|
|
423
|
+
quiz_id: { type: "number", description: "The quiz ID" },
|
|
424
|
+
question_id: { type: "number", description: "The question ID to delete" }
|
|
425
|
+
},
|
|
426
|
+
required: ["course_id", "quiz_id", "question_id"]
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
handler: async (client, args) => {
|
|
430
|
+
const input = z.object({
|
|
431
|
+
course_id: z.union([z.number(), z.string()]),
|
|
432
|
+
quiz_id: z.number(),
|
|
433
|
+
question_id: z.number()
|
|
434
|
+
}).parse(args);
|
|
435
|
+
const courseId = await resolveCourseId(client, input.course_id);
|
|
436
|
+
const result = await client.deleteQuizQuestion(courseId, input.quiz_id, input.question_id);
|
|
437
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
438
|
+
}
|
|
439
|
+
},
|
|
145
440
|
{
|
|
146
441
|
name: "canvas_update_quiz",
|
|
147
442
|
tool: {
|
|
148
443
|
name: "canvas_update_quiz",
|
|
149
|
-
description: "Update an existing quiz",
|
|
444
|
+
description: "Update an existing quiz. Can change title, description, time limit, attempts, dates, shuffle, and publish state.",
|
|
150
445
|
inputSchema: {
|
|
151
446
|
type: "object",
|
|
152
447
|
properties: {
|
|
@@ -154,10 +449,17 @@ export const quizTools = [
|
|
|
154
449
|
quiz_id: { type: "number" },
|
|
155
450
|
title: { type: "string" },
|
|
156
451
|
description: { type: "string" },
|
|
452
|
+
quiz_type: { type: "string", enum: ["practice_quiz", "assignment", "graded_survey", "survey"] },
|
|
453
|
+
time_limit: { type: "number", description: "Time limit in minutes. Use null to remove limit.", nullable: true },
|
|
454
|
+
shuffle_answers: { type: "boolean" },
|
|
455
|
+
allowed_attempts: { type: "number", description: "Number of allowed attempts. Use -1 for unlimited." },
|
|
456
|
+
due_at: { type: "string", description: "ISO-8601 due date. Use null to clear.", nullable: true },
|
|
457
|
+
unlock_at: { type: "string", description: "ISO-8601 unlock date. Use null to clear.", nullable: true },
|
|
458
|
+
lock_at: { type: "string", description: "ISO-8601 lock date. Use null to clear.", nullable: true },
|
|
157
459
|
published: { type: "boolean" },
|
|
158
460
|
require_lockdown_browser: { type: "boolean" },
|
|
159
461
|
show_correct_answers: { type: "boolean" },
|
|
160
|
-
show_correct_answers_at: { type: "string", description: "ISO-8601 date to show correct answers" }
|
|
462
|
+
show_correct_answers_at: { type: "string", description: "ISO-8601 date to show correct answers", nullable: true }
|
|
161
463
|
},
|
|
162
464
|
required: ["course_id", "quiz_id"]
|
|
163
465
|
}
|
|
@@ -168,10 +470,17 @@ export const quizTools = [
|
|
|
168
470
|
quiz_id: z.number(),
|
|
169
471
|
title: z.string().optional(),
|
|
170
472
|
description: z.string().optional(),
|
|
473
|
+
quiz_type: z.enum(["practice_quiz", "assignment", "graded_survey", "survey"]).optional(),
|
|
474
|
+
time_limit: z.number().nullable().optional(),
|
|
475
|
+
shuffle_answers: z.boolean().optional(),
|
|
476
|
+
allowed_attempts: z.number().optional(),
|
|
477
|
+
due_at: z.string().nullable().optional(),
|
|
478
|
+
unlock_at: z.string().nullable().optional(),
|
|
479
|
+
lock_at: z.string().nullable().optional(),
|
|
171
480
|
published: z.boolean().optional(),
|
|
172
481
|
require_lockdown_browser: z.boolean().optional(),
|
|
173
482
|
show_correct_answers: z.boolean().optional(),
|
|
174
|
-
show_correct_answers_at: z.string().optional()
|
|
483
|
+
show_correct_answers_at: z.string().nullable().optional()
|
|
175
484
|
}).parse(args);
|
|
176
485
|
const courseId = await resolveCourseId(client, input.course_id);
|
|
177
486
|
const quizData = { ...input };
|
|
@@ -1,6 +1,165 @@
|
|
|
1
1
|
import { resolveCourseId } from "../common/helpers.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
export const rubricTools = [
|
|
4
|
+
{
|
|
5
|
+
name: "canvas_list_rubrics",
|
|
6
|
+
tool: {
|
|
7
|
+
name: "canvas_list_rubrics",
|
|
8
|
+
description: "List all rubrics in a course",
|
|
9
|
+
inputSchema: {
|
|
10
|
+
type: "object",
|
|
11
|
+
properties: {
|
|
12
|
+
course_id: {
|
|
13
|
+
anyOf: [{ type: "number" }, { type: "string" }],
|
|
14
|
+
description: "The ID or name of the course"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
required: ["course_id"]
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
handler: async (client, args) => {
|
|
21
|
+
const input = z.object({
|
|
22
|
+
course_id: z.union([z.number(), z.string()])
|
|
23
|
+
}).parse(args);
|
|
24
|
+
const courseId = await resolveCourseId(client, input.course_id);
|
|
25
|
+
const rubrics = await client.getRubrics(courseId);
|
|
26
|
+
return {
|
|
27
|
+
content: [{ type: "text", text: JSON.stringify(rubrics, null, 2) }]
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: "canvas_get_rubric",
|
|
33
|
+
tool: {
|
|
34
|
+
name: "canvas_get_rubric",
|
|
35
|
+
description: "Get a specific rubric from a course, optionally including associations and assessments",
|
|
36
|
+
inputSchema: {
|
|
37
|
+
type: "object",
|
|
38
|
+
properties: {
|
|
39
|
+
course_id: {
|
|
40
|
+
anyOf: [{ type: "number" }, { type: "string" }],
|
|
41
|
+
description: "The ID or name of the course"
|
|
42
|
+
},
|
|
43
|
+
rubric_id: { type: "number", description: "The ID of the rubric" },
|
|
44
|
+
include: {
|
|
45
|
+
type: "array",
|
|
46
|
+
items: { type: "string", enum: ["assessments", "graded_assessments", "peer_assessments", "associations", "assignment_associations", "course_associations"] },
|
|
47
|
+
description: "Optional extra data to include"
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
required: ["course_id", "rubric_id"]
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
handler: async (client, args) => {
|
|
54
|
+
const input = z.object({
|
|
55
|
+
course_id: z.union([z.number(), z.string()]),
|
|
56
|
+
rubric_id: z.number(),
|
|
57
|
+
include: z.array(z.string()).optional()
|
|
58
|
+
}).parse(args);
|
|
59
|
+
const courseId = await resolveCourseId(client, input.course_id);
|
|
60
|
+
const rubric = await client.getRubric(courseId, input.rubric_id, input.include);
|
|
61
|
+
return {
|
|
62
|
+
content: [{ type: "text", text: JSON.stringify(rubric, null, 2) }]
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: "canvas_update_rubric",
|
|
68
|
+
tool: {
|
|
69
|
+
name: "canvas_update_rubric",
|
|
70
|
+
description: "Update an existing rubric in a course. You can change its title, criteria, ratings, and association settings.",
|
|
71
|
+
inputSchema: {
|
|
72
|
+
type: "object",
|
|
73
|
+
properties: {
|
|
74
|
+
course_id: {
|
|
75
|
+
anyOf: [{ type: "number" }, { type: "string" }],
|
|
76
|
+
description: "The ID or name of the course"
|
|
77
|
+
},
|
|
78
|
+
rubric_id: { type: "number", description: "The ID of the rubric to update" },
|
|
79
|
+
title: { type: "string", description: "New title for the rubric" },
|
|
80
|
+
criteria: {
|
|
81
|
+
type: "array",
|
|
82
|
+
description: "Updated list of criteria. Replaces all existing criteria.",
|
|
83
|
+
items: {
|
|
84
|
+
type: "object",
|
|
85
|
+
properties: {
|
|
86
|
+
description: { type: "string" },
|
|
87
|
+
long_description: { type: "string" },
|
|
88
|
+
points: { type: "number" },
|
|
89
|
+
ratings: {
|
|
90
|
+
type: "array",
|
|
91
|
+
items: {
|
|
92
|
+
type: "object",
|
|
93
|
+
properties: {
|
|
94
|
+
description: { type: "string" },
|
|
95
|
+
points: { type: "number" },
|
|
96
|
+
long_description: { type: "string" }
|
|
97
|
+
},
|
|
98
|
+
required: ["description", "points"]
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
required: ["description", "points", "ratings"]
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
association_id: { type: "number", description: "Optional Assignment ID to update the association" },
|
|
106
|
+
association_type: { type: "string", description: "Type of association, defaults to 'Assignment'" },
|
|
107
|
+
use_for_grading: { type: "boolean", description: "Whether to use the rubric for grading" },
|
|
108
|
+
purpose: { type: "string", description: "Purpose of association, e.g. 'grading'" }
|
|
109
|
+
},
|
|
110
|
+
required: ["course_id", "rubric_id"]
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
handler: async (client, args) => {
|
|
114
|
+
const input = z.object({
|
|
115
|
+
course_id: z.union([z.number(), z.string()]),
|
|
116
|
+
rubric_id: z.number(),
|
|
117
|
+
title: z.string().optional(),
|
|
118
|
+
criteria: z.array(z.object({
|
|
119
|
+
description: z.string(),
|
|
120
|
+
long_description: z.string().optional(),
|
|
121
|
+
points: z.number(),
|
|
122
|
+
ratings: z.array(z.object({
|
|
123
|
+
description: z.string(),
|
|
124
|
+
points: z.number(),
|
|
125
|
+
long_description: z.string().optional()
|
|
126
|
+
}))
|
|
127
|
+
})).optional(),
|
|
128
|
+
association_id: z.number().optional(),
|
|
129
|
+
association_type: z.string().optional().default("Assignment"),
|
|
130
|
+
use_for_grading: z.boolean().optional(),
|
|
131
|
+
purpose: z.string().optional().default("grading")
|
|
132
|
+
}).parse(args);
|
|
133
|
+
const courseId = await resolveCourseId(client, input.course_id);
|
|
134
|
+
const rubricData = {};
|
|
135
|
+
if (input.title)
|
|
136
|
+
rubricData.title = input.title;
|
|
137
|
+
if (input.criteria) {
|
|
138
|
+
const criteriaRecord = {};
|
|
139
|
+
input.criteria.forEach((c, index) => {
|
|
140
|
+
const ratingsRecord = {};
|
|
141
|
+
c.ratings.forEach((r, rIndex) => {
|
|
142
|
+
ratingsRecord[rIndex.toString()] = r;
|
|
143
|
+
});
|
|
144
|
+
criteriaRecord[index.toString()] = { ...c, ratings: ratingsRecord };
|
|
145
|
+
});
|
|
146
|
+
rubricData.criteria = criteriaRecord;
|
|
147
|
+
}
|
|
148
|
+
let associationData = undefined;
|
|
149
|
+
if (input.association_id) {
|
|
150
|
+
associationData = {
|
|
151
|
+
association_id: input.association_id,
|
|
152
|
+
association_type: input.association_type,
|
|
153
|
+
use_for_grading: input.use_for_grading,
|
|
154
|
+
purpose: input.purpose
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const rubric = await client.updateRubric(courseId, input.rubric_id, rubricData, associationData);
|
|
158
|
+
return {
|
|
159
|
+
content: [{ type: "text", text: JSON.stringify(rubric, null, 2) }]
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
},
|
|
4
163
|
{
|
|
5
164
|
name: "canvas_create_rubric",
|
|
6
165
|
tool: {
|
package/package.json
CHANGED
|
@@ -1,68 +1,70 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@charlie.act7/canvas-mcp-server",
|
|
3
|
-
"publishConfig": {
|
|
4
|
-
"access": "public"
|
|
5
|
-
},
|
|
6
|
-
"version": "1.
|
|
7
|
-
"description": "MCP Server for Canvas LMS - Use AI to interact with your courses, assignments, and grades.",
|
|
8
|
-
"main": "dist/index.js",
|
|
9
|
-
"bin": {
|
|
10
|
-
"canvas-mcp": "dist/index.js"
|
|
11
|
-
},
|
|
12
|
-
"type": "module",
|
|
13
|
-
"files": [
|
|
14
|
-
"dist/index.js",
|
|
15
|
-
"dist/http-server.js",
|
|
16
|
-
"dist/common",
|
|
17
|
-
"dist/prompts",
|
|
18
|
-
"dist/resources",
|
|
19
|
-
"dist/services",
|
|
20
|
-
"dist/tools",
|
|
21
|
-
"README.md",
|
|
22
|
-
"llms-install.md",
|
|
23
|
-
".claude-plugin",
|
|
24
|
-
".mcp.json"
|
|
25
|
-
],
|
|
26
|
-
"scripts": {
|
|
27
|
-
"build": "tsc",
|
|
28
|
-
"start": "node dist/index.js",
|
|
29
|
-
"start:http": "node dist/index.js serve-http",
|
|
30
|
-
"dev": "tsc --watch",
|
|
31
|
-
"chat": "tsx src/ollama_bridge.ts",
|
|
32
|
-
"prepublishOnly": "npm run build"
|
|
33
|
-
},
|
|
34
|
-
"keywords": [
|
|
35
|
-
"mcp",
|
|
36
|
-
"canvas",
|
|
37
|
-
"lms",
|
|
38
|
-
"ai",
|
|
39
|
-
"agent",
|
|
40
|
-
"server"
|
|
41
|
-
],
|
|
42
|
-
"author": "Charlie Cárdenas Toledo",
|
|
43
|
-
"license": "MIT",
|
|
44
|
-
"dependencies": {
|
|
45
|
-
"@fastify/swagger": "^9.5.2",
|
|
46
|
-
"@fastify/swagger-ui": "^5.2.3",
|
|
47
|
-
"@
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@charlie.act7/canvas-mcp-server",
|
|
3
|
+
"publishConfig": {
|
|
4
|
+
"access": "public"
|
|
5
|
+
},
|
|
6
|
+
"version": "1.2.0",
|
|
7
|
+
"description": "MCP Server for Canvas LMS - Use AI to interact with your courses, assignments, and grades.",
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"bin": {
|
|
10
|
+
"canvas-mcp": "dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"type": "module",
|
|
13
|
+
"files": [
|
|
14
|
+
"dist/index.js",
|
|
15
|
+
"dist/http-server.js",
|
|
16
|
+
"dist/common",
|
|
17
|
+
"dist/prompts",
|
|
18
|
+
"dist/resources",
|
|
19
|
+
"dist/services",
|
|
20
|
+
"dist/tools",
|
|
21
|
+
"README.md",
|
|
22
|
+
"llms-install.md",
|
|
23
|
+
".claude-plugin",
|
|
24
|
+
".mcp.json"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsc",
|
|
28
|
+
"start": "node dist/index.js",
|
|
29
|
+
"start:http": "node dist/index.js serve-http",
|
|
30
|
+
"dev": "tsc --watch",
|
|
31
|
+
"chat": "tsx src/ollama_bridge.ts",
|
|
32
|
+
"prepublishOnly": "npm run build"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"mcp",
|
|
36
|
+
"canvas",
|
|
37
|
+
"lms",
|
|
38
|
+
"ai",
|
|
39
|
+
"agent",
|
|
40
|
+
"server"
|
|
41
|
+
],
|
|
42
|
+
"author": "Charlie Cárdenas Toledo",
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@fastify/swagger": "^9.5.2",
|
|
46
|
+
"@fastify/swagger-ui": "^5.2.3",
|
|
47
|
+
"@google/genai": "^2.8.0",
|
|
48
|
+
"@modelcontextprotocol/sdk": "^1.25.3",
|
|
49
|
+
"axios": "^1.6.0",
|
|
50
|
+
"chalk": "^5.3.0",
|
|
51
|
+
"commander": "^11.1.0",
|
|
52
|
+
"conf": "^12.0.0",
|
|
53
|
+
"dotenv": "^16.3.1",
|
|
54
|
+
"fastify": "^5.6.1",
|
|
55
|
+
"form-data": "^4.0.5",
|
|
56
|
+
"inquirer": "^9.2.12",
|
|
57
|
+
"ollama": "^0.6.3",
|
|
58
|
+
"zod": "^3.22.4"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@types/inquirer": "^9.0.7",
|
|
62
|
+
"@types/node": "^20.10.0",
|
|
63
|
+
"nodemon": "^3.1.11",
|
|
64
|
+
"tsx": "^4.21.0",
|
|
65
|
+
"typescript": "^5.3.0"
|
|
66
|
+
},
|
|
67
|
+
"engines": {
|
|
68
|
+
"node": ">=18.0.0"
|
|
69
|
+
}
|
|
70
|
+
}
|