@charlie.act7/canvas-mcp-server 1.1.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.
@@ -0,0 +1,172 @@
1
+ import { resolveCourseId, resolveStudentId } from "../common/helpers.js";
2
+ import { z } from "zod";
3
+ function pickStudentGradeInfo(student) {
4
+ const enrollment = (student.enrollments || [])[0];
5
+ const grades = enrollment?.grades || {};
6
+ return {
7
+ student_id: student.id,
8
+ student_name: student.name,
9
+ email: student.email,
10
+ current_grade: grades.current_grade ?? null,
11
+ final_grade: grades.final_grade ?? null,
12
+ current_score: grades.current_score ?? null,
13
+ final_score: grades.final_score ?? null
14
+ };
15
+ }
16
+ export const studentTools = [
17
+ {
18
+ name: "canvas_list_students_with_grades",
19
+ tool: {
20
+ name: "canvas_list_students_with_grades",
21
+ description: "List students in a course with their current/final grade and score",
22
+ inputSchema: {
23
+ type: "object",
24
+ properties: {
25
+ course_id: {
26
+ anyOf: [{ type: "number" }, { type: "string" }],
27
+ description: "The ID or name of the course"
28
+ }
29
+ },
30
+ required: ["course_id"]
31
+ }
32
+ },
33
+ handler: async (client, args) => {
34
+ const input = z.object({ course_id: z.union([z.number(), z.string()]) }).parse(args);
35
+ const courseId = await resolveCourseId(client, input.course_id);
36
+ const students = await client.getEnrollments(courseId);
37
+ const rows = students.map(pickStudentGradeInfo);
38
+ return {
39
+ content: [{ type: "text", text: JSON.stringify(rows, null, 2) }]
40
+ };
41
+ }
42
+ },
43
+ {
44
+ name: "canvas_get_student_grades",
45
+ tool: {
46
+ name: "canvas_get_student_grades",
47
+ description: "Get grade summary for one student in a course",
48
+ inputSchema: {
49
+ type: "object",
50
+ properties: {
51
+ course_id: {
52
+ anyOf: [{ type: "number" }, { type: "string" }],
53
+ description: "The ID or name of the course"
54
+ },
55
+ student_id: {
56
+ anyOf: [{ type: "number" }, { type: "string" }],
57
+ description: "Student ID or student name"
58
+ }
59
+ },
60
+ required: ["course_id", "student_id"]
61
+ }
62
+ },
63
+ handler: async (client, args) => {
64
+ const input = z.object({
65
+ course_id: z.union([z.number(), z.string()]),
66
+ student_id: z.union([z.number(), z.string()])
67
+ }).parse(args);
68
+ const courseId = await resolveCourseId(client, input.course_id);
69
+ const studentId = await resolveStudentId(client, courseId, input.student_id);
70
+ const student = await client.getStudentInCourse(courseId, studentId);
71
+ return {
72
+ content: [{ type: "text", text: JSON.stringify(pickStudentGradeInfo(student), null, 2) }]
73
+ };
74
+ }
75
+ },
76
+ {
77
+ name: "canvas_get_student_assignments",
78
+ tool: {
79
+ name: "canvas_get_student_assignments",
80
+ description: "Get all assignments for one student with due dates and submission status",
81
+ inputSchema: {
82
+ type: "object",
83
+ properties: {
84
+ course_id: {
85
+ anyOf: [{ type: "number" }, { type: "string" }],
86
+ description: "The ID or name of the course"
87
+ },
88
+ student_id: {
89
+ anyOf: [{ type: "number" }, { type: "string" }],
90
+ description: "Student ID or student name"
91
+ }
92
+ },
93
+ required: ["course_id", "student_id"]
94
+ }
95
+ },
96
+ handler: async (client, args) => {
97
+ const input = z.object({
98
+ course_id: z.union([z.number(), z.string()]),
99
+ student_id: z.union([z.number(), z.string()])
100
+ }).parse(args);
101
+ const courseId = await resolveCourseId(client, input.course_id);
102
+ const studentId = await resolveStudentId(client, courseId, input.student_id);
103
+ const submissions = await client.getStudentCourseSubmissions(courseId, studentId);
104
+ const rows = submissions.map((item) => ({
105
+ assignment_id: item.assignment_id,
106
+ assignment_name: item.assignment?.name ?? null,
107
+ due_at: item.assignment?.due_at ?? null,
108
+ unlock_at: item.assignment?.unlock_at ?? null,
109
+ lock_at: item.assignment?.lock_at ?? null,
110
+ submitted_at: item.submitted_at ?? null,
111
+ late: item.late ?? false,
112
+ missing: item.missing ?? false,
113
+ workflow_state: item.workflow_state ?? null,
114
+ grade: item.grade ?? null,
115
+ score: item.score ?? null
116
+ }));
117
+ return {
118
+ content: [{ type: "text", text: JSON.stringify(rows, null, 2) }]
119
+ };
120
+ }
121
+ },
122
+ {
123
+ name: "canvas_list_assignment_due_dates",
124
+ tool: {
125
+ name: "canvas_list_assignment_due_dates",
126
+ description: "List assignment due dates in a course (optionally only upcoming)",
127
+ inputSchema: {
128
+ type: "object",
129
+ properties: {
130
+ course_id: {
131
+ anyOf: [{ type: "number" }, { type: "string" }],
132
+ description: "The ID or name of the course"
133
+ },
134
+ only_upcoming: {
135
+ type: "boolean",
136
+ description: "If true, return only assignments with due_at >= now"
137
+ }
138
+ },
139
+ required: ["course_id"]
140
+ }
141
+ },
142
+ handler: async (client, args) => {
143
+ const input = z.object({
144
+ course_id: z.union([z.number(), z.string()]),
145
+ only_upcoming: z.boolean().optional()
146
+ }).parse(args);
147
+ const courseId = await resolveCourseId(client, input.course_id);
148
+ const assignments = await client.getAssignments(courseId);
149
+ const now = new Date();
150
+ const rows = assignments
151
+ .map((a) => ({
152
+ assignment_id: a.id ?? null,
153
+ assignment_name: a.name,
154
+ due_at: a.due_at ?? null,
155
+ unlock_at: a.unlock_at ?? null,
156
+ lock_at: a.lock_at ?? null,
157
+ points_possible: a.points_possible ?? null,
158
+ published: a.published ?? null
159
+ }))
160
+ .filter((a) => {
161
+ if (!input.only_upcoming)
162
+ return true;
163
+ if (!a.due_at)
164
+ return false;
165
+ return new Date(a.due_at) >= now;
166
+ });
167
+ return {
168
+ content: [{ type: "text", text: JSON.stringify(rows, null, 2) }]
169
+ };
170
+ }
171
+ }
172
+ ];
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@charlie.act7/canvas-mcp-server",
3
+ "publishConfig": {
4
+ "access": "public"
5
+ },
6
+ "version": "1.1.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
+ ],
23
+ "scripts": {
24
+ "build": "tsc",
25
+ "start": "node dist/index.js",
26
+ "start:http": "node dist/index.js serve-http",
27
+ "dev": "tsc --watch",
28
+ "chat": "tsx src/ollama_bridge.ts",
29
+ "prepublishOnly": "npm run build"
30
+ },
31
+ "keywords": [
32
+ "mcp",
33
+ "canvas",
34
+ "lms",
35
+ "ai",
36
+ "agent",
37
+ "server"
38
+ ],
39
+ "author": "Charlie Cárdenas Toledo",
40
+ "license": "MIT",
41
+ "dependencies": {
42
+ "@fastify/swagger": "^9.5.2",
43
+ "@fastify/swagger-ui": "^5.2.3",
44
+ "@modelcontextprotocol/sdk": "^1.25.3",
45
+ "axios": "^1.6.0",
46
+ "chalk": "^5.3.0",
47
+ "commander": "^11.1.0",
48
+ "conf": "^12.0.0",
49
+ "dotenv": "^16.3.1",
50
+ "fastify": "^5.6.1",
51
+ "form-data": "^4.0.5",
52
+ "inquirer": "^9.2.12",
53
+ "zod": "^3.22.4"
54
+ },
55
+ "devDependencies": {
56
+ "@types/inquirer": "^9.0.7",
57
+ "@types/node": "^20.10.0",
58
+ "nodemon": "^3.1.11",
59
+ "tsx": "^4.21.0",
60
+ "typescript": "^5.3.0"
61
+ },
62
+ "engines": {
63
+ "node": ">=18.0.0"
64
+ }
65
+ }