@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 ADDED
@@ -0,0 +1,117 @@
1
+ # Canvas MCP Server
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@charlie.act7/canvas-mcp-server)](https://www.npmjs.com/package/@charlie.act7/canvas-mcp-server)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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 {};