@charlie.act7/canvas-mcp-server 1.2.1 β 1.2.2
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 +262 -113
- package/dist/index.js +15 -3
- package/dist/services/canvas-client.js +114 -1
- package/dist/tools/access-token-tools.js +183 -0
- package/dist/tools/quiz-tools.js +12 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,29 +1,48 @@
|
|
|
1
1
|
# π Canvas LMS MCP Server
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/@charlie.act7/canvas-mcp-server)
|
|
4
|
+
[](https://www.npmjs.com/package/@charlie.act7/canvas-mcp-server)
|
|
5
|
+
[](https://www.typescriptlang.org/)
|
|
6
|
+
[](https://modelcontextprotocol.io/)
|
|
7
|
+
[](./LICENSE)
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
Model Context Protocol (MCP) server for **Canvas LMS**. It acts as a bridge that allows AI assistants (Claude Code, Claude Desktop, Cursor, Codex CLI, n8n, OpenAI Custom GPTs, and generic MCP clients) to query, grade, audit, and manage Canvas courses, assignments, rubrics, submissions, quizzes, conversations, and analytics using natural language. Two transports are supported: `stdio` (default) and Streamable-HTTP with interactive Swagger API documentation.
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
> **VersiΓ³n en EspaΓ±ol:** [README.es.md](README.es.md)
|
|
11
12
|
|
|
12
13
|
---
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
- [Requirements & Platform Support](#requirements--platform-support)
|
|
15
16
|
- [How It Works](#how-it-works)
|
|
16
|
-
- [
|
|
17
|
-
- [
|
|
18
|
-
|
|
19
|
-
- [
|
|
20
|
-
- [
|
|
21
|
-
- [
|
|
22
|
-
- [
|
|
17
|
+
- [Install](#install)
|
|
18
|
+
- [Connect to Claude Code](#connect-to-claude-code)
|
|
19
|
+
- [Connect to Other Clients](#connect-to-other-clients)
|
|
20
|
+
- [Claude Desktop](#claude-desktop)
|
|
21
|
+
- [Cursor](#cursor)
|
|
22
|
+
- [Codex CLI](#codex-cli)
|
|
23
|
+
- [Generic MCP Client (stdio)](#generic-mcp-client-stdio)
|
|
24
|
+
- [HTTP Server & OpenAI Custom GPTs](#http-server--openai-custom-gpts)
|
|
25
|
+
- [Authentication & Credentials](#authentication--credentials)
|
|
26
|
+
- [Transports](#transports)
|
|
27
|
+
- [Use Cases & Prompts](#use-cases--prompts)
|
|
28
|
+
- [Tools](#tools)
|
|
29
|
+
- [Resources](#resources)
|
|
30
|
+
- [Configuration Reference](#configuration-reference)
|
|
31
|
+
- [Development](#development)
|
|
32
|
+
- [Documentation](#documentation)
|
|
23
33
|
- [License](#license)
|
|
24
34
|
|
|
25
35
|
---
|
|
26
36
|
|
|
37
|
+
## Requirements & Platform Support
|
|
38
|
+
|
|
39
|
+
- **Node.js** β₯ 18.0.0 (Node.js β₯ 20.x recommended).
|
|
40
|
+
- **Canvas LMS Access**: An active account on your institution's Canvas LMS (e.g. `myschool.instructure.com`).
|
|
41
|
+
- **Canvas API Access Token**: Generated via your Canvas Account Settings page.
|
|
42
|
+
- **Linux / macOS / Windows / WSL2**.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
27
46
|
## How It Works
|
|
28
47
|
|
|
29
48
|
When you interact with the server, communication flows as follows:
|
|
@@ -40,88 +59,112 @@ graph LR
|
|
|
40
59
|
|
|
41
60
|
1. **You ask the AI** (e.g., *"Create an assignment due next Friday"*).
|
|
42
61
|
2. **The AI detects your intent** and communicates with the **Canvas MCP Server**, sending the required parameters.
|
|
43
|
-
3. **The server makes a secure call** to the official Canvas API.
|
|
44
|
-
4. **Canvas processes the action** and returns the response.
|
|
62
|
+
3. **The server makes a secure call** to the official Canvas LMS API over HTTPS.
|
|
63
|
+
4. **Canvas LMS processes the action** and returns the response payload.
|
|
45
64
|
5. **The AI confirms the success of the action** back to you in plain, natural language.
|
|
46
65
|
|
|
47
66
|
---
|
|
48
67
|
|
|
49
|
-
##
|
|
68
|
+
## Install
|
|
50
69
|
|
|
51
|
-
|
|
70
|
+
### Published package
|
|
52
71
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
### π Course Auditing & Querying
|
|
57
|
-
* π¬ *"What active courses do I have this semester? Check if there are multiple active sections/parallels."*
|
|
58
|
-
* π¬ *"Show me all ungraded submissions for 'Essay 1: Introduction to Sociology' in Sociology 101."*
|
|
59
|
-
* π¬ *"Who is in Student Group A for the Chemistry class?"*
|
|
60
|
-
* π¬ *"Does the assignment 'Project Proposal' have an active rubric associated? If so, retrieve its criteria."*
|
|
61
|
-
* π¬ *"Search for everything related to 'photosynthesis' across my Biology course β assignments, pages, and discussions."*
|
|
72
|
+
```bash
|
|
73
|
+
npx @charlie.act7/canvas-mcp-server@latest
|
|
74
|
+
```
|
|
62
75
|
|
|
63
|
-
|
|
64
|
-
* π¬ *"Create a new module named 'Week 1: Foundations' in my course."*
|
|
65
|
-
* π¬ *"Add a SubHeader 'REQUIRED READINGS' inside the 'Week 1' module, and link the syllabus page to it."*
|
|
66
|
-
* π¬ *"In my Business course, create an assignment called 'Case Study 1: Market Analysis'. Add an instructions table with columns for Criteria, Requirements, and Points."*
|
|
67
|
-
* π¬ *"Create a threaded discussion topic in my course titled 'Weekly Reflection' and pin it to the top."*
|
|
76
|
+
This is the recommended path for end users. `npx` keeps the binary cached and self-updates on `@latest`.
|
|
68
77
|
|
|
69
|
-
###
|
|
70
|
-
* π¬ *"For assignment 'Case Study 1', find all students who haven't submitted their work. Assign them a grade of 0 and add the comment: 'Activity not submitted. Please contact the instructor if you have a valid excuse.'"*
|
|
71
|
-
* π¬ *"Grade John's submission for 'Essay 1' with a 90 based on the rubric, and add a comment: 'Great job! The analysis is well-structured, though you could expand more on the conclusion. Keep it up!'"*
|
|
78
|
+
### Interactive CLI Setup
|
|
72
79
|
|
|
73
|
-
|
|
74
|
-
* π¬ *"Show me the activity analytics for my Calculus course β how active have students been this week?"*
|
|
75
|
-
* π¬ *"Which students haven't been active in course 12345 in the last few days? I want to reach out to them."*
|
|
76
|
-
* π¬ *"Get the analytics for student [ID] in my Biology course β how many page views and participations do they have?"*
|
|
80
|
+
Configure your Canvas credentials interactively before running:
|
|
77
81
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
* π¬ *"Show me my last 10 inbox conversations."*
|
|
82
|
+
```bash
|
|
83
|
+
npx @charlie.act7/canvas-mcp-server config
|
|
84
|
+
```
|
|
82
85
|
|
|
83
|
-
###
|
|
84
|
-
* π¬ *"Who is enrolled in my course? Show me students and TAs separately."*
|
|
85
|
-
* π¬ *"Search for a student named 'Maria Gonzalez' in account 1."*
|
|
86
|
-
* π¬ *"Enroll user [ID] as a TA in my Physics course."*
|
|
86
|
+
### From source
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
```bash
|
|
89
|
+
git clone https://github.com/CharlieCardenasToledo/mcp-canvas-server.git
|
|
90
|
+
cd mcp-canvas-server
|
|
91
|
+
npm install
|
|
92
|
+
npm run build
|
|
93
|
+
node dist/index.js
|
|
94
|
+
```
|
|
91
95
|
|
|
92
96
|
---
|
|
93
97
|
|
|
94
|
-
##
|
|
98
|
+
## Connect to Claude Code
|
|
95
99
|
|
|
96
|
-
|
|
100
|
+
CLI form:
|
|
97
101
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
5. **Copy the generated token immediately** and store it somewhere safe (you won't be able to see it again after closing the page).
|
|
102
|
+
```bash
|
|
103
|
+
claude mcp add canvas \
|
|
104
|
+
--env CANVAS_API_TOKEN=YOUR_ACCESS_TOKEN_HERE \
|
|
105
|
+
--env CANVAS_API_DOMAIN=myschool.instructure.com \
|
|
106
|
+
-- npx -y @charlie.act7/canvas-mcp-server@latest
|
|
107
|
+
```
|
|
105
108
|
|
|
106
|
-
|
|
107
|
-
|
|
109
|
+
Or from a local build:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
claude mcp add canvas \
|
|
113
|
+
--env CANVAS_API_TOKEN=YOUR_ACCESS_TOKEN_HERE \
|
|
114
|
+
--env CANVAS_API_DOMAIN=myschool.instructure.com \
|
|
115
|
+
-- node /absolute/path/to/mcp-canvas-server/dist/index.js
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Manual form β drop into `~/.claude.json`:
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
{
|
|
122
|
+
"mcpServers": {
|
|
123
|
+
"canvas": {
|
|
124
|
+
"command": "npx",
|
|
125
|
+
"args": ["-y", "@charlie.act7/canvas-mcp-server@latest"],
|
|
126
|
+
"env": {
|
|
127
|
+
"CANVAS_API_TOKEN": "YOUR_ACCESS_TOKEN_HERE",
|
|
128
|
+
"CANVAS_API_DOMAIN": "myschool.instructure.com"
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
108
134
|
|
|
109
135
|
---
|
|
110
136
|
|
|
111
|
-
|
|
137
|
+
## Connect to Other Clients
|
|
138
|
+
|
|
139
|
+
### Claude Desktop
|
|
140
|
+
|
|
141
|
+
Edit your Claude Desktop configuration file:
|
|
142
|
+
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
143
|
+
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
144
|
+
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"mcpServers": {
|
|
148
|
+
"canvas": {
|
|
149
|
+
"command": "npx",
|
|
150
|
+
"args": ["-y", "@charlie.act7/canvas-mcp-server@latest"],
|
|
151
|
+
"env": {
|
|
152
|
+
"CANVAS_API_TOKEN": "YOUR_ACCESS_TOKEN_HERE",
|
|
153
|
+
"CANVAS_API_DOMAIN": "myschool.instructure.com"
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
112
159
|
|
|
113
|
-
|
|
114
|
-
1. Open your Claude Desktop configuration file. On Windows, it is located at:
|
|
115
|
-
`%APPDATA%\Claude\claude_desktop_config.json`
|
|
116
|
-
*(On macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`)*
|
|
117
|
-
2. Add the Canvas server configuration under `mcpServers`:
|
|
160
|
+
### Cursor β `~/.cursor/mcp.json`
|
|
118
161
|
|
|
119
162
|
```json
|
|
120
163
|
{
|
|
121
164
|
"mcpServers": {
|
|
122
165
|
"canvas": {
|
|
123
166
|
"command": "npx",
|
|
124
|
-
"args": ["-y", "@charlie.act7/canvas-mcp-server"],
|
|
167
|
+
"args": ["-y", "@charlie.act7/canvas-mcp-server@latest"],
|
|
125
168
|
"env": {
|
|
126
169
|
"CANVAS_API_TOKEN": "YOUR_ACCESS_TOKEN_HERE",
|
|
127
170
|
"CANVAS_API_DOMAIN": "myschool.instructure.com"
|
|
@@ -130,47 +173,126 @@ To let the server act on your behalf, it needs permission:
|
|
|
130
173
|
}
|
|
131
174
|
}
|
|
132
175
|
```
|
|
133
|
-
3. Save the file and restart Claude Desktop. You will see a socket/plug icon indicating the server is successfully connected.
|
|
134
176
|
|
|
135
|
-
|
|
136
|
-
|
|
177
|
+
### Codex CLI
|
|
178
|
+
|
|
137
179
|
```bash
|
|
138
|
-
|
|
180
|
+
codex mcp add canvas \
|
|
181
|
+
--env CANVAS_API_TOKEN=YOUR_ACCESS_TOKEN_HERE \
|
|
182
|
+
--env CANVAS_API_DOMAIN=myschool.instructure.com \
|
|
183
|
+
npx -y @charlie.act7/canvas-mcp-server@latest
|
|
139
184
|
```
|
|
140
|
-
|
|
185
|
+
|
|
186
|
+
### Generic MCP client (stdio)
|
|
187
|
+
|
|
188
|
+
Any client that can spawn an MCP server over stdio can use `npx -y @charlie.act7/canvas-mcp-server@latest`. The server speaks MCP 2025 (`tools`, `resources`, `prompts`).
|
|
189
|
+
|
|
190
|
+
### HTTP Server & OpenAI Custom GPTs
|
|
191
|
+
|
|
192
|
+
Run the server in HTTP mode with Fastify and interactive Swagger documentation:
|
|
193
|
+
|
|
141
194
|
```bash
|
|
142
|
-
/canvas-
|
|
195
|
+
npx @charlie.act7/canvas-mcp-server serve-http --port 3000 --host 0.0.0.0
|
|
196
|
+
# or from source:
|
|
197
|
+
npm run start:http
|
|
143
198
|
```
|
|
144
199
|
|
|
200
|
+
Visit `http://localhost:3000` to inspect interactive Swagger / OpenAPI definitions or hook into OpenAI Custom GPT Actions.
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Authentication & Credentials
|
|
205
|
+
|
|
206
|
+
### Step 1: Obtain Canvas Credentials
|
|
207
|
+
|
|
208
|
+
1. Log in to your **Canvas LMS** account.
|
|
209
|
+
2. Go to **Account** β‘οΈ **Settings** in the sidebar navigation.
|
|
210
|
+
3. Scroll to **Approved Integrations** and click **+ New Access Token**.
|
|
211
|
+
4. Enter a purpose (e.g. "Claude Canvas MCP") and click **Generate Token**.
|
|
212
|
+
5. Copy the generated token immediately and store it securely (it will not be shown again).
|
|
213
|
+
|
|
214
|
+
> [!IMPORTANT]
|
|
215
|
+
> Note down your **Canvas Domain**. This is the web address of your institution's Canvas platform, for example: `myschool.instructure.com`.
|
|
216
|
+
|
|
217
|
+
### Token Auto-Renewal
|
|
218
|
+
|
|
219
|
+
If your institution enforces token expiration, the server checks the active token's expiry before each request and automatically regenerates and persists it once it is within 24 hours of expiring.
|
|
220
|
+
|
|
221
|
+
Toggles & Tuning:
|
|
222
|
+
- `CANVAS_TOKEN_AUTO_RENEW=false` β Disable automatic token regeneration.
|
|
223
|
+
- `CANVAS_TOKEN_RENEW_THRESHOLD_HOURS=48` β Change the renewal threshold window (default: `24` hours).
|
|
224
|
+
|
|
145
225
|
---
|
|
146
226
|
|
|
147
|
-
##
|
|
148
|
-
|
|
227
|
+
## Transports
|
|
228
|
+
|
|
229
|
+
The server supports two transport modes:
|
|
230
|
+
|
|
231
|
+
### stdio (default)
|
|
232
|
+
|
|
149
233
|
```bash
|
|
150
|
-
npx @charlie.act7/canvas-mcp-server
|
|
234
|
+
npx @charlie.act7/canvas-mcp-server@latest
|
|
151
235
|
```
|
|
152
|
-
|
|
236
|
+
|
|
237
|
+
Used by desktop applications and CLI tools (Claude Desktop, Claude Code, Cursor, Codex).
|
|
238
|
+
|
|
239
|
+
### HTTP Server / Fastify REST API
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
npx @charlie.act7/canvas-mcp-server serve-http --port 3000 --host 0.0.0.0
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Optionally pass `--port <port>` (default `3000`) and `--host <host>` (default `0.0.0.0` or `127.0.0.1`).
|
|
153
246
|
|
|
154
247
|
---
|
|
155
248
|
|
|
156
|
-
##
|
|
249
|
+
## Use Cases & Prompts
|
|
157
250
|
|
|
158
|
-
|
|
159
|
-
|
|
251
|
+
> [!TIP]
|
|
252
|
+
> **Token Saving & Efficiency:** Whenever possible, specify the Canvas ID or the direct Canvas URL (e.g., `https://[your_institution].instructure.com/courses/[course_id]/assignments/[assignment_id]`) in your prompts. This prevents the AI from scanning all your courses/resources, leading to faster responses and substantial token savings.
|
|
160
253
|
|
|
161
|
-
###
|
|
254
|
+
### π Course Auditing & Querying
|
|
255
|
+
- π¬ *"What active courses do I have this semester? Check if there are multiple active sections/parallels."*
|
|
256
|
+
- π¬ *"Show me all ungraded submissions for 'Essay 1: Introduction to Sociology' in Sociology 101."*
|
|
257
|
+
- π¬ *"Who is in Student Group A for the Chemistry class?"*
|
|
258
|
+
- π¬ *"Does the assignment 'Project Proposal' have an active rubric associated? If so, retrieve its criteria."*
|
|
259
|
+
- π¬ *"Search for everything related to 'photosynthesis' across my Biology course β assignments, pages, and discussions."*
|
|
260
|
+
|
|
261
|
+
### βοΈ Creating & Organizing Course Content
|
|
262
|
+
- π¬ *"Create a new module named 'Week 1: Foundations' in my course."*
|
|
263
|
+
- π¬ *"Add a SubHeader 'REQUIRED READINGS' inside the 'Week 1' module, and link the syllabus page to it."*
|
|
264
|
+
- π¬ *"In my Business course, create an assignment called 'Case Study 1: Market Analysis'. Add an instructions table with columns for Criteria, Requirements, and Points."*
|
|
265
|
+
- π¬ *"Create a threaded discussion topic in my course titled 'Weekly Reflection' and pin it to the top."*
|
|
266
|
+
|
|
267
|
+
### π― Grading & Absence Management
|
|
268
|
+
- π¬ *"For assignment 'Case Study 1', find all students who haven't submitted their work. Assign them a grade of 0 and add the comment: 'Activity not submitted.'"*
|
|
269
|
+
- π¬ *"Grade John's submission for 'Essay 1' with a 90 based on the rubric, and add feedback."*
|
|
270
|
+
|
|
271
|
+
### π Student Engagement & Analytics
|
|
272
|
+
- π¬ *"Show me the activity analytics for my Calculus course β how active have students been this week?"*
|
|
273
|
+
- π¬ *"Which students haven't been active in course 12345 in the last few days?"*
|
|
274
|
+
|
|
275
|
+
### π¬ Messaging & Communication
|
|
276
|
+
- π¬ *"Send a private message to student [ID] reminding them their 'Project Proposal' is due tomorrow."*
|
|
277
|
+
- π¬ *"How many unread messages do I have in my Canvas inbox?"*
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Tools
|
|
282
|
+
|
|
283
|
+
The Canvas MCP Server exposes **117 tools** organized into 21 functional categories:
|
|
162
284
|
|
|
163
285
|
| Category | Tools | Description |
|
|
164
286
|
|---|---|---|
|
|
165
287
|
| **Courses** | `list_courses` Β· `create_course` Β· `update_course` Β· `get_syllabus` | Manage and configure courses |
|
|
166
288
|
| **Modules** | `list_modules` Β· `create_module` Β· `update_module` Β· `delete_module` Β· `create_module_item` Β· `update_module_item` Β· `delete_module_item` | Full CRUD for modules and their items |
|
|
167
|
-
| **Pages** | `list_pages` Β· `get_page_content` Β· `create_page` Β· `update_page` Β· `delete_page` |
|
|
289
|
+
| **Pages** | `list_pages` Β· `get_page_content` Β· `create_page` Β· `update_page` Β· `delete_page` | Wiki page publishing & editing |
|
|
168
290
|
| **Files & Folders** | `list_files` Β· `upload_file` Β· `update_file` Β· `delete_file` Β· `list_folders` Β· `create_folder` Β· `update_folder` Β· `delete_folder` | File management with folder support |
|
|
169
291
|
| **Assignments** | `get_assignments` Β· `get_assignment` Β· `create_assignment` Β· `update_assignment` Β· `delete_assignment` Β· `update_assignment_dates` Β· `bulk_update_due_dates` Β· `list_assignment_groups` | Full assignment lifecycle |
|
|
170
292
|
| **Submissions** | `get_submissions` Β· `get_submission` Β· `get_submission_comments` Β· `delete_submission_comment` Β· `submit_assignment` | View and manage student submissions |
|
|
171
293
|
| **Grading** | `grade_submission` Β· `grade_multiple_submissions` Β· `audit_course` | Grade individually or in bulk |
|
|
172
294
|
| **Rubrics** | `list_rubrics` Β· `get_rubric` Β· `create_rubric` Β· `update_rubric` Β· `create_rubric_association` | Build and attach grading rubrics |
|
|
173
|
-
| **Quizzes
|
|
295
|
+
| **Classic Quizzes** | `list_quizzes` Β· `get_quiz` Β· `create_quiz` Β· `update_quiz` Β· `update_quiz_dates` Β· `list_quiz_questions` Β· `get_quiz_question` Β· `create_quiz_question` Β· `update_quiz_question` Β· `delete_quiz_question` Β· `create_quiz_group` | Classic Canvas quiz engine |
|
|
174
296
|
| **New Quizzes (LTI)** | `create_new_quiz` Β· `update_new_quiz` Β· `delete_new_quiz` Β· `list_new_quiz_items` Β· `get_new_quiz_item` Β· `create_new_quiz_item` Β· `update_new_quiz_item` Β· `delete_new_quiz_item` | Modern LTI quiz engine (`/api/quiz/v1`) |
|
|
175
297
|
| **Students** | `list_students` Β· `list_students_with_grades` Β· `get_student_grades` Β· `get_student_assignments` Β· `list_assignment_due_dates` | Roster and progress tracking |
|
|
176
298
|
| **Enrollments** | `list_course_enrollments` Β· `enroll_user` Β· `remove_enrollment` Β· `get_user` Β· `get_profile` Β· `search_users` | Manage who is in your course |
|
|
@@ -181,40 +303,67 @@ This will guide you step-by-step to input your domain and API token, storing the
|
|
|
181
303
|
| **Calendar** | `list_appointment_groups` Β· `get_appointment_group` Β· `create_appointment_group` Β· `update_appointment_group` Β· `delete_appointment_group` Β· `list_appointment_group_users` Β· `list_appointment_group_groups` Β· `get_next_appointment` | Scheduling and appointments |
|
|
182
304
|
| **Analytics** | `get_course_analytics` Β· `get_student_analytics` Β· `get_course_activity_stream` Β· `search_course_content` | Engagement data and content search |
|
|
183
305
|
| **Peer Reviews** | `list_peer_reviews` Β· `get_submission_peer_reviews` Β· `create_peer_review` Β· `delete_peer_review` | Configure and manage peer assessments |
|
|
184
|
-
| **
|
|
306
|
+
| **Access Tokens** | `list_access_tokens` Β· `get_access_token` Β· `create_access_token` Β· `update_access_token` Β· `regenerate_access_token` Β· `delete_access_token` | Manage Canvas API tokens with auto-renewal |
|
|
307
|
+
| **Health & Config** | `health_check` Β· `set_canvas_config` | Connection checks & runtime configuration updates |
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## Resources
|
|
312
|
+
|
|
313
|
+
Supported native MCP resources:
|
|
314
|
+
|
|
315
|
+
| Resource URI | Description |
|
|
316
|
+
|---|---|
|
|
317
|
+
| `canvas://courses/{id}/readme` | Formatted Markdown course summary |
|
|
318
|
+
| `canvas://courses/{id}/pages/{slug}` | Direct HTML content of Canvas pages |
|
|
185
319
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## Configuration Reference
|
|
323
|
+
|
|
324
|
+
All configuration parameters can be passed as environment variables or stored via CLI configuration (`npx @charlie.act7/canvas-mcp-server config`).
|
|
325
|
+
|
|
326
|
+
| Env Var | Default | Purpose |
|
|
327
|
+
|---|---|---|
|
|
328
|
+
| `CANVAS_API_TOKEN` | _(unset)_ | Canvas LMS API access token. |
|
|
329
|
+
| `CANVAS_API_DOMAIN` | _(unset)_ | Domain of your Canvas LMS instance (e.g. `myschool.instructure.com`). |
|
|
330
|
+
| `CANVAS_TOKEN_AUTO_RENEW` | `true` | Automatically regenerate expiring access tokens before expiration. |
|
|
331
|
+
| `CANVAS_TOKEN_RENEW_THRESHOLD_HOURS` | `24` | Threshold in hours before token expiry to trigger auto-renewal. |
|
|
332
|
+
| `PORT` / `HTTP_PORT` | `3000` | HTTP port when running `serve-http`. |
|
|
333
|
+
| `HTTP_HOST` | `0.0.0.0` | Host interface binding for HTTP server mode. |
|
|
334
|
+
| `GEMINI_API_KEY` | _(unset)_ | (Optional) Gemini API key for local LLM bridge / script utilities. |
|
|
335
|
+
| `OLLAMA_HOST` | `http://localhost:11434` | (Optional) Local Ollama host address for local model bridge. |
|
|
191
336
|
|
|
192
337
|
---
|
|
193
338
|
|
|
194
|
-
##
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
339
|
+
## Development
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
npm run build # Compile TypeScript source code (tsc)
|
|
343
|
+
npm run dev # Watch mode compilation (tsc --watch)
|
|
344
|
+
npm start # Run MCP server in stdio mode
|
|
345
|
+
npm run start:http # Run HTTP server with Swagger UI at http://localhost:3000
|
|
346
|
+
npm run chat # Launch interactive Ollama bridge test CLI
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Source Layout
|
|
350
|
+
|
|
351
|
+
- `src/index.ts` β CLI entry point (Commander), stdio MCP server bootstrap
|
|
352
|
+
- `src/http-server.ts` β Fastify HTTP server, Swagger / OpenAPI route definitions
|
|
353
|
+
- `src/common/` β `canvas-client.ts` (API client & token auto-renewal), `config.ts` (`conf` manager), `types.ts`
|
|
354
|
+
- `src/tools/` β 117+ tool implementations divided by functional module
|
|
355
|
+
- `src/resources/` β MCP resource providers (`canvas://`)
|
|
356
|
+
- `src/prompts/` β MCP prompt templates
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## Documentation
|
|
361
|
+
|
|
362
|
+
- [`README.es.md`](./README.es.md) β VersiΓ³n en EspaΓ±ol del README.
|
|
363
|
+
- [`llms-install.md`](./llms-install.md) β Detailed guide for client integration (Cursor, Copilot, Custom GPTs).
|
|
216
364
|
|
|
217
365
|
---
|
|
218
366
|
|
|
219
367
|
## License
|
|
220
|
-
|
|
368
|
+
|
|
369
|
+
This project is licensed under the **MIT License**. Created by [Charlie CΓ‘rdenas Toledo](https://github.com/CharlieCardenasToledo).
|
package/dist/index.js
CHANGED
|
@@ -28,6 +28,7 @@ import { conversationTools } from "./tools/conversation-tools.js";
|
|
|
28
28
|
import { newQuizTools } from "./tools/new-quiz-tools.js";
|
|
29
29
|
import { analyticsTools } from "./tools/analytics-tools.js";
|
|
30
30
|
import { peerReviewTools } from "./tools/peer-review-tools.js";
|
|
31
|
+
import { accessTokenTools } from "./tools/access-token-tools.js";
|
|
31
32
|
import { canvasResources } from "./resources/canvas-resources.js";
|
|
32
33
|
import { canvasPrompts } from "./prompts/canvas-prompts.js";
|
|
33
34
|
import { startHttpServer } from "./http-server.js";
|
|
@@ -85,7 +86,16 @@ function getClient() {
|
|
|
85
86
|
console.error(`Please run ${chalk.cyan("canvas-mcp config")} or set env vars.`);
|
|
86
87
|
process.exit(1);
|
|
87
88
|
}
|
|
88
|
-
return new CanvasClient(token, domain
|
|
89
|
+
return new CanvasClient(token, domain, {
|
|
90
|
+
autoRenewToken: process.env.CANVAS_TOKEN_AUTO_RENEW !== "false",
|
|
91
|
+
renewThresholdHours: process.env.CANVAS_TOKEN_RENEW_THRESHOLD_HOURS
|
|
92
|
+
? Number.parseFloat(process.env.CANVAS_TOKEN_RENEW_THRESHOLD_HOURS)
|
|
93
|
+
: undefined,
|
|
94
|
+
onTokenRenewed: (newToken) => {
|
|
95
|
+
configManager.set("CANVAS_API_TOKEN", newToken);
|
|
96
|
+
console.error(chalk.green("Canvas access token auto-renewed and persisted."));
|
|
97
|
+
}
|
|
98
|
+
});
|
|
89
99
|
}
|
|
90
100
|
// NOTE: We could keep CLI commands for grading/auditing here calling the client directly,
|
|
91
101
|
// but for the sake of the MCP Server refactor, we are focusing on the 'start' command.
|
|
@@ -125,7 +135,8 @@ program
|
|
|
125
135
|
...conversationTools,
|
|
126
136
|
...newQuizTools,
|
|
127
137
|
...analyticsTools,
|
|
128
|
-
...peerReviewTools
|
|
138
|
+
...peerReviewTools,
|
|
139
|
+
...accessTokenTools
|
|
129
140
|
]);
|
|
130
141
|
// --- Tool Handlers ---
|
|
131
142
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
@@ -202,7 +213,8 @@ program
|
|
|
202
213
|
...conversationTools,
|
|
203
214
|
...newQuizTools,
|
|
204
215
|
...analyticsTools,
|
|
205
|
-
...peerReviewTools
|
|
216
|
+
...peerReviewTools,
|
|
217
|
+
...accessTokenTools
|
|
206
218
|
]);
|
|
207
219
|
await startHttpServer(client, options.host, port, allTools);
|
|
208
220
|
});
|
|
@@ -4,11 +4,22 @@ export class CanvasClient {
|
|
|
4
4
|
quizClient;
|
|
5
5
|
token;
|
|
6
6
|
domain;
|
|
7
|
-
|
|
7
|
+
autoRenewToken;
|
|
8
|
+
renewThresholdMs;
|
|
9
|
+
onTokenRenewed;
|
|
10
|
+
tokenMetaCacheMs = 15 * 60 * 1000;
|
|
11
|
+
tokenMeta = null;
|
|
12
|
+
tokenMetaCheckedAt = 0;
|
|
13
|
+
isCheckingToken = false;
|
|
14
|
+
constructor(token, domain, options = {}) {
|
|
8
15
|
this.token = token;
|
|
9
16
|
this.domain = domain;
|
|
17
|
+
this.autoRenewToken = options.autoRenewToken ?? true;
|
|
18
|
+
this.renewThresholdMs = (options.renewThresholdHours ?? 24) * 60 * 60 * 1000;
|
|
19
|
+
this.onTokenRenewed = options.onTokenRenewed;
|
|
10
20
|
this.client = this.createAxiosInstance(token, domain);
|
|
11
21
|
this.quizClient = this.createAxiosInstance(token, domain, 'api/quiz/v1');
|
|
22
|
+
this.attachAutoRenewInterceptor();
|
|
12
23
|
}
|
|
13
24
|
createAxiosInstance(token, domain, apiPath = 'api/v1') {
|
|
14
25
|
const baseURL = `https://${domain}/${apiPath}`;
|
|
@@ -25,6 +36,71 @@ export class CanvasClient {
|
|
|
25
36
|
this.domain = domain;
|
|
26
37
|
this.client = this.createAxiosInstance(token, domain);
|
|
27
38
|
this.quizClient = this.createAxiosInstance(token, domain, 'api/quiz/v1');
|
|
39
|
+
this.tokenMeta = null;
|
|
40
|
+
this.tokenMetaCheckedAt = 0;
|
|
41
|
+
this.attachAutoRenewInterceptor();
|
|
42
|
+
}
|
|
43
|
+
attachAutoRenewInterceptor() {
|
|
44
|
+
this.client.interceptors.request.use(async (config) => {
|
|
45
|
+
if (this.autoRenewToken && !this.isCheckingToken) {
|
|
46
|
+
await this.maybeRenewToken();
|
|
47
|
+
}
|
|
48
|
+
return config;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
applyNewToken(newToken) {
|
|
52
|
+
this.token = newToken;
|
|
53
|
+
this.client.defaults.headers['Authorization'] = `Bearer ${newToken}`;
|
|
54
|
+
this.quizClient.defaults.headers['Authorization'] = `Bearer ${newToken}`;
|
|
55
|
+
}
|
|
56
|
+
// Canvas only returns the full token value on creation/regeneration, never on list/get.
|
|
57
|
+
// To identify which listed token corresponds to the one currently authenticating this
|
|
58
|
+
// client, we match its (short) token_hint as a substring of the known full token value.
|
|
59
|
+
async refreshTokenMeta() {
|
|
60
|
+
this.isCheckingToken = true;
|
|
61
|
+
try {
|
|
62
|
+
const tokens = await this.getAllPages('users/self/user_generated_tokens', { per_page: 100 });
|
|
63
|
+
const match = tokens.find(t => t.token_hint && this.token.includes(t.token_hint));
|
|
64
|
+
this.tokenMeta = match
|
|
65
|
+
? { id: match.id, expiresAtMs: match.expires_at ? new Date(match.expires_at).getTime() : null }
|
|
66
|
+
: null;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// Don't block requests if we can't determine token metadata (e.g. insufficient permissions).
|
|
70
|
+
this.tokenMeta = null;
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
this.tokenMetaCheckedAt = Date.now();
|
|
74
|
+
this.isCheckingToken = false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async maybeRenewToken() {
|
|
78
|
+
const now = Date.now();
|
|
79
|
+
if (now - this.tokenMetaCheckedAt >= this.tokenMetaCacheMs) {
|
|
80
|
+
await this.refreshTokenMeta();
|
|
81
|
+
}
|
|
82
|
+
if (!this.tokenMeta || this.tokenMeta.expiresAtMs === null)
|
|
83
|
+
return;
|
|
84
|
+
if (this.tokenMeta.expiresAtMs - now > this.renewThresholdMs)
|
|
85
|
+
return;
|
|
86
|
+
this.isCheckingToken = true;
|
|
87
|
+
try {
|
|
88
|
+
const response = await this.client.put(`users/self/tokens/${this.tokenMeta.id}`, {
|
|
89
|
+
token: { regenerate: true }
|
|
90
|
+
});
|
|
91
|
+
if (response.data.token) {
|
|
92
|
+
this.applyNewToken(response.data.token);
|
|
93
|
+
this.onTokenRenewed?.(response.data.token);
|
|
94
|
+
}
|
|
95
|
+
this.tokenMeta = {
|
|
96
|
+
id: this.tokenMeta.id,
|
|
97
|
+
expiresAtMs: response.data.expires_at ? new Date(response.data.expires_at).getTime() : null
|
|
98
|
+
};
|
|
99
|
+
this.tokenMetaCheckedAt = Date.now();
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
this.isCheckingToken = false;
|
|
103
|
+
}
|
|
28
104
|
}
|
|
29
105
|
parseLinkHeader(header) {
|
|
30
106
|
if (!header)
|
|
@@ -772,4 +848,41 @@ export class CanvasClient {
|
|
|
772
848
|
await this.quizClient.delete(`courses/${courseId}/quizzes/${quizId}/items/${itemId}`);
|
|
773
849
|
return { deleted: true };
|
|
774
850
|
}
|
|
851
|
+
// --- Access Tokens ---
|
|
852
|
+
async listAccessTokens(userId = 'self') {
|
|
853
|
+
return this.getAllPages(`users/${userId}/user_generated_tokens`, { per_page: 100 });
|
|
854
|
+
}
|
|
855
|
+
async getAccessToken(userId = 'self', tokenId) {
|
|
856
|
+
const response = await this.client.get(`users/${userId}/tokens/${tokenId}`);
|
|
857
|
+
return response.data;
|
|
858
|
+
}
|
|
859
|
+
async createAccessToken(userId = 'self', data) {
|
|
860
|
+
const response = await this.client.post(`users/${userId}/tokens`, { token: data });
|
|
861
|
+
return response.data;
|
|
862
|
+
}
|
|
863
|
+
async updateAccessToken(userId = 'self', tokenId, data) {
|
|
864
|
+
const response = await this.client.put(`users/${userId}/tokens/${tokenId}`, { token: data });
|
|
865
|
+
return response.data;
|
|
866
|
+
}
|
|
867
|
+
async deleteAccessToken(userId = 'self', tokenId) {
|
|
868
|
+
await this.client.delete(`users/${userId}/tokens/${tokenId}`);
|
|
869
|
+
return { deleted: true };
|
|
870
|
+
}
|
|
871
|
+
// Convenience wrapper around updateAccessToken({ regenerate: true }) that, when the
|
|
872
|
+
// regenerated token is the one currently authenticating this client, also hot-swaps
|
|
873
|
+
// and persists it so the MCP server keeps working without a manual reconfiguration.
|
|
874
|
+
async regenerateAccessToken(userId = 'self', tokenId) {
|
|
875
|
+
const result = await this.updateAccessToken(userId, tokenId, { regenerate: true });
|
|
876
|
+
const isActiveToken = userId === 'self' && this.tokenMeta?.id === Number(tokenId);
|
|
877
|
+
if (isActiveToken && result.token) {
|
|
878
|
+
this.applyNewToken(result.token);
|
|
879
|
+
this.onTokenRenewed?.(result.token);
|
|
880
|
+
this.tokenMeta = {
|
|
881
|
+
id: this.tokenMeta.id,
|
|
882
|
+
expiresAtMs: result.expires_at ? new Date(result.expires_at).getTime() : null
|
|
883
|
+
};
|
|
884
|
+
this.tokenMetaCheckedAt = Date.now();
|
|
885
|
+
}
|
|
886
|
+
return result;
|
|
887
|
+
}
|
|
775
888
|
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const userIdSchema = z.union([z.number(), z.string()]).optional();
|
|
3
|
+
export const accessTokenTools = [
|
|
4
|
+
{
|
|
5
|
+
name: "canvas_list_access_tokens",
|
|
6
|
+
tool: {
|
|
7
|
+
name: "canvas_list_access_tokens",
|
|
8
|
+
description: "List manually-generated Canvas API access tokens for a user (defaults to the authenticated user)",
|
|
9
|
+
inputSchema: {
|
|
10
|
+
type: "object",
|
|
11
|
+
properties: {
|
|
12
|
+
user_id: {
|
|
13
|
+
anyOf: [{ type: "number" }, { type: "string" }],
|
|
14
|
+
description: "User ID, or 'self' for the authenticated user (default)"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
handler: async (client, args) => {
|
|
20
|
+
const input = z.object({ user_id: userIdSchema }).parse(args ?? {});
|
|
21
|
+
const result = await client.listAccessTokens(input.user_id ?? 'self');
|
|
22
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: "canvas_get_access_token",
|
|
27
|
+
tool: {
|
|
28
|
+
name: "canvas_get_access_token",
|
|
29
|
+
description: "Get details of a specific Canvas access token by its database ID or token_hint",
|
|
30
|
+
inputSchema: {
|
|
31
|
+
type: "object",
|
|
32
|
+
properties: {
|
|
33
|
+
user_id: {
|
|
34
|
+
anyOf: [{ type: "number" }, { type: "string" }],
|
|
35
|
+
description: "User ID, or 'self' for the authenticated user (default)"
|
|
36
|
+
},
|
|
37
|
+
token_id: {
|
|
38
|
+
anyOf: [{ type: "number" }, { type: "string" }],
|
|
39
|
+
description: "The token's database ID or its token_hint"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
required: ["token_id"]
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
handler: async (client, args) => {
|
|
46
|
+
const input = z.object({
|
|
47
|
+
user_id: userIdSchema,
|
|
48
|
+
token_id: z.union([z.number(), z.string()])
|
|
49
|
+
}).parse(args);
|
|
50
|
+
const result = await client.getAccessToken(input.user_id ?? 'self', input.token_id);
|
|
51
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: "canvas_create_access_token",
|
|
56
|
+
tool: {
|
|
57
|
+
name: "canvas_create_access_token",
|
|
58
|
+
description: "Create a new Canvas API access token. The full token value is returned only in this response β save it, it cannot be retrieved again later.",
|
|
59
|
+
inputSchema: {
|
|
60
|
+
type: "object",
|
|
61
|
+
properties: {
|
|
62
|
+
user_id: {
|
|
63
|
+
anyOf: [{ type: "number" }, { type: "string" }],
|
|
64
|
+
description: "User ID, or 'self' for the authenticated user (default)"
|
|
65
|
+
},
|
|
66
|
+
purpose: { type: "string", description: "Description of what the token is for" },
|
|
67
|
+
expires_at: { type: "string", description: "ISO 8601 datetime when the token should expire (omit for no expiration)" },
|
|
68
|
+
scopes: {
|
|
69
|
+
type: "array",
|
|
70
|
+
items: { type: "string" },
|
|
71
|
+
description: "API scopes to restrict the token to (ignored unless the account has scoped tokens enabled)"
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
required: ["purpose"]
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
handler: async (client, args) => {
|
|
78
|
+
const input = z.object({
|
|
79
|
+
user_id: userIdSchema,
|
|
80
|
+
purpose: z.string(),
|
|
81
|
+
expires_at: z.string().optional(),
|
|
82
|
+
scopes: z.array(z.string()).optional()
|
|
83
|
+
}).parse(args);
|
|
84
|
+
const { user_id, ...data } = input;
|
|
85
|
+
const result = await client.createAccessToken(user_id ?? 'self', data);
|
|
86
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: "canvas_update_access_token",
|
|
91
|
+
tool: {
|
|
92
|
+
name: "canvas_update_access_token",
|
|
93
|
+
description: "Update a Canvas access token's purpose, expiration, or scopes",
|
|
94
|
+
inputSchema: {
|
|
95
|
+
type: "object",
|
|
96
|
+
properties: {
|
|
97
|
+
user_id: {
|
|
98
|
+
anyOf: [{ type: "number" }, { type: "string" }],
|
|
99
|
+
description: "User ID, or 'self' for the authenticated user (default)"
|
|
100
|
+
},
|
|
101
|
+
token_id: {
|
|
102
|
+
anyOf: [{ type: "number" }, { type: "string" }],
|
|
103
|
+
description: "The token's database ID or its token_hint"
|
|
104
|
+
},
|
|
105
|
+
purpose: { type: "string", description: "New description of what the token is for" },
|
|
106
|
+
expires_at: { type: "string", description: "New ISO 8601 expiration datetime, or null to remove expiration" },
|
|
107
|
+
scopes: { type: "array", items: { type: "string" }, description: "New API scopes for the token" }
|
|
108
|
+
},
|
|
109
|
+
required: ["token_id"]
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
handler: async (client, args) => {
|
|
113
|
+
const input = z.object({
|
|
114
|
+
user_id: userIdSchema,
|
|
115
|
+
token_id: z.union([z.number(), z.string()]),
|
|
116
|
+
purpose: z.string().optional(),
|
|
117
|
+
expires_at: z.string().nullable().optional(),
|
|
118
|
+
scopes: z.array(z.string()).optional()
|
|
119
|
+
}).parse(args);
|
|
120
|
+
const { user_id, token_id, ...data } = input;
|
|
121
|
+
const result = await client.updateAccessToken(user_id ?? 'self', token_id, data);
|
|
122
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: "canvas_regenerate_access_token",
|
|
127
|
+
tool: {
|
|
128
|
+
name: "canvas_regenerate_access_token",
|
|
129
|
+
description: "Regenerate a Canvas access token, issuing a new token value while keeping its purpose/scopes. If the regenerated token is the one currently authenticating this MCP server, it is automatically applied and persisted so the server keeps working without reconfiguration.",
|
|
130
|
+
inputSchema: {
|
|
131
|
+
type: "object",
|
|
132
|
+
properties: {
|
|
133
|
+
user_id: {
|
|
134
|
+
anyOf: [{ type: "number" }, { type: "string" }],
|
|
135
|
+
description: "User ID, or 'self' for the authenticated user (default)"
|
|
136
|
+
},
|
|
137
|
+
token_id: {
|
|
138
|
+
anyOf: [{ type: "number" }, { type: "string" }],
|
|
139
|
+
description: "The token's database ID or its token_hint"
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
required: ["token_id"]
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
handler: async (client, args) => {
|
|
146
|
+
const input = z.object({
|
|
147
|
+
user_id: userIdSchema,
|
|
148
|
+
token_id: z.union([z.number(), z.string()])
|
|
149
|
+
}).parse(args);
|
|
150
|
+
const result = await client.regenerateAccessToken(input.user_id ?? 'self', input.token_id);
|
|
151
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
name: "canvas_delete_access_token",
|
|
156
|
+
tool: {
|
|
157
|
+
name: "canvas_delete_access_token",
|
|
158
|
+
description: "Permanently delete a Canvas access token",
|
|
159
|
+
inputSchema: {
|
|
160
|
+
type: "object",
|
|
161
|
+
properties: {
|
|
162
|
+
user_id: {
|
|
163
|
+
anyOf: [{ type: "number" }, { type: "string" }],
|
|
164
|
+
description: "User ID, or 'self' for the authenticated user (default)"
|
|
165
|
+
},
|
|
166
|
+
token_id: {
|
|
167
|
+
anyOf: [{ type: "number" }, { type: "string" }],
|
|
168
|
+
description: "The token's database ID or its token_hint"
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
required: ["token_id"]
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
handler: async (client, args) => {
|
|
175
|
+
const input = z.object({
|
|
176
|
+
user_id: userIdSchema,
|
|
177
|
+
token_id: z.union([z.number(), z.string()])
|
|
178
|
+
}).parse(args);
|
|
179
|
+
const result = await client.deleteAccessToken(input.user_id ?? 'self', input.token_id);
|
|
180
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
];
|
package/dist/tools/quiz-tools.js
CHANGED
|
@@ -196,18 +196,18 @@ export const quizTools = [
|
|
|
196
196
|
name: "canvas_create_quiz_question",
|
|
197
197
|
tool: {
|
|
198
198
|
name: "canvas_create_quiz_question",
|
|
199
|
-
description: `Create a new question in a quiz. Supports all 12 Canvas question types:
|
|
200
|
-
- multiple_choice_question: answers with answer_text + answer_weight (100=correct, 0=wrong)
|
|
201
|
-
- true_false_question: two answers ("True"/"False") with answer_weight
|
|
202
|
-
- short_answer_question: one or more correct answer_text values, all with answer_weight 100
|
|
203
|
-
- fill_in_multiple_blanks_question: answers with blank_id + answer_text + answer_weight 100
|
|
204
|
-
- multiple_answers_question: checkboxes; correct answers get answer_weight 100, wrong get 0
|
|
205
|
-
- multiple_dropdowns_question: answers with blank_id + answer_text + answer_weight
|
|
206
|
-
- matching_question: answers with answer_match_left + answer_match_right; add distractors via matching_answer_incorrect_matches on any one answer
|
|
207
|
-
- numerical_question: answers with numerical_answer_type (exact_answer: exact+margin; range_answer: start+end; precision_answer: approximate+precision)
|
|
208
|
-
- calculated_question: answers with variables and formulas (advanced)
|
|
209
|
-
- essay_question: no answers needed
|
|
210
|
-
- file_upload_question: no answers needed
|
|
199
|
+
description: `Create a new question in a quiz. Supports all 12 Canvas question types:
|
|
200
|
+
- multiple_choice_question: answers with answer_text + answer_weight (100=correct, 0=wrong)
|
|
201
|
+
- true_false_question: two answers ("True"/"False") with answer_weight
|
|
202
|
+
- short_answer_question: one or more correct answer_text values, all with answer_weight 100
|
|
203
|
+
- fill_in_multiple_blanks_question: answers with blank_id + answer_text + answer_weight 100
|
|
204
|
+
- multiple_answers_question: checkboxes; correct answers get answer_weight 100, wrong get 0
|
|
205
|
+
- multiple_dropdowns_question: answers with blank_id + answer_text + answer_weight
|
|
206
|
+
- matching_question: answers with answer_match_left + answer_match_right; add distractors via matching_answer_incorrect_matches on any one answer
|
|
207
|
+
- numerical_question: answers with numerical_answer_type (exact_answer: exact+margin; range_answer: start+end; precision_answer: approximate+precision)
|
|
208
|
+
- calculated_question: answers with variables and formulas (advanced)
|
|
209
|
+
- essay_question: no answers needed
|
|
210
|
+
- file_upload_question: no answers needed
|
|
211
211
|
- text_only_question: no answers needed, just displays text`,
|
|
212
212
|
inputSchema: {
|
|
213
213
|
type: "object",
|
package/package.json
CHANGED