@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.
- package/README.md +117 -0
- package/dist/common/config-manager.js +28 -0
- package/dist/common/helpers.js +46 -0
- package/dist/common/tool-model.js +1 -0
- package/dist/common/types.js +1 -0
- package/dist/http-server.js +760 -0
- package/dist/index.js +168 -0
- package/dist/prompts/canvas-prompts.js +64 -0
- package/dist/resources/canvas-resources.js +55 -0
- package/dist/services/canvas-client.js +459 -0
- package/dist/tools/assignment-tools.js +626 -0
- package/dist/tools/calendar-tools.js +240 -0
- package/dist/tools/communication-tools.js +130 -0
- package/dist/tools/config-tools.js +39 -0
- package/dist/tools/course-tools.js +123 -0
- package/dist/tools/create-tools.js +76 -0
- package/dist/tools/file-tools.js +229 -0
- package/dist/tools/grading-tools.js +187 -0
- package/dist/tools/group-tools.js +192 -0
- package/dist/tools/module-tools.js +269 -0
- package/dist/tools/question-bank-tools.js +238 -0
- package/dist/tools/quiz-question-tools.js +303 -0
- package/dist/tools/quiz-tools.js +184 -0
- package/dist/tools/rubric-tools.js +145 -0
- package/dist/tools/student-tools.js +172 -0
- package/package.json +65 -0
package/README.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Canvas MCP Server
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@charlie.act7/canvas-mcp-server)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
A [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server for **Canvas LMS**. Lets AI agents (Claude, etc.) interact with your courses, assignments, grades, quizzes, modules, files, and more — all through natural language.
|
|
7
|
+
|
|
8
|
+
## Quick Start (Claude Desktop / Claude Code)
|
|
9
|
+
|
|
10
|
+
### Option 1: npx (recommended — no install needed)
|
|
11
|
+
|
|
12
|
+
Add this to your `claude_desktop_config.json`:
|
|
13
|
+
|
|
14
|
+
```json
|
|
15
|
+
{
|
|
16
|
+
"mcpServers": {
|
|
17
|
+
"canvas": {
|
|
18
|
+
"command": "npx",
|
|
19
|
+
"args": ["-y", "@charlie.act7/canvas-mcp-server"],
|
|
20
|
+
"env": {
|
|
21
|
+
"CANVAS_API_TOKEN": "your_token_here",
|
|
22
|
+
"CANVAS_API_DOMAIN": "your_school.instructure.com"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Option 2: Docker
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"mcpServers": {
|
|
34
|
+
"canvas": {
|
|
35
|
+
"command": "docker",
|
|
36
|
+
"args": [
|
|
37
|
+
"run", "-i", "--rm",
|
|
38
|
+
"-e", "CANVAS_API_TOKEN=your_token_here",
|
|
39
|
+
"-e", "CANVAS_API_DOMAIN=your_school.instructure.com",
|
|
40
|
+
"charliecardenas/canvas-mcp"
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
> **Note:** Build the image first: `docker build -t charliecardenas/canvas-mcp .`
|
|
48
|
+
|
|
49
|
+
## Getting Your Canvas Credentials
|
|
50
|
+
|
|
51
|
+
1. Log in to Canvas
|
|
52
|
+
2. Go to **Account → Settings**
|
|
53
|
+
3. Scroll to **Approved Integrations** → click **New Access Token**
|
|
54
|
+
4. Copy the token — you won't see it again
|
|
55
|
+
|
|
56
|
+
Your domain is the hostname of your Canvas instance (e.g. `uide.instructure.com`).
|
|
57
|
+
|
|
58
|
+
## Available Tools
|
|
59
|
+
|
|
60
|
+
| Category | Tools |
|
|
61
|
+
|---|---|
|
|
62
|
+
| **Courses** | `canvas_list_courses`, `set_canvas_config` |
|
|
63
|
+
| **Modules** | `canvas_list_modules` |
|
|
64
|
+
| **Pages** | `canvas_list_pages`, `canvas_get_page_content` |
|
|
65
|
+
| **Files** | `canvas_list_files` |
|
|
66
|
+
| **Announcements** | `canvas_list_announcements` |
|
|
67
|
+
| **Assignments** | `canvas_get_assignments`, `canvas_get_submissions` |
|
|
68
|
+
| **Grading** | `canvas_grade_submission`, `canvas_audit_course` |
|
|
69
|
+
| **Quizzes** | Quiz listing and question management |
|
|
70
|
+
| **Students** | Student roster and progress |
|
|
71
|
+
| **Groups** | Group management |
|
|
72
|
+
| **Calendar** | Calendar events |
|
|
73
|
+
| **Rubrics** | Rubric creation and management |
|
|
74
|
+
| **Create** | Create assignments, pages, announcements |
|
|
75
|
+
| **Communication** | Messaging and discussions |
|
|
76
|
+
|
|
77
|
+
## Resources
|
|
78
|
+
|
|
79
|
+
MCP clients that support resources can access Canvas content directly:
|
|
80
|
+
|
|
81
|
+
- `canvas://courses/{id}/readme` — Course summary
|
|
82
|
+
- `canvas://courses/{id}/pages/{slug}` — Page HTML content
|
|
83
|
+
|
|
84
|
+
## Local Development
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# Install dependencies
|
|
88
|
+
npm install
|
|
89
|
+
|
|
90
|
+
# Configure credentials interactively
|
|
91
|
+
npx canvas-mcp config
|
|
92
|
+
|
|
93
|
+
# Build
|
|
94
|
+
npm run build
|
|
95
|
+
|
|
96
|
+
# Start MCP server (stdio)
|
|
97
|
+
npm start
|
|
98
|
+
|
|
99
|
+
# Start HTTP API (for GPT Builder / OpenAPI)
|
|
100
|
+
npm run start:http
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The HTTP server runs on `http://localhost:3000`:
|
|
104
|
+
- Swagger UI: `http://localhost:3000/docs`
|
|
105
|
+
- OpenAPI JSON: `http://localhost:3000/openapi.json`
|
|
106
|
+
|
|
107
|
+
## Deploy on Render
|
|
108
|
+
|
|
109
|
+
This repo includes `render.yaml` for one-click deploy as a Web Service.
|
|
110
|
+
|
|
111
|
+
Required environment variables:
|
|
112
|
+
- `CANVAS_API_TOKEN`
|
|
113
|
+
- `CANVAS_API_DOMAIN` (e.g. `your-school.instructure.com`)
|
|
114
|
+
|
|
115
|
+
## License
|
|
116
|
+
|
|
117
|
+
MIT © Charlie Cárdenas Toledo
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import Conf from 'conf';
|
|
2
|
+
export class ConfigManager {
|
|
3
|
+
conf;
|
|
4
|
+
constructor() {
|
|
5
|
+
this.conf = new Conf({
|
|
6
|
+
projectName: 'canvas-mcp-server',
|
|
7
|
+
projectSuffix: ''
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
set(key, value) {
|
|
11
|
+
this.conf.set(key, value);
|
|
12
|
+
}
|
|
13
|
+
get(key) {
|
|
14
|
+
return this.conf.get(key);
|
|
15
|
+
}
|
|
16
|
+
getAll() {
|
|
17
|
+
return this.conf.store;
|
|
18
|
+
}
|
|
19
|
+
clear() {
|
|
20
|
+
this.conf.clear();
|
|
21
|
+
}
|
|
22
|
+
hasConfig() {
|
|
23
|
+
return this.conf.has('CANVAS_API_TOKEN') && this.conf.has('CANVAS_API_DOMAIN');
|
|
24
|
+
}
|
|
25
|
+
get path() {
|
|
26
|
+
return this.conf.path;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export async function resolveCourseId(client, courseIdentifier) {
|
|
2
|
+
// If it's already a number or a string that looks like a number, return it as number
|
|
3
|
+
if (typeof courseIdentifier === 'number') {
|
|
4
|
+
return courseIdentifier;
|
|
5
|
+
}
|
|
6
|
+
if (!isNaN(Number(courseIdentifier))) {
|
|
7
|
+
return Number(courseIdentifier);
|
|
8
|
+
}
|
|
9
|
+
// It's a string (name), so we need to search for it
|
|
10
|
+
const courses = await client.getCourses();
|
|
11
|
+
const searchTerm = courseIdentifier.toLowerCase();
|
|
12
|
+
const matchedCourse = courses.find(course => {
|
|
13
|
+
const name = (course.name || "").toLowerCase();
|
|
14
|
+
const originalName = (course.original_name || "").toLowerCase(); // Cast to string as it might be missing in type def but present in API
|
|
15
|
+
const code = (course.course_code || "").toLowerCase();
|
|
16
|
+
return name.includes(searchTerm) || originalName.includes(searchTerm) || code.includes(searchTerm);
|
|
17
|
+
});
|
|
18
|
+
if (matchedCourse) {
|
|
19
|
+
return matchedCourse.id;
|
|
20
|
+
}
|
|
21
|
+
throw new Error(`Course not found matching: "${courseIdentifier}". Please provide a valid Course ID or a more specific name.`);
|
|
22
|
+
}
|
|
23
|
+
export async function resolveStudentId(client, courseId, studentIdentifier) {
|
|
24
|
+
if (typeof studentIdentifier === "number") {
|
|
25
|
+
return studentIdentifier;
|
|
26
|
+
}
|
|
27
|
+
if (!isNaN(Number(studentIdentifier))) {
|
|
28
|
+
return Number(studentIdentifier);
|
|
29
|
+
}
|
|
30
|
+
const students = await client.getEnrollments(courseId);
|
|
31
|
+
const searchTerm = studentIdentifier.toLowerCase();
|
|
32
|
+
const matchedStudent = students.find((student) => {
|
|
33
|
+
const name = (student.name || "").toLowerCase();
|
|
34
|
+
const sortableName = (student.sortable_name || "").toLowerCase();
|
|
35
|
+
const email = (student.email || "").toLowerCase();
|
|
36
|
+
const login = (student.login_id || "").toLowerCase();
|
|
37
|
+
return (name.includes(searchTerm) ||
|
|
38
|
+
sortableName.includes(searchTerm) ||
|
|
39
|
+
email.includes(searchTerm) ||
|
|
40
|
+
login.includes(searchTerm));
|
|
41
|
+
});
|
|
42
|
+
if (matchedStudent) {
|
|
43
|
+
return matchedStudent.id;
|
|
44
|
+
}
|
|
45
|
+
throw new Error(`Student not found matching: "${studentIdentifier}". Please provide a valid student ID or a more specific name.`);
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|