canvas-mcp-server
canvas-mcp-server
用于 Canvas LMS REST API 的 MCP 服务器。为 LLM 提供对课程、作业、成绩、提交、公告、讨论、模块、页面和文件的读取访问。
20 个工具,全部只读。
要求
Node.js 18+
任何机构的 Canvas 账户
在 Canvas 网页界面中,从 账户 → 设置 → 新建访问令牌 获取访问令牌
安装
npm install
npm run build配置
Canvas 没有共享的 API 主机——每个机构都运行自己的实例。以下两个变量都是必需的。
{
"mcpServers": {
"canvas": {
"command": "node",
"args": ["/absolute/path/to/canvas-mcp-server/dist/index.js"],
"env": {
"CANVAS_BASE_URL": "https://bcourses.berkeley.edu",
"CANVAS_ACCESS_TOKEN": "your-token-here"
}
}
}
}变量 | 必需 | 默认值 | 用途 |
| 是 | — | 你所在机构的 Canvas 主机,包含协议,无尾部路径 |
| 是 | — | 账户 → 设置 → 新建访问令牌 |
| 否 |
| 每次请求的超时时间 |
| 否 |
|
|
| 否 |
| HTTP 传输绑定地址 |
| 托管时 | — | 在 |
| 否 | localhost + claude.ai | 逗号分隔的来源允许列表 |
交互式检查工具:
CANVAS_BASE_URL=https://your.canvas CANVAS_ACCESS_TOKEN=your-token npm run inspect部署(适用于 Claude 移动端 / claude.ai 连接器)
Claude 从 Anthropic 的云端连接到自定义连接器,而不是从你的设备,因此移动端和 claude.ai 需要通过公共 HTTPS 访问此服务。Claude Code 和 Claude Desktop 则不需要——请改用 stdio。
1. 生成路径密钥
openssl rand -hex 32如果没有设置 MCP_PATH_SECRET,服务器将拒绝在非回环接口上启动,因为持有你的 Canvas 令牌的公共端点相当于你账户的开放代理。设置后,端点将移至 /mcp/<secret>,所有其他路径都返回 404——包括错误的密钥,因此探测主机不会暴露那里存在 MCP 服务器。
2. 部署
附带的 Dockerfile 和 railway.json 可直接在 Railway、Render 或 Fly 上使用。该镜像设置 TRANSPORT=http 和 HOST=0.0.0.0,并以非 root 用户运行。在平台的控制面板中设置三个变量:
变量 | 值 |
| 你所在机构的 Canvas 主机 |
| 你的令牌 |
| 步骤 1 中的值 |
PORT 由平台注入。/healthz 是一个无需认证的存活探针。
3. 验证
curl -s https://your-app.up.railway.app/healthz4. 添加连接器
在 claude.ai 上通过浏览器——无法从移动应用添加连接器:
自定义 → 连接器 → 添加自定义连接器
URL:
https://your-app.up.railway.app/mcp/<secret>在手机上打开聊天,并在 + → 连接器 下启用它。
将该 URL 视为密码。如果泄露,请轮换 MCP_PATH_SECRET 并重新添加连接器。
工具
课程 — canvas_list_courses, canvas_get_course, canvas_get_grades, canvas_list_enrollments, canvas_get_profile
作业 — canvas_list_assignments, canvas_get_assignment, canvas_get_submission, canvas_list_quizzes
计划器 — canvas_list_planner_items, canvas_list_upcoming, canvas_list_calendar_events
公告与讨论 — canvas_list_announcements, canvas_list_discussions, canvas_get_discussion
课程内容 — canvas_list_modules, canvas_list_module_items, canvas_list_pages, canvas_get_page, canvas_list_files
每个读取工具都接受 response_format: "markdown" | "json"。Markdown 是默认格式,针对 LLM 阅读进行了优化;JSON 是完整的结构化负载。无论格式如何,structuredContent 始终会被填充。
示例
“这周有什么到期?”
→ 使用 canvas_list_planner_items,并将 end_date 设为一周后。一次调用即可覆盖所有课程,并报告提交状态。默认从今天开始,因此对于“我落后了什么”,请显式传入更早的 start_date。
“我的成绩如何?”
→ 使用 canvas_get_grades。一次调用即可获取所有活跃课程的当前分数和字母等级。
“我的教授这周宣布了什么?”
→ 先使用 canvas_list_courses 获取 ID,然后一次性使用 canvas_list_announcements 获取所有公告。
“对于项目 2,我实际上需要做什么?”
→ 使用 canvas_list_assignments 并设置 search_term="project 2" 获取 ID,然后使用 canvas_get_assignment 获取完整说明。
设计说明
构造上只读。 每个工具都带有 readOnlyHint: true 和 destructiveHint: false,并且客户端没有暴露任何写入路径。Canvas 令牌拥有你账户的全部权限——它们可以提交作业、发布讨论和更改个人资料设置——因此服务器故意不暴露任何这些功能。测试对此进行了断言:如果添加了写入工具,测试套件将失败。
基础 URL 是必需的,而不是默认的。 与单租户 API 不同,Canvas 为每个机构运行一个实例。没有合理的默认值,一个学校 Canvas 颁发的令牌在另一个学校毫无意义,因此服务器在启动时失败,而不是稍后用 401 误导你。
分页信息位于头部。 Canvas 在 RFC 5988 Link 头部中报告“是否有下一页”,并且从不返回总数。这些 URL 被记录为不透明的,因此 has_more 从头部读取,而 page/per_page 仍然是面向调用者的控制项——代理获得一个简单的 next_page 来跟随,而不是需要处理的游标。
ID 以字符串形式请求。 Canvas ID 是 64 位整数,JavaScript 无法精确表示。客户端发送 Accept: application/json+canvas-string-ids,Canvas 会遵守该请求,将所有 ID 作为字符串返回,因此 ID 在 JSON 往返中保持完整。
HTML 在到达模型之前被扁平化。 作业描述、公告、讨论帖子和页面都以 HTML 存储。逐字传递会消耗大量上下文,因此标签变为换行符,实体被解码,长正文被摘录,并保留 html_url 以获取完整版本。
include[] 未暴露。 Canvas 有二十多个 include 选项,它们在列表和单课程端点之间有所不同,并且大多数控制字段对代理没有用处。每个工具只请求它需要的内容,并仅暴露会改变用户所见内容的开关——include_syllabus、include_grades、include_submission。
课程 ID 被规范化为上下文代码。 某些 Canvas 端点以 course_1234 而不是 1234 来寻址课程。两种形式在所有地方都被接受并转换,因此代理无需记住哪个端点需要哪种形式。
错误解析为后续操作。 404 会指出为该资源生成有效 ID 的工具。403 区分权限问题和速率限制耗尽,而 Canvas 令人困惑地在同一状态下返回这两者。401 指出一个学校 Canvas 的令牌在另一个学校无法使用。
两个 Canvas 怪癖被处理而不是传递。 课程在 enrollments[].computed_current_score 下报告的成绩与 Enrollments API 所称的 grades.current_score 是同一个数字;两者都会被读取。此外,当没有可提交的内容时,计划器项目的 submissions 字段是布尔值 false——而不是对象——在读取之前会进行检查。
注意事项
公告无法全局列出:Canvas 要求至少一个课程 ID,因此必须先运行
canvas_list_courses。canvas_list_discussions在分页之后应用其scope过滤器,因此过滤后的页面可能比per_page短,但并非结果结束。对于被认为较大的模块,Canvas 会从列表响应中省略模块项;
canvas_list_module_items会获取它们。页面通过 URL 别名(
week-1-reading)寻址,而不是标题。canvas_list_pages在其url字段中返回别名。日历端点最多接受 10 个课程,并静默忽略其余课程;
canvas_list_calendar_events会在修剪时报告。成绩仅反映教师已发布的内容,对于配置为隐藏最终成绩的课程,成绩会被完全省略。
项目结构
src/
├── index.ts # entry point, transport selection
├── constants.ts # enum values, limits, character limit
├── types.ts # interfaces for every Canvas entity
├── services/
│ └── canvas-client.ts # fetch wrapper, auth, Link pagination, error → guidance mapping
├── schemas/
│ ├── inputs.ts # Zod input schemas
│ └── outputs.ts # structuredContent schemas
├── formatters/
│ ├── response.ts # pagination, truncation, HTML flattening, format dispatch
│ └── entities.ts # per-entity markdown rendering
└── tools/
├── courses.ts
├── assignments.ts
├── planner.ts
├── announcements.ts
└── content.ts测试
npm run build
npm test # 43 checks: MCP handshake, tools, pagination, formatting, errors (mocked API)
npm run test:http # 19 checks: config validation, path-secret gating, method handling, origins两个测试套件都针对本地模拟运行,因此无需令牌或网络访问。
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Gateway between LLM agents and world data through eight tools and a bundled endpoint catalog.
Read-only MCP server for ClassQuill, a tutoring-business-management platform.
Read-only MCP server for Muovi, Argentina's trust-first local services marketplace (6 tools).
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/RyK57/canvas-mcp-server'
If you have feedback or need assistance with the MCP directory API, please join our Discord server