@charlie.act7/canvas-mcp-server 1.2.0 → 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/dist/index.js CHANGED
@@ -35,6 +35,16 @@ import { startHttpServer } from "./http-server.js";
35
35
  dotenv.config();
36
36
  const configManager = new ConfigManager();
37
37
  const program = new Command();
38
+ function uniqueTools(tools) {
39
+ const seen = new Set();
40
+ return tools.filter((tool) => {
41
+ if (seen.has(tool.name)) {
42
+ return false;
43
+ }
44
+ seen.add(tool.name);
45
+ return true;
46
+ });
47
+ }
38
48
  program
39
49
  .name("canvas-mcp")
40
50
  .description("MCP Server for Canvas LMS (Refactored)")
@@ -96,7 +106,7 @@ program
96
106
  },
97
107
  });
98
108
  // --- Aggregation ---
99
- const allTools = [
109
+ const allTools = uniqueTools([
100
110
  ...courseTools,
101
111
  ...assignmentTools,
102
112
  ...quizTools,
@@ -116,7 +126,7 @@ program
116
126
  ...newQuizTools,
117
127
  ...analyticsTools,
118
128
  ...peerReviewTools
119
- ];
129
+ ]);
120
130
  // --- Tool Handlers ---
121
131
  server.setRequestHandler(ListToolsRequestSchema, async () => {
122
132
  return {
@@ -173,7 +183,7 @@ program
173
183
  .action(async (options) => {
174
184
  const client = getClient();
175
185
  const port = Number.parseInt(options.port, 10);
176
- const allTools = [
186
+ const allTools = uniqueTools([
177
187
  ...courseTools,
178
188
  ...assignmentTools,
179
189
  ...quizTools,
@@ -193,7 +203,7 @@ program
193
203
  ...newQuizTools,
194
204
  ...analyticsTools,
195
205
  ...peerReviewTools
196
- ];
206
+ ]);
197
207
  await startHttpServer(client, options.host, port, allTools);
198
208
  });
199
209
  program.parse(process.argv);
@@ -273,9 +273,38 @@ export class CanvasClient {
273
273
  return response.data;
274
274
  }
275
275
  async updateModule(courseId, moduleId, data) {
276
- const response = await this.client.put(`courses/${courseId}/modules/${moduleId}`, {
277
- module: data
278
- });
276
+ // Canvas only accepts arrays (prerequisite_module_ids, completion_requirements)
277
+ // via form-encoding, not JSON.
278
+ const hasArrays = data.prerequisite_module_ids !== undefined || data.completion_requirements !== undefined;
279
+ if (hasArrays) {
280
+ const params = new URLSearchParams();
281
+ if (data.name !== undefined)
282
+ params.set('module[name]', data.name);
283
+ if (data.published !== undefined)
284
+ params.set('module[published]', String(data.published));
285
+ if (data.position !== undefined)
286
+ params.set('module[position]', String(data.position));
287
+ if (data.require_sequential_progress !== undefined) {
288
+ params.set('module[require_sequential_progress]', String(data.require_sequential_progress));
289
+ }
290
+ if (data.prerequisite_module_ids) {
291
+ for (const id of data.prerequisite_module_ids) {
292
+ params.append('module[prerequisite_module_ids][]', String(id));
293
+ }
294
+ }
295
+ if (data.completion_requirements) {
296
+ for (const req of data.completion_requirements) {
297
+ params.append('module[completion_requirements][][id]', String(req.id));
298
+ params.append('module[completion_requirements][][type]', req.type);
299
+ if (req.min_score !== undefined) {
300
+ params.append('module[completion_requirements][][min_score]', String(req.min_score));
301
+ }
302
+ }
303
+ }
304
+ const response = await this.client.put(`courses/${courseId}/modules/${moduleId}`, params, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } });
305
+ return response.data;
306
+ }
307
+ const response = await this.client.put(`courses/${courseId}/modules/${moduleId}`, { module: data });
279
308
  return response.data;
280
309
  }
281
310
  async deleteModule(courseId, moduleId) {
@@ -290,9 +319,20 @@ export class CanvasClient {
290
319
  return response.data;
291
320
  }
292
321
  async updateModuleItem(courseId, moduleId, itemId, data) {
293
- const response = await this.client.put(`courses/${courseId}/modules/${moduleId}/items/${itemId}`, {
294
- module_item: data
295
- });
322
+ // Canvas only accepts completion_requirement via form-encoding, not JSON.
323
+ // Use URLSearchParams when completion_requirement is present.
324
+ if (data.completion_requirement) {
325
+ const params = new URLSearchParams();
326
+ params.set('module_item[completion_requirement][type]', data.completion_requirement.type);
327
+ const { completion_requirement, ...rest } = data;
328
+ for (const [key, value] of Object.entries(rest)) {
329
+ if (value !== undefined)
330
+ params.set(`module_item[${key}]`, String(value));
331
+ }
332
+ const response = await this.client.put(`courses/${courseId}/modules/${moduleId}/items/${itemId}`, params, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } });
333
+ return response.data;
334
+ }
335
+ const response = await this.client.put(`courses/${courseId}/modules/${moduleId}/items/${itemId}`, { module_item: data });
296
336
  return response.data;
297
337
  }
298
338
  async deleteModuleItem(courseId, moduleId, itemId) {
@@ -333,6 +373,39 @@ export class CanvasClient {
333
373
  }
334
374
  return uploadResponse.data;
335
375
  }
376
+ async getQuizSubmissions(courseId, quizId) {
377
+ const response = await this.client.get(`courses/${courseId}/quizzes/${quizId}/submissions`, {
378
+ params: { include: ['user'], per_page: 100 }
379
+ });
380
+ return response.data.quiz_submissions ?? [];
381
+ }
382
+ async deleteQuiz(courseId, quizId) {
383
+ await this.client.delete(`courses/${courseId}/quizzes/${quizId}`);
384
+ return { deleted: true };
385
+ }
386
+ async checkQuizPending(courseId, quizId) {
387
+ const [students, submissionsResp] = await Promise.all([
388
+ this.getEnrollments(courseId),
389
+ this.client.get(`courses/${courseId}/quizzes/${quizId}/submissions`, {
390
+ params: { per_page: 100 }
391
+ })
392
+ ]);
393
+ const realStudents = students.filter(u => u.name !== 'Estudiante de prueba' && u.sis_user_id !== null);
394
+ const submittedIds = new Set((submissionsResp.data.quiz_submissions ?? []).map((s) => s.user_id));
395
+ const pending = realStudents
396
+ .filter(u => !submittedIds.has(u.id))
397
+ .map(u => ({
398
+ id: u.id,
399
+ name: u.name ?? 'Desconocido',
400
+ email: u.login_id ?? ''
401
+ }));
402
+ return {
403
+ quiz_id: quizId,
404
+ total_students: realStudents.length,
405
+ submitted: submittedIds.size,
406
+ pending
407
+ };
408
+ }
336
409
  async createQuiz(courseId, quiz) {
337
410
  const response = await this.client.post(`courses/${courseId}/quizzes`, {
338
411
  quiz: quiz
@@ -63,7 +63,7 @@ export const moduleTools = [
63
63
  name: "canvas_update_module",
64
64
  tool: {
65
65
  name: "canvas_update_module",
66
- description: "Update an existing module",
66
+ description: "Update an existing module (name, published, position, prerequisites, completion requirements)",
67
67
  inputSchema: {
68
68
  type: "object",
69
69
  properties: {
@@ -74,7 +74,32 @@ export const moduleTools = [
74
74
  module_id: { type: "number", description: "The ID of the module" },
75
75
  name: { type: "string", description: "The new name of the module" },
76
76
  published: { type: "boolean", description: "Whether the module is published" },
77
- position: { type: "number", description: "The new position of the module" }
77
+ position: { type: "number", description: "The new position of the module" },
78
+ prerequisite_module_ids: {
79
+ type: "array",
80
+ items: { type: "number" },
81
+ description: "IDs of modules that must be completed before this one unlocks"
82
+ },
83
+ require_sequential_progress: {
84
+ type: "boolean",
85
+ description: "Whether items in the module must be completed in order"
86
+ },
87
+ completion_requirements: {
88
+ type: "array",
89
+ description: "Completion requirements for module items. Replaces ALL existing requirements — include every item that needs one.",
90
+ items: {
91
+ type: "object",
92
+ properties: {
93
+ id: { type: "number", description: "The module item ID" },
94
+ type: {
95
+ type: "string",
96
+ description: "must_view, must_submit, must_contribute, min_score, must_mark_done"
97
+ },
98
+ min_score: { type: "number", description: "Required when type is min_score" }
99
+ },
100
+ required: ["id", "type"]
101
+ }
102
+ }
78
103
  },
79
104
  required: ["course_id", "module_id"],
80
105
  },
@@ -85,13 +110,23 @@ export const moduleTools = [
85
110
  module_id: z.number(),
86
111
  name: z.string().optional(),
87
112
  published: z.boolean().optional(),
88
- position: z.number().optional()
113
+ position: z.number().optional(),
114
+ prerequisite_module_ids: z.array(z.number()).optional(),
115
+ require_sequential_progress: z.boolean().optional(),
116
+ completion_requirements: z.array(z.object({
117
+ id: z.number(),
118
+ type: z.string(),
119
+ min_score: z.number().optional()
120
+ })).optional()
89
121
  }).parse(args);
90
122
  const courseId = await resolveCourseId(client, input.course_id);
91
123
  const module = await client.updateModule(courseId, input.module_id, {
92
124
  name: input.name,
93
125
  published: input.published,
94
- position: input.position
126
+ position: input.position,
127
+ prerequisite_module_ids: input.prerequisite_module_ids,
128
+ require_sequential_progress: input.require_sequential_progress,
129
+ completion_requirements: input.completion_requirements
95
130
  });
96
131
  return {
97
132
  content: [{ type: "text", text: JSON.stringify(module, null, 2) }],
@@ -206,7 +241,11 @@ export const moduleTools = [
206
241
  position: { type: "number", description: "The new position of the item" },
207
242
  indent: { type: "number", description: "The new level of indentation (0-3)" },
208
243
  published: { type: "boolean", description: "Whether the item is published" },
209
- new_module_id: { type: "number", description: "Move the item to a new module" }
244
+ new_module_id: { type: "number", description: "Move the item to a new module" },
245
+ completion_requirement_type: {
246
+ type: "string",
247
+ description: "Completion requirement type: must_view, must_submit, must_contribute, min_score, must_mark_done"
248
+ }
210
249
  },
211
250
  required: ["course_id", "module_id", "item_id"],
212
251
  },
@@ -220,7 +259,8 @@ export const moduleTools = [
220
259
  position: z.number().optional(),
221
260
  indent: z.number().optional(),
222
261
  published: z.boolean().optional(),
223
- new_module_id: z.number().optional()
262
+ new_module_id: z.number().optional(),
263
+ completion_requirement_type: z.string().optional()
224
264
  }).parse(args);
225
265
  const courseId = await resolveCourseId(client, input.course_id);
226
266
  const result = await client.updateModuleItem(courseId, input.module_id, input.item_id, {
@@ -228,7 +268,10 @@ export const moduleTools = [
228
268
  position: input.position,
229
269
  indent: input.indent,
230
270
  published: input.published,
231
- module_id: input.new_module_id
271
+ module_id: input.new_module_id,
272
+ ...(input.completion_requirement_type && {
273
+ completion_requirement: { type: input.completion_requirement_type }
274
+ })
232
275
  });
233
276
  return {
234
277
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
@@ -0,0 +1,238 @@
1
+ import { resolveCourseId } from "../common/helpers.js";
2
+ import { z } from "zod";
3
+ export const questionBankTools = [
4
+ {
5
+ name: "canvas_list_question_banks",
6
+ tool: {
7
+ name: "canvas_list_question_banks",
8
+ description: "List question banks for a specific 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({ course_id: z.union([z.number(), z.string()]) }).parse(args);
22
+ const courseId = await resolveCourseId(client, input.course_id);
23
+ const banks = await client.listQuestionBanks(courseId);
24
+ return {
25
+ content: [{ type: "text", text: JSON.stringify(banks, null, 2) }]
26
+ };
27
+ }
28
+ },
29
+ {
30
+ name: "canvas_create_question_bank",
31
+ tool: {
32
+ name: "canvas_create_question_bank",
33
+ description: "Create a new question bank in a course",
34
+ inputSchema: {
35
+ type: "object",
36
+ properties: {
37
+ course_id: {
38
+ anyOf: [{ type: "number" }, { type: "string" }],
39
+ description: "The ID or name of the course"
40
+ },
41
+ title: {
42
+ type: "string",
43
+ description: "Title for the question bank"
44
+ }
45
+ },
46
+ required: ["course_id", "title"]
47
+ }
48
+ },
49
+ handler: async (client, args) => {
50
+ const input = z.object({
51
+ course_id: z.union([z.number(), z.string()]),
52
+ title: z.string().min(1)
53
+ }).parse(args);
54
+ const courseId = await resolveCourseId(client, input.course_id);
55
+ const bank = await client.createQuestionBank(courseId, input.title);
56
+ return {
57
+ content: [{ type: "text", text: JSON.stringify(bank, null, 2) }]
58
+ };
59
+ }
60
+ },
61
+ {
62
+ name: "canvas_create_assessment_question",
63
+ tool: {
64
+ name: "canvas_create_assessment_question",
65
+ description: "Create a question in a question bank. Supports types: multiple_choice_question, true_false_question, essay_question, short_answer_question, fill_in_multiple_blanks_question, multiple_answers_question, matching_question, numerical_question.",
66
+ inputSchema: {
67
+ type: "object",
68
+ properties: {
69
+ course_id: {
70
+ anyOf: [{ type: "number" }, { type: "string" }],
71
+ description: "The ID or name of the course"
72
+ },
73
+ bank_id: {
74
+ type: "number",
75
+ description: "The question bank ID"
76
+ },
77
+ name: {
78
+ type: "string",
79
+ description: "Short name/title for the question"
80
+ },
81
+ question_type: {
82
+ type: "string",
83
+ description: "Question type (e.g. multiple_choice_question, true_false_question, essay_question, short_answer_question)"
84
+ },
85
+ question_text: {
86
+ type: "string",
87
+ description: "The question text (HTML supported)"
88
+ },
89
+ points_possible: {
90
+ type: "number",
91
+ description: "Point value for the question"
92
+ },
93
+ answers: {
94
+ type: "array",
95
+ description: "Array of answer objects. For multiple_choice: weight=100 for correct, weight=0 for incorrect. For true_false: text='True'/'False'.",
96
+ items: {
97
+ type: "object",
98
+ properties: {
99
+ text: { type: "string", description: "Answer text" },
100
+ weight: { type: "number", description: "100 for correct, 0 for incorrect" },
101
+ comments: { type: "string", description: "Optional feedback comment" }
102
+ },
103
+ required: ["text", "weight"]
104
+ }
105
+ }
106
+ },
107
+ required: ["course_id", "bank_id", "name", "question_type", "question_text", "points_possible"]
108
+ }
109
+ },
110
+ handler: async (client, args) => {
111
+ const answerSchema = z.object({
112
+ text: z.string(),
113
+ weight: z.number(),
114
+ comments: z.string().optional()
115
+ });
116
+ const input = z.object({
117
+ course_id: z.union([z.number(), z.string()]),
118
+ bank_id: z.coerce.number(),
119
+ name: z.string().min(1),
120
+ question_type: z.string().min(1),
121
+ question_text: z.string().min(1),
122
+ points_possible: z.coerce.number(),
123
+ answers: z.array(answerSchema).optional()
124
+ }).parse(args);
125
+ const courseId = await resolveCourseId(client, input.course_id);
126
+ const question = await client.createAssessmentQuestion(courseId, input.bank_id, {
127
+ name: input.name,
128
+ question_type: input.question_type,
129
+ question_text: input.question_text,
130
+ points_possible: input.points_possible,
131
+ answers: input.answers
132
+ });
133
+ return {
134
+ content: [{ type: "text", text: JSON.stringify(question, null, 2) }]
135
+ };
136
+ }
137
+ },
138
+ {
139
+ name: "canvas_move_question_bank_questions",
140
+ tool: {
141
+ name: "canvas_move_question_bank_questions",
142
+ description: "Move questions from one question bank to another. If question_ids is omitted, all questions are moved.",
143
+ inputSchema: {
144
+ type: "object",
145
+ properties: {
146
+ course_id: {
147
+ anyOf: [{ type: "number" }, { type: "string" }],
148
+ description: "The ID or name of the course"
149
+ },
150
+ source_bank_id: {
151
+ type: "number",
152
+ description: "Source question bank ID"
153
+ },
154
+ dest_bank_id: {
155
+ type: "number",
156
+ description: "Destination question bank ID"
157
+ },
158
+ question_ids: {
159
+ type: "array",
160
+ items: { type: "number" },
161
+ description: "Optional list of specific question IDs to move. If omitted, all questions are moved."
162
+ }
163
+ },
164
+ required: ["course_id", "source_bank_id", "dest_bank_id"]
165
+ }
166
+ },
167
+ handler: async (client, args) => {
168
+ const input = z.object({
169
+ course_id: z.union([z.number(), z.string()]),
170
+ source_bank_id: z.coerce.number(),
171
+ dest_bank_id: z.coerce.number(),
172
+ question_ids: z.array(z.coerce.number()).optional()
173
+ }).parse(args);
174
+ const courseId = await resolveCourseId(client, input.course_id);
175
+ const result = await client.moveQuestionBankQuestions(courseId, input.source_bank_id, input.dest_bank_id, input.question_ids);
176
+ return {
177
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
178
+ };
179
+ }
180
+ },
181
+ {
182
+ name: "canvas_create_quiz_group",
183
+ tool: {
184
+ name: "canvas_create_quiz_group",
185
+ description: "Create a quiz group linked to a question bank. The group randomly picks questions from the bank when students take the quiz.",
186
+ inputSchema: {
187
+ type: "object",
188
+ properties: {
189
+ course_id: {
190
+ anyOf: [{ type: "number" }, { type: "string" }],
191
+ description: "The ID or name of the course"
192
+ },
193
+ quiz_id: {
194
+ type: "number",
195
+ description: "The quiz ID"
196
+ },
197
+ name: {
198
+ type: "string",
199
+ description: "Name for the quiz group"
200
+ },
201
+ pick_count: {
202
+ type: "number",
203
+ description: "Number of questions to randomly pick from the bank"
204
+ },
205
+ question_points: {
206
+ type: "number",
207
+ description: "Points per question in this group"
208
+ },
209
+ assessment_question_bank_id: {
210
+ type: "number",
211
+ description: "The question bank ID to link to this group"
212
+ }
213
+ },
214
+ required: ["course_id", "quiz_id", "name", "pick_count", "question_points"]
215
+ }
216
+ },
217
+ handler: async (client, args) => {
218
+ const input = z.object({
219
+ course_id: z.union([z.number(), z.string()]),
220
+ quiz_id: z.coerce.number(),
221
+ name: z.string().min(1),
222
+ pick_count: z.coerce.number().min(1),
223
+ question_points: z.coerce.number().min(0),
224
+ assessment_question_bank_id: z.coerce.number().optional()
225
+ }).parse(args);
226
+ const courseId = await resolveCourseId(client, input.course_id);
227
+ const group = await client.createQuizGroup(courseId, input.quiz_id, {
228
+ name: input.name,
229
+ pick_count: input.pick_count,
230
+ question_points: input.question_points,
231
+ assessment_question_bank_id: input.assessment_question_bank_id
232
+ });
233
+ return {
234
+ content: [{ type: "text", text: JSON.stringify(group, null, 2) }]
235
+ };
236
+ }
237
+ }
238
+ ];
@@ -437,6 +437,78 @@ export const quizTools = [
437
437
  return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
438
438
  }
439
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
+ },
440
512
  {
441
513
  name: "canvas_update_quiz",
442
514
  tool: {
package/package.json CHANGED
@@ -1,70 +1,76 @@
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
- }
1
+ {
2
+ "name": "@charlie.act7/canvas-mcp-server",
3
+ "repository": {
4
+ "type": "git",
5
+ "url": "git+https://github.com/CharlieCardenasToledo/mcp-canvas-server.git"
6
+ },
7
+ "homepage": "https://github.com/CharlieCardenasToledo/mcp-canvas-server#readme",
8
+ "bugs": {
9
+ "url": "https://github.com/CharlieCardenasToledo/mcp-canvas-server/issues"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "version": "1.2.1",
15
+ "description": "MCP Server for Canvas LMS - Use AI to interact with your courses, assignments, and grades.",
16
+ "main": "dist/index.js",
17
+ "bin": {
18
+ "canvas-mcp": "dist/index.js"
19
+ },
20
+ "type": "module",
21
+ "files": [
22
+ "dist/index.js",
23
+ "dist/http-server.js",
24
+ "dist/common",
25
+ "dist/prompts",
26
+ "dist/resources",
27
+ "dist/services",
28
+ "dist/tools",
29
+ "README.md",
30
+ "llms-install.md"
31
+ ],
32
+ "scripts": {
33
+ "build": "tsc",
34
+ "start": "node dist/index.js",
35
+ "start:http": "node dist/index.js serve-http",
36
+ "dev": "tsc --watch",
37
+ "chat": "tsx src/ollama_bridge.ts",
38
+ "prepublishOnly": "npm run build"
39
+ },
40
+ "keywords": [
41
+ "mcp",
42
+ "canvas",
43
+ "lms",
44
+ "ai",
45
+ "agent",
46
+ "server"
47
+ ],
48
+ "author": "Charlie Cárdenas Toledo",
49
+ "license": "MIT",
50
+ "dependencies": {
51
+ "@fastify/swagger": "^9.5.2",
52
+ "@fastify/swagger-ui": "^5.2.3",
53
+ "@google/genai": "^2.8.0",
54
+ "@modelcontextprotocol/sdk": "^1.25.3",
55
+ "axios": "^1.6.0",
56
+ "chalk": "^5.3.0",
57
+ "commander": "^11.1.0",
58
+ "conf": "^12.0.0",
59
+ "dotenv": "^16.3.1",
60
+ "fastify": "^5.6.1",
61
+ "form-data": "^4.0.5",
62
+ "inquirer": "^9.2.12",
63
+ "ollama": "^0.6.3",
64
+ "zod": "^3.22.4"
65
+ },
66
+ "devDependencies": {
67
+ "@types/inquirer": "^9.0.7",
68
+ "@types/node": "^20.10.0",
69
+ "nodemon": "^3.1.11",
70
+ "tsx": "^4.21.0",
71
+ "typescript": "^5.3.0"
72
+ },
73
+ "engines": {
74
+ "node": ">=18.0.0"
75
+ }
76
+ }
@@ -1,11 +0,0 @@
1
- {
2
- "name": "canvas-lms",
3
- "description": "Canvas LMS integration for teachers. Manage courses, assignments, grades, quizzes, students, modules, and more — directly from Claude Code.",
4
- "version": "1.1.0",
5
- "author": {
6
- "name": "CharlieCardenasToledo"
7
- },
8
- "homepage": "https://github.com/CharlieCardenasToledo/canvas-mcp-server",
9
- "repository": "https://github.com/CharlieCardenasToledo/canvas-mcp-server",
10
- "license": "MIT"
11
- }
package/.mcp.json DELETED
@@ -1,12 +0,0 @@
1
- {
2
- "mcpServers": {
3
- "canvas": {
4
- "command": "npx",
5
- "args": ["-y", "@charlie.act7/canvas-mcp-server"],
6
- "env": {
7
- "CANVAS_API_TOKEN": "${CANVAS_API_TOKEN}",
8
- "CANVAS_API_DOMAIN": "${CANVAS_API_DOMAIN}"
9
- }
10
- }
11
- }
12
- }