@charlie.act7/canvas-mcp-server 1.1.3 → 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.es.md +46 -26
- package/README.md +93 -46
- 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
|
@@ -95,6 +95,148 @@ export const courseTools = [
|
|
|
95
95
|
};
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
|
+
{
|
|
99
|
+
name: "canvas_create_course",
|
|
100
|
+
tool: {
|
|
101
|
+
name: "canvas_create_course",
|
|
102
|
+
description: "Create a new course in a Canvas account",
|
|
103
|
+
inputSchema: {
|
|
104
|
+
type: "object",
|
|
105
|
+
properties: {
|
|
106
|
+
account_id: {
|
|
107
|
+
anyOf: [{ type: "number" }, { type: "string" }],
|
|
108
|
+
description: "Account ID where the course will be created (use 'self' or your institution's account ID)"
|
|
109
|
+
},
|
|
110
|
+
name: { type: "string", description: "Course name" },
|
|
111
|
+
course_code: { type: "string", description: "Short course code (e.g. MAT101)" },
|
|
112
|
+
start_at: { type: "string", description: "Start date (ISO 8601)" },
|
|
113
|
+
end_at: { type: "string", description: "End date (ISO 8601)" },
|
|
114
|
+
syllabus_body: { type: "string", description: "HTML content for the syllabus" },
|
|
115
|
+
time_zone: { type: "string", description: "Time zone (e.g. America/Guayaquil)" },
|
|
116
|
+
locale: { type: "string", description: "Locale (e.g. es, en)" }
|
|
117
|
+
},
|
|
118
|
+
required: ["account_id", "name"],
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
handler: async (client, args) => {
|
|
122
|
+
const input = z.object({
|
|
123
|
+
account_id: z.union([z.number(), z.string()]),
|
|
124
|
+
name: z.string(),
|
|
125
|
+
course_code: z.string().optional(),
|
|
126
|
+
start_at: z.string().optional(),
|
|
127
|
+
end_at: z.string().optional(),
|
|
128
|
+
syllabus_body: z.string().optional(),
|
|
129
|
+
time_zone: z.string().optional(),
|
|
130
|
+
locale: z.string().optional()
|
|
131
|
+
}).parse(args);
|
|
132
|
+
const { account_id, ...courseData } = input;
|
|
133
|
+
const result = await client.createCourse(account_id, courseData);
|
|
134
|
+
return {
|
|
135
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: "canvas_update_course",
|
|
141
|
+
tool: {
|
|
142
|
+
name: "canvas_update_course",
|
|
143
|
+
description: "Update an existing course's settings",
|
|
144
|
+
inputSchema: {
|
|
145
|
+
type: "object",
|
|
146
|
+
properties: {
|
|
147
|
+
course_id: {
|
|
148
|
+
anyOf: [{ type: "number" }, { type: "string" }],
|
|
149
|
+
description: "The ID or name of the course"
|
|
150
|
+
},
|
|
151
|
+
name: { type: "string", description: "New course name" },
|
|
152
|
+
course_code: { type: "string", description: "New course code" },
|
|
153
|
+
start_at: { type: "string", description: "Start date (ISO 8601)" },
|
|
154
|
+
end_at: { type: "string", description: "End date (ISO 8601)" },
|
|
155
|
+
syllabus_body: { type: "string", description: "HTML content for the syllabus" },
|
|
156
|
+
time_zone: { type: "string", description: "Time zone" },
|
|
157
|
+
locale: { type: "string", description: "Locale (e.g. es, en)" },
|
|
158
|
+
default_view: {
|
|
159
|
+
type: "string",
|
|
160
|
+
description: "Default course home view: feed, wiki, modules, assignments, syllabus"
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
required: ["course_id"],
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
handler: async (client, args) => {
|
|
167
|
+
const input = z.object({
|
|
168
|
+
course_id: z.union([z.number(), z.string()]),
|
|
169
|
+
name: z.string().optional(),
|
|
170
|
+
course_code: z.string().optional(),
|
|
171
|
+
start_at: z.string().optional(),
|
|
172
|
+
end_at: z.string().optional(),
|
|
173
|
+
syllabus_body: z.string().optional(),
|
|
174
|
+
time_zone: z.string().optional(),
|
|
175
|
+
locale: z.string().optional(),
|
|
176
|
+
default_view: z.string().optional()
|
|
177
|
+
}).parse(args);
|
|
178
|
+
const courseId = await resolveCourseId(client, input.course_id);
|
|
179
|
+
const { course_id: _, ...updateData } = input;
|
|
180
|
+
const result = await client.updateCourse(courseId, updateData);
|
|
181
|
+
return {
|
|
182
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
name: "canvas_get_syllabus",
|
|
188
|
+
tool: {
|
|
189
|
+
name: "canvas_get_syllabus",
|
|
190
|
+
description: "Get the syllabus content of a course",
|
|
191
|
+
inputSchema: {
|
|
192
|
+
type: "object",
|
|
193
|
+
properties: {
|
|
194
|
+
course_id: {
|
|
195
|
+
anyOf: [{ type: "number" }, { type: "string" }],
|
|
196
|
+
description: "The ID or name of the course"
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
required: ["course_id"],
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
handler: async (client, args) => {
|
|
203
|
+
const input = z.object({ course_id: z.union([z.number(), z.string()]) }).parse(args);
|
|
204
|
+
const courseId = await resolveCourseId(client, input.course_id);
|
|
205
|
+
const result = await client.getSyllabus(courseId);
|
|
206
|
+
return {
|
|
207
|
+
content: [{ type: "text", text: result.syllabus_body || "(No syllabus content)" }],
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
name: "canvas_delete_page",
|
|
213
|
+
tool: {
|
|
214
|
+
name: "canvas_delete_page",
|
|
215
|
+
description: "Delete a page from a course",
|
|
216
|
+
inputSchema: {
|
|
217
|
+
type: "object",
|
|
218
|
+
properties: {
|
|
219
|
+
course_id: {
|
|
220
|
+
anyOf: [{ type: "number" }, { type: "string" }],
|
|
221
|
+
description: "The ID or name of the course"
|
|
222
|
+
},
|
|
223
|
+
page_id: { type: "string", description: "The ID or URL-slug of the page to delete" }
|
|
224
|
+
},
|
|
225
|
+
required: ["course_id", "page_id"],
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
handler: async (client, args) => {
|
|
229
|
+
const input = z.object({
|
|
230
|
+
course_id: z.union([z.number(), z.string()]),
|
|
231
|
+
page_id: z.union([z.string(), z.number()])
|
|
232
|
+
}).parse(args);
|
|
233
|
+
const courseId = await resolveCourseId(client, input.course_id);
|
|
234
|
+
const result = await client.deletePage(courseId, String(input.page_id));
|
|
235
|
+
return {
|
|
236
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
},
|
|
98
240
|
{
|
|
99
241
|
name: "canvas_list_students",
|
|
100
242
|
tool: {
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { resolveCourseId } from "../common/helpers.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
export const enrollmentTools = [
|
|
4
|
+
{
|
|
5
|
+
name: "canvas_health_check",
|
|
6
|
+
tool: {
|
|
7
|
+
name: "canvas_health_check",
|
|
8
|
+
description: "Verify Canvas API connectivity and token validity",
|
|
9
|
+
inputSchema: { type: "object", properties: {} },
|
|
10
|
+
},
|
|
11
|
+
handler: async (client, _args) => {
|
|
12
|
+
const result = await client.healthCheck();
|
|
13
|
+
return {
|
|
14
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: "canvas_get_profile",
|
|
20
|
+
tool: {
|
|
21
|
+
name: "canvas_get_profile",
|
|
22
|
+
description: "Get the profile of the currently authenticated user",
|
|
23
|
+
inputSchema: { type: "object", properties: {} },
|
|
24
|
+
},
|
|
25
|
+
handler: async (client, _args) => {
|
|
26
|
+
const result = await client.getProfile();
|
|
27
|
+
return {
|
|
28
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: "canvas_get_user",
|
|
34
|
+
tool: {
|
|
35
|
+
name: "canvas_get_user",
|
|
36
|
+
description: "Get details of a specific user by their Canvas user ID",
|
|
37
|
+
inputSchema: {
|
|
38
|
+
type: "object",
|
|
39
|
+
properties: {
|
|
40
|
+
user_id: {
|
|
41
|
+
anyOf: [{ type: "number" }, { type: "string" }],
|
|
42
|
+
description: "Canvas user ID (number) or 'self' for current user"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
required: ["user_id"],
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
handler: async (client, args) => {
|
|
49
|
+
const input = z.object({
|
|
50
|
+
user_id: z.union([z.coerce.number(), z.literal("self")])
|
|
51
|
+
}).parse(args);
|
|
52
|
+
const result = await client.getUser(input.user_id);
|
|
53
|
+
return {
|
|
54
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: "canvas_search_users",
|
|
60
|
+
tool: {
|
|
61
|
+
name: "canvas_search_users",
|
|
62
|
+
description: "Search for users in a Canvas account by name or email",
|
|
63
|
+
inputSchema: {
|
|
64
|
+
type: "object",
|
|
65
|
+
properties: {
|
|
66
|
+
account_id: {
|
|
67
|
+
anyOf: [{ type: "number" }, { type: "string" }],
|
|
68
|
+
description: "Account ID to search in (use 1 for root account or your institution's account ID)"
|
|
69
|
+
},
|
|
70
|
+
search_term: {
|
|
71
|
+
type: "string",
|
|
72
|
+
description: "Name, email, or login to search for (min 3 characters)"
|
|
73
|
+
},
|
|
74
|
+
enrollment_type: {
|
|
75
|
+
type: "string",
|
|
76
|
+
description: "Filter by role: student, teacher, ta, observer, designer"
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
required: ["account_id", "search_term"],
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
handler: async (client, args) => {
|
|
83
|
+
const input = z.object({
|
|
84
|
+
account_id: z.union([z.coerce.number(), z.string()]),
|
|
85
|
+
search_term: z.string().min(3),
|
|
86
|
+
enrollment_type: z.string().optional()
|
|
87
|
+
}).parse(args);
|
|
88
|
+
const result = await client.searchUsers(input.account_id, input.search_term, input.enrollment_type);
|
|
89
|
+
return {
|
|
90
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: "canvas_list_course_enrollments",
|
|
96
|
+
tool: {
|
|
97
|
+
name: "canvas_list_course_enrollments",
|
|
98
|
+
description: "List all enrollments in a course, optionally filtered by role",
|
|
99
|
+
inputSchema: {
|
|
100
|
+
type: "object",
|
|
101
|
+
properties: {
|
|
102
|
+
course_id: {
|
|
103
|
+
anyOf: [{ type: "number" }, { type: "string" }],
|
|
104
|
+
description: "The ID or name of the course"
|
|
105
|
+
},
|
|
106
|
+
type: {
|
|
107
|
+
type: "array",
|
|
108
|
+
items: { type: "string" },
|
|
109
|
+
description: "Filter by enrollment type(s): StudentEnrollment, TeacherEnrollment, TaEnrollment, ObserverEnrollment, DesignerEnrollment"
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
required: ["course_id"],
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
handler: async (client, args) => {
|
|
116
|
+
const input = z.object({
|
|
117
|
+
course_id: z.union([z.number(), z.string()]),
|
|
118
|
+
type: z.array(z.string()).optional()
|
|
119
|
+
}).parse(args);
|
|
120
|
+
const courseId = await resolveCourseId(client, input.course_id);
|
|
121
|
+
const result = await client.listCourseEnrollments(courseId, input.type);
|
|
122
|
+
return {
|
|
123
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: "canvas_enroll_user",
|
|
129
|
+
tool: {
|
|
130
|
+
name: "canvas_enroll_user",
|
|
131
|
+
description: "Enroll a user in a course with a specific role",
|
|
132
|
+
inputSchema: {
|
|
133
|
+
type: "object",
|
|
134
|
+
properties: {
|
|
135
|
+
course_id: {
|
|
136
|
+
anyOf: [{ type: "number" }, { type: "string" }],
|
|
137
|
+
description: "The ID or name of the course"
|
|
138
|
+
},
|
|
139
|
+
user_id: { type: "number", description: "Canvas user ID to enroll" },
|
|
140
|
+
enrollment_type: {
|
|
141
|
+
type: "string",
|
|
142
|
+
description: "Role: StudentEnrollment, TeacherEnrollment, TaEnrollment, ObserverEnrollment, DesignerEnrollment",
|
|
143
|
+
default: "StudentEnrollment"
|
|
144
|
+
},
|
|
145
|
+
notify: {
|
|
146
|
+
type: "boolean",
|
|
147
|
+
description: "Send enrollment notification email to the user (default: false)"
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
required: ["course_id", "user_id", "enrollment_type"],
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
handler: async (client, args) => {
|
|
154
|
+
const input = z.object({
|
|
155
|
+
course_id: z.union([z.number(), z.string()]),
|
|
156
|
+
user_id: z.coerce.number(),
|
|
157
|
+
enrollment_type: z.string(),
|
|
158
|
+
notify: z.boolean().optional()
|
|
159
|
+
}).parse(args);
|
|
160
|
+
const courseId = await resolveCourseId(client, input.course_id);
|
|
161
|
+
const result = await client.enrollUser(courseId, input.user_id, input.enrollment_type, input.notify);
|
|
162
|
+
return {
|
|
163
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: "canvas_remove_enrollment",
|
|
169
|
+
tool: {
|
|
170
|
+
name: "canvas_remove_enrollment",
|
|
171
|
+
description: "Remove or deactivate a user's enrollment from a course",
|
|
172
|
+
inputSchema: {
|
|
173
|
+
type: "object",
|
|
174
|
+
properties: {
|
|
175
|
+
course_id: {
|
|
176
|
+
anyOf: [{ type: "number" }, { type: "string" }],
|
|
177
|
+
description: "The ID or name of the course"
|
|
178
|
+
},
|
|
179
|
+
enrollment_id: { type: "number", description: "The enrollment ID to remove (get it from canvas_list_course_enrollments)" },
|
|
180
|
+
task: {
|
|
181
|
+
type: "string",
|
|
182
|
+
enum: ["conclude", "delete", "deactivate"],
|
|
183
|
+
description: "Action: conclude (end gracefully), delete (remove permanently), deactivate (temporarily disable). Default: conclude"
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
required: ["course_id", "enrollment_id"],
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
handler: async (client, args) => {
|
|
190
|
+
const input = z.object({
|
|
191
|
+
course_id: z.union([z.number(), z.string()]),
|
|
192
|
+
enrollment_id: z.coerce.number(),
|
|
193
|
+
task: z.enum(["conclude", "delete", "deactivate"]).optional()
|
|
194
|
+
}).parse(args);
|
|
195
|
+
const courseId = await resolveCourseId(client, input.course_id);
|
|
196
|
+
const result = await client.removeEnrollment(courseId, input.enrollment_id, input.task ?? "conclude");
|
|
197
|
+
return {
|
|
198
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
];
|