Skip to main content
Glama
RyK57

canvas-mcp-server

by RyK57

canvas-mcp-server

用于 Canvas LMS REST API 的 MCP 服务器。为 LLM 提供对课程、作业、成绩、提交、公告、讨论、模块、页面和文件的读取访问。

20 个工具,全部只读。

要求

  • Node.js 18+

  • 任何机构的 Canvas 账户

  • 在 Canvas 网页界面中,从 账户 → 设置 → 新建访问令牌 获取访问令牌

Related MCP server: Canvas LMS MCP Server

安装

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_BASE_URL

是

—

你所在机构的 Canvas 主机,包含协议,无尾部路径

CANVAS_ACCESS_TOKEN

是

—

账户 → 设置 → 新建访问令牌

CANVAS_REQUEST_TIMEOUT_MS

否

30000

每次请求的超时时间

TRANSPORT

否

stdio

stdio 或 http

PORT / HOST

否

3000 / 127.0.0.1

HTTP 传输绑定地址

MCP_PATH_SECRET

托管时

—

在 /mcp/<secret> 提供端点。当 HOST 不是回环地址时必需

ALLOWED_ORIGINS

否

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_BASE_URL

你所在机构的 Canvas 主机

CANVAS_ACCESS_TOKEN

你的令牌

MCP_PATH_SECRET

步骤 1 中的值

PORT 由平台注入。/healthz 是一个无需认证的存活探针。

3. 验证

curl -s https://your-app.up.railway.app/healthz

4. 添加连接器

在 claude.ai 上通过浏览器——无法从移动应用添加连接器:

  1. 自定义 → 连接器 → 添加自定义连接器

  2. URL:https://your-app.up.railway.app/mcp/<secret>

  3. 在手机上打开聊天,并在 + → 连接器 下启用它。

将该 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

两个测试套件都针对本地模拟运行,因此无需令牌或网络访问。

Related MCP Connectors

Related MCP Servers