Skip to main content

canvas-teacher-mcp

An MCP server for Canvas LMS, from the instructor's side. Ask your assistant to read a course, build an assignment page or a quiz, post an announcement, or run a grading pass — it calls Canvas for you.

v0.1.0. Everything is created unpublished and nothing is ever deleted.

Install

Point your MCP client at it. Nothing to install by hand — uvx fetches it on first run.

{
  "mcpServers": {
    "canvas-teacher": {
      "command": "uvx",
      "args": ["canvas-teacher-mcp"],
      "env": { "CANVAS_LMS_ROOT": "/absolute/path/to/your/teaching/folder" }
    }
  }
}

Claude Desktop — Settings → Developer → Edit Config opens the file. Add the entry inside the existing mcpServers object rather than replacing it, then quit and reopen the app. A GUI app does not inherit your shell PATH, so if the server does not appear, give the full path to uvx (which uvx will tell you).

Claude Code — the same JSON in .mcp.json at your project root. Servers attach when a session starts, so open a new one.

Codex~/.codex/config.toml, TOML rather than JSON, with the environment in a table of its own:

[mcp_servers.canvas-teacher]
command = "uvx"
args = ["canvas-teacher-mcp"]

[mcp_servers.canvas-teacher.env]
CANVAS_LMS_ROOT = "/Users/you/Teaching"

Use a plain chat or the codex CLI: inside a Codex Desktop project, no MCP tools appear at all, while codex mcp list still shows the server — which makes it read as a setup mistake when it is not (openai/codex #25843).

CANVAS_LMS_ROOT is the one thing you choose: the folder your courses live in. It is required — the server keeps no root of its own, so this declaration is the only place it is written, and you can always see what it is. Leave it out and the server says so on connect, telling your assistant which file to add it to.

The folder need not exist yet; setup creates what it needs. An environment already exporting CANVAS_LMS_ROOT works too — the server just reads the environment, and does not care how a value got there.

Updating. uvx remembers which versions exist, not only the ones it downloaded, so a new release can go unnoticed. To pick one up, either run it once with --refresh-package:

uvx --refresh-package canvas-teacher-mcp canvas-teacher-mcp

or put those two arguments in front of the package name in the declaration above, and the check happens every time your client starts it. (uv cache clean canvas-teacher-mcp does not do this — it removes the downloaded package and keeps the stale list of versions.)

To try a change before it is released, point at the repository instead: "args": ["--from", "git+https://github.com/Canvas-LMS-MCP/canvas-teacher-mcp", "canvas-teacher-mcp"]

First run

Say "set up Canvas". Setup answers one step at a time; you relay what it asks for.

you     set up Canvas
setup   course root: /Users/you/Teaching
        skills: read from this package
        workflow: read from this package
        schools: none registered

        Next: what is your Canvas address? e.g. https://myschool.instructure.com

you     https://myschool.instructure.com
setup   Created /Users/you/Teaching/.claude/Canvas-Auth/myschool.json

        Two ways to finish, and the first keeps the token out of this conversation:
          1. Open that file and paste the token into its empty "token" field,
             then ask me to run setup again — I will verify it.
          2. Tell me the token and I will store it. It will then live in this
             conversation's record as well as the file.

        The token comes from Canvas: Account -> Settings -> + New Access Token.

The file it made:

{
  "base_url": "https://myschool.instructure.com/api/v1",
  "token": ""
}

Paste your token between those quotes, save, and say "run setup again":

setup   schools: myschool
          myschool: signed in as Your Name

A token that Canvas rejects is never stored, and a token already stored is never overwritten.

No token at your school? Some run SSO only. Say so — the server opens a browser once, you log in as usual, and the session is saved and refreshed from then on.

Add a course

Give the assistant the course's Canvas URL.

you     add this course: https://myschool.instructure.com/courses/12345
setup   Registered Intro to Programming as slug 'cs101'.
        Config: /Users/you/Teaching/CS101/.claude/course-config/cs101.json
        School: myschool (guessed from the domain — say so if it is wrong)

        Tools now take course='cs101'.

It read the course's own name and code from Canvas; the slug and the folder are proposals. To put it somewhere else, say so: "add it under Fall2026/CS101".

The config is two lines, because the rest is derived:

{
  "canvas_url": "https://myschool.instructure.com/courses/12345",
  "school": "myschool"
}

course_id, base_url, domain and the token variable all come from that URL. Add more only when a feature needs it: github_org for GitHub assignments, db_path for grading records, drive_folder for Google Docs, output_dir to write somewhere other than the default.

Registering is for convenience — it is what lets tools take course='cs101' instead of an id. Authentication is per SCHOOL, so any course on that domain is reachable by id without registering.

Where things end up

<ROOT>/
├─ .claude/
│  ├─ Canvas-Auth/<school>.json     your credentials — never leaves this machine
│  ├─ skills/                       (optional) the methods, if you copy them to edit
│  └─ CourseGlobalWorkflow/         (optional) the working rules, same
└─ AVC/                             one folder per school
   └─ CS101/
      └─ .claude/
         ├─ course-config/cs101.json   the course's coordinates
         ├─ input/                     material you bring in
         └─ output/<kind>/             everything the server writes

Registering a course proposes <school>/<course>; pass course_dir for anything else, and any depth works — a school whose departments are separate can put a level between them.

Credential files are written owner-only, and an existing world-readable one is corrected the next time setup runs. Keep Canvas-Auth/ out of git: one token in a history is a token to revoke.

Skills and the working rules need no copying. They are read from inside the package and work as they are. Copy them into your tree only when you want to CHANGE them — grading policy, late grace, rubric splits, the tone of a comment. The packaged copies are replaced on every upgrade, so an edit there is lost; an edit in your tree survives.

What it does

Read — courses, modules and their items, assignments, quizzes and questions, pages, submissions with their attachments fetched and read (PDF, images, Office files, notebooks), students, and every link or embed inside a page, module or quiz.

Write — pages, modules, assignments, quizzes and their questions, due dates, module items. An announcement is previewed first and sent only when you say so.

Build — a coding assignment page from a spec, a notebook assignment page, a weekly agenda, a module overview, a formatted Google Doc, a quiz from a plain-text question bank, and a student starter repository from a solved assignment.

Grade — read the assignment as the student saw it, propose the rubric, run the machine pass, and post the result when you ask for that separately. A grading run stops at a report; posting is its own step, defaults to a dry run, and refuses to post evidence that was never read.

Anything elsecanvas_api_request calls any Canvas endpoint the tools above do not cover.

What it will not do

  • Delete. No tool removes a page, an assignment, a submission or a course, and the direct API call refuses DELETE.
  • Publish. Everything is created unpublished. Publishing stays yours.
  • Run arbitrary code. There is no shell here, and no run_python.

Notes that will save you a morning

The Canvas API answers 200 to several calls that change nothing. This server sends the forms Canvas actually accepts:

  • a grade must be form-encoded; as JSON it returns 200 and sets nothing
  • a quiz-question update needs the FULL payload, or it applies nothing
  • question_count stays cached until the quiz is touched again
  • seconds_late_override is ignored when sent with late_policy_status
  • submission comments render HTML in SpeedGrader, but the API returns them flattened — the API response is not proof of what the student sees

Requirements

Python 3.10+, which uvx provides. A school without API tokens also needs Playwright's Chrome, installed on the first browser login. GitHub assignment tools need git and gh; Google Docs need the gws CLI.

License

MIT.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

canvas_teacher_mcp-0.0.8.tar.gz (3.0 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

canvas_teacher_mcp-0.0.8-py3-none-any.whl (3.0 MB view details)

Uploaded Python 3

File details

Details for the file canvas_teacher_mcp-0.0.8.tar.gz.

File metadata

  • Download URL: canvas_teacher_mcp-0.0.8.tar.gz
  • Upload date:
  • Size: 3.0 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.6 {"installer":{"name":"uv","version":"0.10.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for canvas_teacher_mcp-0.0.8.tar.gz
Algorithm Hash digest
SHA256 a3a1e18307d104f8ad70931ecb6908c32ca09305c7e5a7b1bbf6e35d7d9ee77f
MD5 76c9098cbebfa9b0ac2ad2682a53cf3b
BLAKE2b-256 2b5d9d8f1e53fada97295ed2e65e8a9d61c8e897df52f5777d4a6f4c5b1663b3

See more details on using hashes here.

File details

Details for the file canvas_teacher_mcp-0.0.8-py3-none-any.whl.

File metadata

  • Download URL: canvas_teacher_mcp-0.0.8-py3-none-any.whl
  • Upload date:
  • Size: 3.0 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.6 {"installer":{"name":"uv","version":"0.10.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for canvas_teacher_mcp-0.0.8-py3-none-any.whl
Algorithm Hash digest
SHA256 a7064843b15fa65b77016cad4fa1c96ef05d05afd9d44cacf08961aa97244378
MD5 943f1dadf27936b13e1a44b1d97f8d06
BLAKE2b-256 cbfa87c3ef6671ac5ac57ba2ecd966260c8d7bf76f9204416e11a2cbc950e15b

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.0.8 This release

2 files

0.0.7

2 files

0.0.6

2 files

0.0.5

2 files

0.0.4

2 files

0.0.3

2 files

0.0.2

2 files

0.0.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page