@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.
@@ -100,6 +100,107 @@ export const communicationTools = [
100
100
  };
101
101
  }
102
102
  },
103
+ {
104
+ name: "canvas_update_announcement",
105
+ tool: {
106
+ name: "canvas_update_announcement",
107
+ description: "Update the title or message of an existing announcement in a course",
108
+ inputSchema: {
109
+ type: "object",
110
+ properties: {
111
+ course_id: { type: "number", description: "The ID of the course" },
112
+ topic_id: { type: "number", description: "The ID of the announcement (discussion topic)" },
113
+ title: { type: "string", description: "New title for the announcement (optional)" },
114
+ message: { type: "string", description: "New HTML message body for the announcement (optional)" },
115
+ },
116
+ required: ["course_id", "topic_id"],
117
+ },
118
+ },
119
+ handler: async (client, args) => {
120
+ const input = z.object({
121
+ course_id: z.coerce.number(),
122
+ topic_id: z.coerce.number(),
123
+ title: z.string().optional(),
124
+ message: z.string().optional(),
125
+ }).parse(args);
126
+ const fields = {};
127
+ if (input.title)
128
+ fields.title = input.title;
129
+ if (input.message)
130
+ fields.message = input.message;
131
+ const result = await client.updateAnnouncement(input.course_id, input.topic_id, fields);
132
+ return {
133
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
134
+ };
135
+ }
136
+ },
137
+ {
138
+ name: "canvas_create_discussion",
139
+ tool: {
140
+ name: "canvas_create_discussion",
141
+ description: "Create a new discussion topic in a course",
142
+ inputSchema: {
143
+ type: "object",
144
+ properties: {
145
+ course_id: { type: "number", description: "The ID of the course" },
146
+ title: { type: "string", description: "Title of the discussion" },
147
+ message: { type: "string", description: "Body/message of the discussion" },
148
+ discussion_type: {
149
+ type: "string",
150
+ enum: ["side_comment", "threaded"],
151
+ description: "Discussion type (default: side_comment)"
152
+ },
153
+ published: { type: "boolean", description: "Whether to publish immediately (default: true)" },
154
+ pinned: { type: "boolean", description: "Pin to top of discussion list" },
155
+ require_initial_post: { type: "boolean", description: "Students must post before seeing replies" },
156
+ allow_rating: { type: "boolean", description: "Allow students to rate posts" }
157
+ },
158
+ required: ["course_id", "title", "message"],
159
+ },
160
+ },
161
+ handler: async (client, args) => {
162
+ const input = z.object({
163
+ course_id: z.coerce.number(),
164
+ title: z.string(),
165
+ message: z.string(),
166
+ discussion_type: z.enum(["side_comment", "threaded"]).optional(),
167
+ published: z.boolean().optional(),
168
+ pinned: z.boolean().optional(),
169
+ require_initial_post: z.boolean().optional(),
170
+ allow_rating: z.boolean().optional()
171
+ }).parse(args);
172
+ const { course_id, ...data } = input;
173
+ const result = await client.createDiscussion(course_id, data);
174
+ return {
175
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
176
+ };
177
+ }
178
+ },
179
+ {
180
+ name: "canvas_delete_discussion",
181
+ tool: {
182
+ name: "canvas_delete_discussion",
183
+ description: "Delete a discussion topic from a course",
184
+ inputSchema: {
185
+ type: "object",
186
+ properties: {
187
+ course_id: { type: "number", description: "The ID of the course" },
188
+ topic_id: { type: "number", description: "The ID of the discussion topic to delete" }
189
+ },
190
+ required: ["course_id", "topic_id"],
191
+ },
192
+ },
193
+ handler: async (client, args) => {
194
+ const input = z.object({
195
+ course_id: z.coerce.number(),
196
+ topic_id: z.coerce.number()
197
+ }).parse(args);
198
+ const result = await client.deleteDiscussion(input.course_id, input.topic_id);
199
+ return {
200
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
201
+ };
202
+ }
203
+ },
103
204
  {
104
205
  name: "canvas_post_discussion_reply",
105
206
  tool: {
@@ -0,0 +1,130 @@
1
+ import { z } from "zod";
2
+ export const conversationTools = [
3
+ {
4
+ name: "canvas_list_conversations",
5
+ tool: {
6
+ name: "canvas_list_conversations",
7
+ description: "List conversations (inbox messages) for the current user",
8
+ inputSchema: {
9
+ type: "object",
10
+ properties: {
11
+ scope: {
12
+ type: "string",
13
+ enum: ["inbox", "unread", "archived", "sent"],
14
+ description: "Which mailbox to list (default: inbox)"
15
+ }
16
+ },
17
+ },
18
+ },
19
+ handler: async (client, args) => {
20
+ const input = z.object({
21
+ scope: z.enum(["inbox", "unread", "archived", "sent"]).optional()
22
+ }).parse(args);
23
+ const result = await client.listConversations(input.scope);
24
+ return {
25
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
26
+ };
27
+ }
28
+ },
29
+ {
30
+ name: "canvas_get_conversation",
31
+ tool: {
32
+ name: "canvas_get_conversation",
33
+ description: "Get a full conversation thread including all messages",
34
+ inputSchema: {
35
+ type: "object",
36
+ properties: {
37
+ conversation_id: { type: "number", description: "The ID of the conversation" }
38
+ },
39
+ required: ["conversation_id"],
40
+ },
41
+ },
42
+ handler: async (client, args) => {
43
+ const input = z.object({ conversation_id: z.coerce.number() }).parse(args);
44
+ const result = await client.getConversation(input.conversation_id);
45
+ return {
46
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
47
+ };
48
+ }
49
+ },
50
+ {
51
+ name: "canvas_get_conversation_unread_count",
52
+ tool: {
53
+ name: "canvas_get_conversation_unread_count",
54
+ description: "Get the number of unread messages in the inbox",
55
+ inputSchema: { type: "object", properties: {} },
56
+ },
57
+ handler: async (client, _args) => {
58
+ const result = await client.getConversationUnreadCount();
59
+ return {
60
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
61
+ };
62
+ }
63
+ },
64
+ {
65
+ name: "canvas_send_conversation",
66
+ tool: {
67
+ name: "canvas_send_conversation",
68
+ description: "Send a new message to one or more users (private message or course announcement)",
69
+ inputSchema: {
70
+ type: "object",
71
+ properties: {
72
+ recipients: {
73
+ type: "array",
74
+ items: { anyOf: [{ type: "number" }, { type: "string" }] },
75
+ description: "Array of user IDs or login IDs to send the message to"
76
+ },
77
+ subject: { type: "string", description: "Message subject" },
78
+ body: { type: "string", description: "Message body" },
79
+ course_id: {
80
+ type: "number",
81
+ description: "Associate message with a course context (optional but recommended)"
82
+ },
83
+ group_conversation: {
84
+ type: "boolean",
85
+ description: "If true, all recipients share one thread. If false, each gets an individual message (default: false)"
86
+ }
87
+ },
88
+ required: ["recipients", "subject", "body"],
89
+ },
90
+ },
91
+ handler: async (client, args) => {
92
+ const input = z.object({
93
+ recipients: z.array(z.union([z.coerce.number(), z.string()])),
94
+ subject: z.string(),
95
+ body: z.string(),
96
+ course_id: z.coerce.number().optional(),
97
+ group_conversation: z.boolean().optional()
98
+ }).parse(args);
99
+ const result = await client.sendConversation(input);
100
+ return {
101
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
102
+ };
103
+ }
104
+ },
105
+ {
106
+ name: "canvas_reply_to_conversation",
107
+ tool: {
108
+ name: "canvas_reply_to_conversation",
109
+ description: "Reply to an existing conversation thread",
110
+ inputSchema: {
111
+ type: "object",
112
+ properties: {
113
+ conversation_id: { type: "number", description: "The ID of the conversation to reply to" },
114
+ body: { type: "string", description: "The reply message text" }
115
+ },
116
+ required: ["conversation_id", "body"],
117
+ },
118
+ },
119
+ handler: async (client, args) => {
120
+ const input = z.object({
121
+ conversation_id: z.coerce.number(),
122
+ body: z.string()
123
+ }).parse(args);
124
+ const result = await client.replyToConversation(input.conversation_id, input.body);
125
+ return {
126
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
127
+ };
128
+ }
129
+ }
130
+ ];
@@ -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
+ ];