@charlie.act7/canvas-mcp-server 1.1.8 → 1.2.1
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/LICENSE +21 -0
- package/README.md +49 -22
- package/dist/http-server.js +139 -5
- package/dist/index.js +47 -6
- package/dist/services/agent-runner.js +214 -0
- package/dist/services/canvas-client.js +327 -11
- 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/module-tools.js +50 -7
- package/dist/tools/new-quiz-tools.js +357 -0
- package/dist/tools/peer-review-tools.js +130 -0
- package/dist/tools/quiz-tools.js +384 -3
- package/dist/tools/rubric-tools.js +159 -0
- package/package.json +74 -66
- package/.claude-plugin/plugin.json +0 -11
- package/.mcp.json +0 -10
package/dist/tools/quiz-tools.js
CHANGED
|
@@ -142,11 +142,378 @@ 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
|
+
},
|
|
440
|
+
{
|
|
441
|
+
name: "canvas_get_quiz_submissions",
|
|
442
|
+
tool: {
|
|
443
|
+
name: "canvas_get_quiz_submissions",
|
|
444
|
+
description: "Get all submissions for a quiz, including user info. Useful to see who has responded.",
|
|
445
|
+
inputSchema: {
|
|
446
|
+
type: "object",
|
|
447
|
+
properties: {
|
|
448
|
+
course_id: { anyOf: [{ type: "number" }, { type: "string" }], description: "The ID or name of the course" },
|
|
449
|
+
quiz_id: { type: "number", description: "The quiz ID" }
|
|
450
|
+
},
|
|
451
|
+
required: ["course_id", "quiz_id"]
|
|
452
|
+
}
|
|
453
|
+
},
|
|
454
|
+
handler: async (client, args) => {
|
|
455
|
+
const input = z.object({
|
|
456
|
+
course_id: z.union([z.number(), z.string()]),
|
|
457
|
+
quiz_id: z.number()
|
|
458
|
+
}).parse(args);
|
|
459
|
+
const courseId = await resolveCourseId(client, input.course_id);
|
|
460
|
+
const submissions = await client.getQuizSubmissions(courseId, input.quiz_id);
|
|
461
|
+
return { content: [{ type: "text", text: JSON.stringify(submissions, null, 2) }] };
|
|
462
|
+
}
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
name: "canvas_check_quiz_pending",
|
|
466
|
+
tool: {
|
|
467
|
+
name: "canvas_check_quiz_pending",
|
|
468
|
+
description: "Check which enrolled students have NOT submitted a quiz. Returns a list of pending students with their name and email. Excludes test accounts.",
|
|
469
|
+
inputSchema: {
|
|
470
|
+
type: "object",
|
|
471
|
+
properties: {
|
|
472
|
+
course_id: { anyOf: [{ type: "number" }, { type: "string" }], description: "The ID or name of the course" },
|
|
473
|
+
quiz_id: { type: "number", description: "The quiz ID" }
|
|
474
|
+
},
|
|
475
|
+
required: ["course_id", "quiz_id"]
|
|
476
|
+
}
|
|
477
|
+
},
|
|
478
|
+
handler: async (client, args) => {
|
|
479
|
+
const input = z.object({
|
|
480
|
+
course_id: z.union([z.number(), z.string()]),
|
|
481
|
+
quiz_id: z.number()
|
|
482
|
+
}).parse(args);
|
|
483
|
+
const courseId = await resolveCourseId(client, input.course_id);
|
|
484
|
+
const result = await client.checkQuizPending(courseId, input.quiz_id);
|
|
485
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
486
|
+
}
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
name: "canvas_delete_quiz",
|
|
490
|
+
tool: {
|
|
491
|
+
name: "canvas_delete_quiz",
|
|
492
|
+
description: "Permanently delete a classic quiz from a course. This cannot be undone.",
|
|
493
|
+
inputSchema: {
|
|
494
|
+
type: "object",
|
|
495
|
+
properties: {
|
|
496
|
+
course_id: { anyOf: [{ type: "number" }, { type: "string" }], description: "The ID or name of the course" },
|
|
497
|
+
quiz_id: { type: "number", description: "The quiz ID to delete" }
|
|
498
|
+
},
|
|
499
|
+
required: ["course_id", "quiz_id"]
|
|
500
|
+
}
|
|
501
|
+
},
|
|
502
|
+
handler: async (client, args) => {
|
|
503
|
+
const input = z.object({
|
|
504
|
+
course_id: z.union([z.number(), z.string()]),
|
|
505
|
+
quiz_id: z.number()
|
|
506
|
+
}).parse(args);
|
|
507
|
+
const courseId = await resolveCourseId(client, input.course_id);
|
|
508
|
+
const result = await client.deleteQuiz(courseId, input.quiz_id);
|
|
509
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
510
|
+
}
|
|
511
|
+
},
|
|
145
512
|
{
|
|
146
513
|
name: "canvas_update_quiz",
|
|
147
514
|
tool: {
|
|
148
515
|
name: "canvas_update_quiz",
|
|
149
|
-
description: "Update an existing quiz",
|
|
516
|
+
description: "Update an existing quiz. Can change title, description, time limit, attempts, dates, shuffle, and publish state.",
|
|
150
517
|
inputSchema: {
|
|
151
518
|
type: "object",
|
|
152
519
|
properties: {
|
|
@@ -154,10 +521,17 @@ export const quizTools = [
|
|
|
154
521
|
quiz_id: { type: "number" },
|
|
155
522
|
title: { type: "string" },
|
|
156
523
|
description: { type: "string" },
|
|
524
|
+
quiz_type: { type: "string", enum: ["practice_quiz", "assignment", "graded_survey", "survey"] },
|
|
525
|
+
time_limit: { type: "number", description: "Time limit in minutes. Use null to remove limit.", nullable: true },
|
|
526
|
+
shuffle_answers: { type: "boolean" },
|
|
527
|
+
allowed_attempts: { type: "number", description: "Number of allowed attempts. Use -1 for unlimited." },
|
|
528
|
+
due_at: { type: "string", description: "ISO-8601 due date. Use null to clear.", nullable: true },
|
|
529
|
+
unlock_at: { type: "string", description: "ISO-8601 unlock date. Use null to clear.", nullable: true },
|
|
530
|
+
lock_at: { type: "string", description: "ISO-8601 lock date. Use null to clear.", nullable: true },
|
|
157
531
|
published: { type: "boolean" },
|
|
158
532
|
require_lockdown_browser: { type: "boolean" },
|
|
159
533
|
show_correct_answers: { type: "boolean" },
|
|
160
|
-
show_correct_answers_at: { type: "string", description: "ISO-8601 date to show correct answers" }
|
|
534
|
+
show_correct_answers_at: { type: "string", description: "ISO-8601 date to show correct answers", nullable: true }
|
|
161
535
|
},
|
|
162
536
|
required: ["course_id", "quiz_id"]
|
|
163
537
|
}
|
|
@@ -168,10 +542,17 @@ export const quizTools = [
|
|
|
168
542
|
quiz_id: z.number(),
|
|
169
543
|
title: z.string().optional(),
|
|
170
544
|
description: z.string().optional(),
|
|
545
|
+
quiz_type: z.enum(["practice_quiz", "assignment", "graded_survey", "survey"]).optional(),
|
|
546
|
+
time_limit: z.number().nullable().optional(),
|
|
547
|
+
shuffle_answers: z.boolean().optional(),
|
|
548
|
+
allowed_attempts: z.number().optional(),
|
|
549
|
+
due_at: z.string().nullable().optional(),
|
|
550
|
+
unlock_at: z.string().nullable().optional(),
|
|
551
|
+
lock_at: z.string().nullable().optional(),
|
|
171
552
|
published: z.boolean().optional(),
|
|
172
553
|
require_lockdown_browser: z.boolean().optional(),
|
|
173
554
|
show_correct_answers: z.boolean().optional(),
|
|
174
|
-
show_correct_answers_at: z.string().optional()
|
|
555
|
+
show_correct_answers_at: z.string().nullable().optional()
|
|
175
556
|
}).parse(args);
|
|
176
557
|
const courseId = await resolveCourseId(client, input.course_id);
|
|
177
558
|
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: {
|