Skip to main content
Glama
README.md
# canvas-mcp

**Run your Canvas course from an AI assistant.**

`canvas-mcp` is an [MCP](https://modelcontextprotocol.io) server that gives Claude,
Cursor, VS Code, or any other MCP-capable assistant **47 tools** for Canvas LMS —
reading your roster, drafting assignments, building modules, grading submissions,
posting announcements, and pulling the whole gradebook into one table.

It is written for **instructors**, not Canvas administrators. Everything it does,
it does as *you*, with *your* token, inside the courses you already teach.

```
You:  "Which students haven't submitted Essay 2 yet, and when is it due?"
      "Draft a Week 9 module on Bonhoeffer with a reading page and a discussion,
       but leave it unpublished so I can look first."
      "Give everyone who submitted Lab 3 full marks with the comment 'Nice work'."
```

---

## Table of contents

- [What you get](#what-you-get)
- [Quick start (5 minutes)](#quick-start-5-minutes)
  - [1. Install](#1-install)
  - [2. Get a Canvas API token](#2-get-a-canvas-api-token)
  - [3. Find your course id](#3-find-your-course-id)
  - [4. Tell the server about them](#4-tell-the-server-about-them)
  - [5. Check it works](#5-check-it-works)
  - [6. Connect your AI assistant](#6-connect-your-ai-assistant)
- [Safety model](#safety-model)
- [The 47 tools](#the-47-tools)
- [Configuration reference](#configuration-reference)
- [Command-line interface](#command-line-interface)
- [Recipes](#recipes)
- [Student privacy and FERPA](#student-privacy-and-ferpa)
- [Troubleshooting](#troubleshooting)
- [FAQ](#faq)
- [Contributing](#contributing)
- [License](#license)

---

## What you get

| | |
|---|---|
| **47 tools** | across diagnostics, courses, people, assignments, grading, content, and communication |
| **Three safety tiers** | read-only always on; writes on by default; deletes **off** by default |
| **Tools you disable are never published** | a withheld tool isn't in the model's tool list at all — it can't be called and it costs no context |
| **Bounded output** | big payloads are slimmed to the fields that matter and truncated with an explicit notice, so one `list_submissions` doesn't eat your context window |
| **Readable text, not raw HTML** | syllabi, pages, and discussion threads come back as plain text with paragraph breaks intact |
| **Optional PII redaction** | `CANVAS_REDACT_PII=1` replaces emails and SIS ids with stable `anon-…` hashes |
| **Retries that understand Canvas** | Canvas signals rate limiting with a `403`, not a `429`; this server tells the two apart and only retries the right one |
| **A `doctor` command** | tells you exactly which of base-url / token / permissions / course-id is wrong, instead of a generic 401 |
| **No vendor lock-in** | pure `requests` against the public Canvas REST API; no Canvas app registration, no OAuth dance, no admin approval |

---

## Quick start (5 minutes)

### 1. Install

Pick whichever matches how you already run Python.

**With `uv` (recommended — no virtualenv to manage):**

```bash
uvx --from git+https://github.com/zhenghh04/canvas-mcp canvas-mcp doctor
```

**With `pip`:**

```bash
pip install git+https://github.com/zhenghh04/canvas-mcp
```

**From a clone (if you want to modify it):**

```bash
git clone https://github.com/zhenghh04/canvas-mcp
cd canvas-mcp
pip install -e ".[dev]"
pytest          # 163 tests, all offline — none of them touch a real course
```

Requires Python 3.10 or newer.

### 2. Get a Canvas API token

You do **not** need an administrator, and you do **not** need to register an app.

1. Log in to Canvas in a browser.
2. Click **Account** (left sidebar) → **Settings**.
3. Scroll to **Approved Integrations**.
4. Click **+ New Access Token**.
5. Purpose: `canvas-mcp`. Expiry: leave blank for no expiry, or set a date — if you
   set one, note it, because an expired token looks exactly like a wrong token.
6. Click **Generate Token**.
7. **Copy the token now.** Canvas shows it exactly once. If you lose it, delete the
   entry and make a new one.

The token carries *all* your Canvas permissions — in every course you teach, and
every course you take. Treat it like your password. See
[Student privacy and FERPA](#student-privacy-and-ferpa).

> **Locked down?** Some institutions disable self-service tokens. If you don't see
> **+ New Access Token**, ask your Canvas admin for a token or for the setting to be
> enabled for faculty.

### 3. Find your course id

Open the course in Canvas and look at the URL:

```
https://yourschool.instructure.com/courses/12345
                                           ^^^^^
                                           this is the course id
```

Setting a default course id is optional but makes every conversation shorter — you
can say "the roster" instead of "the roster for course 12345". You can always
override it per call, and `canvas_list_courses` will show you all of them.

### 4. Tell the server about them

Two settings are required: **where your Canvas lives** and **your token**.

You can supply them three ways. Later entries do *not* override earlier ones —
**an already-set environment variable always wins**, so your MCP client's `env`
block beats any `.env` file.

| Priority | Source |
|---|---|
| 1 | Real environment variables (including your MCP client's `env` block) |
| 2 | `$CANVAS_MCP_ENV_FILE` — point this at any file you like |
| 3 | `./.env` in the current directory |
| 4 | `~/.config/canvas-mcp/.env` |

The last one is usually what you want, because it works no matter which directory
your assistant starts in:

```bash
mkdir -p ~/.config/canvas-mcp
cat > ~/.config/canvas-mcp/.env <<'EOF'
CANVAS_BASE_URL=https://yourschool.instructure.com
CANVAS_API_TOKEN=paste-your-token-here
CANVAS_DEFAULT_COURSE_ID=12345
EOF
chmod 600 ~/.config/canvas-mcp/.env
```

That `chmod` matters — the file now contains a credential.

See [`env.example`](env.example) for every available setting, annotated.

### 5. Check it works

```bash
canvas-mcp doctor
```

A healthy run looks like:

```
canvas-mcp 0.2.0

[OK  ] CANVAS_BASE_URL set — https://yourschool.instructure.com
[OK  ] CANVAS_API_TOKEN set
[OK  ] Token authenticates — Ada Lovelace (id 10665)
[OK  ] Teacher enrollments visible — 1 active course(s)
          12345  Introduction to Analytical Engines (CS-101)
[OK  ] CANVAS_DEFAULT_COURSE_ID resolves — Introduction to Analytical Engines (CS-101)

42 tools would be published (writes=on, destructive=off). Run `canvas-mcp tools` for the list.
```

If any line is wrong, `doctor` says what to fix. See
[Troubleshooting](#troubleshooting).

### 6. Connect your AI assistant

<details open>
<summary><strong>Claude Code</strong> (CLI)</summary>

One command:

```bash
claude mcp add canvas -- canvas-mcp serve
```

Or add to `.mcp.json` in your project (or `~/.claude.json` for every project):

```json
{
  "mcpServers": {
    "canvas": {
      "command": "canvas-mcp",
      "args": ["serve"],
      "env": {
        "CANVAS_BASE_URL": "https://yourschool.instructure.com",
        "CANVAS_DEFAULT_COURSE_ID": "12345"
      }
    }
  }
}
```

Note that the **token is deliberately not in this file** — `.mcp.json` is the kind
of file that gets committed to git by accident. Leave `CANVAS_API_TOKEN` in
`~/.config/canvas-mcp/.env`.
</details>

<details>
<summary><strong>Claude Desktop</strong></summary>

Edit `claude_desktop_config.json`:

- **macOS** — `~/Library/Application Support/Claude/claude_desktop_config.json`
- **Windows** — `%APPDATA%\Claude\claude_desktop_config.json`
- **Linux** — `~/.config/Claude/claude_desktop_config.json`

```json
{
  "mcpServers": {
    "canvas": {
      "command": "canvas-mcp",
      "args": ["serve"],
      "env": {
        "CANVAS_BASE_URL": "https://yourschool.instructure.com",
        "CANVAS_DEFAULT_COURSE_ID": "12345"
      }
    }
  }
}
```

Then **fully quit and reopen** Claude Desktop — reloading the window is not enough.

If `canvas-mcp` isn't found, Claude Desktop doesn't inherit your shell `PATH`. Use
an absolute path (`which canvas-mcp` will tell you), or use `uvx`:

```json
{
  "mcpServers": {
    "canvas": {
      "command": "uvx",
      "args": ["--from", "git+https://github.com/zhenghh04/canvas-mcp", "canvas-mcp", "serve"]
    }
  }
}
```
</details>

<details>
<summary><strong>Cursor</strong></summary>

**Settings → MCP → Add new MCP server**, or edit `~/.cursor/mcp.json`:

```json
{
  "mcpServers": {
    "canvas": {
      "command": "canvas-mcp",
      "args": ["serve"],
      "env": { "CANVAS_BASE_URL": "https://yourschool.instructure.com" }
    }
  }
}
```
</details>

<details>
<summary><strong>VS Code (GitHub Copilot agent mode)</strong></summary>

Create `.vscode/mcp.json`:

```json
{
  "servers": {
    "canvas": {
      "type": "stdio",
      "command": "canvas-mcp",
      "args": ["serve"],
      "env": { "CANVAS_BASE_URL": "https://yourschool.instructure.com" }
    }
  }
}
```
</details>

<details>
<summary><strong>Anything else (generic stdio, or HTTP)</strong></summary>

Standard stdio MCP server:

```bash
canvas-mcp serve                      # stdio, the default
```

Or serve over HTTP for a remote or multi-client setup:

```bash
canvas-mcp serve --transport http --host 127.0.0.1 --port 8900
```

**Bind to `127.0.0.1` unless you have put real authentication in front of it.**
This server has no auth of its own: anyone who can reach the port is acting as you,
in your courses, with your token.
</details>

Ready-made config snippets live in [`examples/`](examples/).

---

## Safety model

Three tiers. A tool you don't enable is **never registered** — the model cannot see
it, cannot call it, and it costs nothing in context.

| Tier | Default | Controlled by | What's in it |
|---|---|---|---|
| **read** | always on | — | 24 tools. Rosters, assignments, submissions, pages, the gradebook. Never changes anything. |
| **write** | **on** | `CANVAS_ENABLE_WRITES=0` to disable | 18 tools. Create and edit assignments, pages, modules; post grades; send announcements and messages. |
| **destructive** | **off** | `CANVAS_ALLOW_DESTRUCTIVE=1` to enable | 5 tools. Delete assignments, pages, modules; remove enrollments; arbitrary `POST`/`PUT`/`DELETE`. |

The destructive flag cannot bypass the write gate: with `CANVAS_ENABLE_WRITES=0`,
setting `CANVAS_ALLOW_DESTRUCTIVE=1` publishes nothing.

**Three postures worth knowing:**

```bash
# Read-only. Nothing can change. Good for a first week, or for a shared setup.
CANVAS_ENABLE_WRITES=0

# Default. Author and grade freely; nothing can be deleted.
# (this is what you get with no settings at all)

# Full. Only turn this on for a specific cleanup task, then turn it back off.
CANVAS_ALLOW_DESTRUCTIVE=1
```

Beyond the tiers, every mutating tool says so **in its own description** —
`MUTATES the course`, `MUTATES STUDENT RECORDS`, `NOTIFIES PEOPLE`, `IRREVERSIBLE` —
because that text is what the model actually reads when deciding whether to ask you
first. The server's instructions tell the assistant to get explicit go-ahead before
any of them.

Some further guarantees, each with a test behind it:

- **Authoring is unpublished by default.** `canvas_create_assignment`,
  `canvas_create_page`, and `canvas_create_module` all create *drafts*. Students see
  nothing until you publish.
- **Your token never reaches the file-storage host.** Canvas uploads go to an S3 /
  InstFS URL with its own pre-signed credentials; the bearer token is stripped.
- **Student filenames are sanitised.** `canvas_download_submissions` writes
  attachments to disk, and `../../.ssh/authorized_keys` is a perfectly legal Canvas
  filename.
- **Messages don't leak the recipient list.** `canvas_message_students` sends
  individually by default, not as a group conversation.
- **Errors are errors.** A bug in this server raises; only genuine Canvas failures
  are converted into a readable `ERROR: …` string for the model.

---

## The 47 tools

Legend: (blank) read-only · `!` write · `!!` destructive.

Run `canvas-mcp tools` to see exactly what *your* configuration publishes.

### Diagnostics

| | Tool | What it does |
|---|---|---|
| | `canvas_auth_status` | Check connectivity, identity, and which tiers are enabled. Call this first. |
| | `canvas_api_get` | Escape hatch: `GET` any Canvas REST endpoint that has no dedicated tool. |
| `!!` | `canvas_api_write` | Escape hatch: `POST`/`PUT`/`DELETE` any endpoint. Unrestricted. |

### Courses

| | Tool | What it does |
|---|---|---|
| | `canvas_list_courses` | List the courses you can see. |
| | `canvas_get_course` | One course: teachers, student count, syllabus as plain text. |
| | `canvas_list_sections` | Sections with enrollment counts. |
| `!` | `canvas_update_course` | Edit course settings (name, dates, publish state). |
| `!` | `canvas_update_syllabus` | Replace the syllabus body. |

### People

| | Tool | What it does |
|---|---|---|
| | `canvas_list_students` | The roster. |
| | `canvas_list_enrollments` | Enrollments including the `enrollment_id` you need to change one. |
| `!` | `canvas_enroll_user` | Add a user to the course. |
| `!!` | `canvas_remove_enrollment` | Remove someone from the course. |

### Assignments

| | Tool | What it does |
|---|---|---|
| | `canvas_list_assignments` | Assignments with due dates and needs-grading counts. |
| | `canvas_get_assignment` | One assignment, description rendered to plain text. |
| | `canvas_list_assignment_groups` | Gradebook categories and their weights. |
| `!` | `canvas_create_assignment_group` | Create a gradebook category. |
| `!` | `canvas_create_assignment` | Create an assignment (unpublished by default). |
| `!` | `canvas_update_assignment` | Edit an existing assignment. |
| `!!` | `canvas_delete_assignment` | Delete an assignment. |

### Grading

| | Tool | What it does |
|---|---|---|
| | `canvas_list_submissions` | Status, score, timestamps, lateness for an assignment. |
| | `canvas_get_submission` | One student's submission: body text, attachments, comments. |
| | `canvas_get_gradebook` | The whole gradebook as one student × assignment table. |
| | `canvas_download_submissions` | Download every file attachment for an assignment. |
| `!` | `canvas_grade_submission` | Post a grade and/or a comment. |
| `!` | `canvas_bulk_grade` | Post many grades to one assignment at once. |

### Content

| | Tool | What it does |
|---|---|---|
| | `canvas_list_pages` | Wiki page titles and url slugs. |
| | `canvas_get_page` | One page's body as plain text. |
| | `canvas_list_modules` | The course outline, optionally with items. |
| | `canvas_list_quizzes` | Quizzes in a course. |
| | `canvas_list_files` | Files uploaded to a course. |
| | `canvas_download_file` | Download one course file. |
| `!` | `canvas_create_page` | Create a wiki page (unpublished by default). |
| `!` | `canvas_update_page` | Edit a wiki page. |
| `!` | `canvas_create_module` | Create a module (unpublished by default). |
| `!` | `canvas_update_module` | Edit or publish a module. |
| `!` | `canvas_create_module_item` | Add a page / assignment / quiz / link to a module. |
| `!` | `canvas_upload_file` | Upload a local file into Files. |
| `!!` | `canvas_delete_page` | Delete a wiki page. |
| `!!` | `canvas_delete_module` | Delete a module. |

### Communication

| | Tool | What it does |
|---|---|---|
| | `canvas_list_announcements` | Announcements, newest first, with their text. |
| | `canvas_list_discussions` | Discussion topics. |
| | `canvas_get_discussion` | A topic with its full reply thread as plain text. |
| | `canvas_list_calendar_events` | Calendar events, optionally with assignment due dates. |
| `!` | `canvas_post_announcement` | Post an announcement — **students are notified**. |
| `!` | `canvas_create_discussion` | Create a discussion topic. |
| `!` | `canvas_create_calendar_event` | Add an event to the course calendar. |
| `!` | `canvas_message_students` | Send a Canvas inbox message. |

Full parameter-by-parameter reference: [`docs/TOOLS.md`](docs/TOOLS.md).

---

## Configuration reference

Every setting is an environment variable. All are optional except the first two.

### Required

| Variable | Example | Notes |
|---|---|---|
| `CANVAS_BASE_URL` | `https://yourschool.instructure.com` | No trailing slash needed; one is stripped. Do **not** include `/api/v1`. |
| `CANVAS_API_TOKEN` | `7~abc123…` | From Account → Settings → New Access Token. |

### Convenience

| Variable | Default | Notes |
|---|---|---|
| `CANVAS_DEFAULT_COURSE_ID` | — | Used whenever a tool's `course_id` is omitted. |
| `CANVAS_MCP_ENV_FILE` | — | Path to a `.env` file to load first. |
| `CANVAS_DOWNLOAD_DIR` | `./canvas-downloads` | Where downloaded files and submissions land. |

### Safety

| Variable | Default | Notes |
|---|---|---|
| `CANVAS_ENABLE_WRITES` | `1` | `0` for a strictly read-only server. |
| `CANVAS_ALLOW_DESTRUCTIVE` | `0` | `1` publishes the five delete/raw-write tools. |
| `CANVAS_REDACT_PII` | `0` | `1` pseudonymises emails, login ids, SIS ids. |

### Trimming the tool surface

Fewer tools means less context spent and fewer ways to go wrong.

| Variable | Example | Notes |
|---|---|---|
| `CANVAS_TOOL_GROUPS` | `courses,assignments,grading` | Comma-separated. Groups: `diagnostics`, `courses`, `people`, `assignments`, `grading`, `content`, `communication`. |
| `CANVAS_TOOLS` | `canvas_auth_status,canvas_get_gradebook` | Strict allowlist. Only these, and only if their tier is enabled. |
| `CANVAS_DISABLE_TOOLS` | `canvas_message_students` | Denylist, applied after everything else. |

An allowlist can never bypass a tier gate. Listing `canvas_delete_page` in
`CANVAS_TOOLS` does nothing unless `CANVAS_ALLOW_DESTRUCTIVE=1`.

### Limits and performance

| Variable | Default | Notes |
|---|---|---|
| `CANVAS_TIMEOUT` | `30` | Seconds per API request. |
| `CANVAS_UPLOAD_TIMEOUT` | `300` | Seconds for file uploads. |
| `CANVAS_MAX_UPLOAD_MB` | `100` | Refuse larger local files. |
| `CANVAS_PER_PAGE` | `100` | Canvas page size. |
| `CANVAS_MAX_PAGES` | `10` | Pagination cap. Results past it are marked `_truncated`. |
| `CANVAS_MAX_CHARS` | `20000` | Response size cap, with an explicit truncation notice. |
| `CANVAS_MAX_RETRIES` | `3` | Attempts on rate limits and 5xx. |

---

## Command-line interface

```bash
canvas-mcp                  # same as `serve` — stdio MCP server
canvas-mcp serve            # stdio (what MCP clients launch)
canvas-mcp serve --transport http --host 127.0.0.1 --port 8900
canvas-mcp doctor           # diagnose configuration and permissions
canvas-mcp tools            # list what this configuration publishes, and what it withholds
canvas-mcp config           # show resolved settings (token redacted)
```

`canvas-mcp config` prints `"api_token": "set (70 chars)"` — never the token itself.

`serve` deliberately **starts even when misconfigured**, printing a warning to
stderr. An MCP client that sees its server exit immediately reports only "broken
pipe", which tells you nothing; a running server can answer `canvas_auth_status`
with an actual explanation.

---

## Recipes

Things that work well. More in [`docs/RECIPES.md`](docs/RECIPES.md).

**Triage before office hours**
> "Who hasn't submitted Essay 2? For each, tell me whether they've submitted the
> previous two assignments."

**Build a week**
> "Create an unpublished module called 'Week 9 — Bonhoeffer', add a page with the
> reading questions below, and a discussion topic due Friday 11:59pm. Don't publish
> anything."

**Grade a batch**
> "List submissions for Lab 3. For everyone who submitted on time with a file
> attached, give 10/10 and the comment 'Received, nice work.' Show me the list
> before you post anything."

**Catch grading drift**
> "Pull the gradebook. Which assignments still have ungraded submissions, and which
> students are more than one standard deviation below the mean overall?"

**Sync a syllabus**
> "Here's my updated syllabus as markdown. Replace the Canvas syllabus with it,
> preserving the existing course-policies section at the bottom."

**Read the room**
> "Summarise the Week 4 discussion thread. What are the three most common
> misunderstandings, and which students should I follow up with?"

A habit worth forming: **ask for the plan before the action.** "Show me what you'd
post, then wait" costs one extra turn and catches the wrong-assignment-id mistake
before it reaches 30 students.

---

## Student privacy and FERPA

This server reads real student data. A few things are worth being deliberate about.

**Your token is your whole Canvas account.** Not just one course — every course you
teach and every course you take. Keep it in a `chmod 600` file, never in a
git-tracked config, and revoke it from Account → Settings if it leaks.

**Student data flows to your AI provider.** When the assistant reads a roster or a
submission, that text goes to whoever runs the model. Check whether your institution
has a data-processing agreement covering that provider before putting student work
through it. This is the single most important thing on this page.

**`CANVAS_REDACT_PII=1` helps, partially.** It replaces emails, login ids, and SIS
ids with stable `anon-<hash>` values — stable so the model can still correlate rows,
non-reversible so the identifier itself doesn't travel. **Names are deliberately not
redacted**, because an assistant that can't say *"follow up with Dana"* isn't much
use. If names are the concern, redaction is not the answer — don't route the roster
through a model at all.

**Downloads land on your disk.** `canvas_download_submissions` writes student work
to `CANVAS_DOWNLOAD_DIR`. That's a FERPA-relevant directory; treat it like one, and
clean it up.

**Keep the destructive tier off.** Grades and enrollments are student records. The
default posture lets the assistant *write* grades (recoverable, visible in the
gradebook history) but not *remove* enrollments (not recoverable in the same way).

More detail: [`docs/SAFETY.md`](docs/SAFETY.md).

---

## Troubleshooting

Run `canvas-mcp doctor` first — most of these it will name directly.

<details>
<summary><strong>"CANVAS_API_TOKEN is not set"</strong></summary>

The server couldn't find your token. Check, in order:

1. `canvas-mcp config` — which env file did it load? If `env file: (none)`, it found
   no file at any of the four locations.
2. Your MCP client may start the server in a different working directory, so a
   `./.env` won't be found. Use `~/.config/canvas-mcp/.env`.
3. Make sure the file has no quotes and no spaces around `=`:
   `CANVAS_API_TOKEN=7~abc` — not `CANVAS_API_TOKEN = "7~abc"`.
</details>

<details>
<summary><strong>401 Unauthorized</strong></summary>

The token is wrong, revoked, or expired. Canvas tokens can have an expiry date set
at creation, and an expired token returns exactly the same 401 as a typo'd one.

Generate a fresh one at Account → Settings → + New Access Token.

Also confirm `CANVAS_BASE_URL` points at the tenant the token came from — a valid
token for `school-a.instructure.com` is a 401 at `school-b.instructure.com`.
</details>

<details>
<summary><strong>403 Forbidden</strong></summary>

Two different things wear this status code in Canvas.

- **Rate limiting.** The body says "Rate Limit Exceeded". The server detects this
  and retries with backoff; if you still see it, you're hammering the API — raise
  `CANVAS_TIMEOUT` and make fewer, larger requests.
- **Real permission denied.** You're not a teacher in that course, or your role
  lacks the specific permission (some institutions restrict `message_students` or
  enrollment changes even for teachers). Nothing this server can do; ask your Canvas
  admin.
</details>

<details>
<summary><strong>404 Not Found</strong></summary>

Usually a wrong id, but Canvas also returns 404 instead of 403 for things you're not
allowed to see, which is a deliberate anti-enumeration measure. So: check the id is
right *and* that you're enrolled in that course as a teacher.
</details>

<details>
<summary><strong>422 Unprocessable Entity</strong></summary>

Canvas rejected the content. Common causes: a due date outside the course term, a
page title that collides with an existing one, `points_possible` on an assignment
whose grading type doesn't take points. The error message passes Canvas's own
explanation through.
</details>

<details>
<summary><strong>The assistant can't see any Canvas tools</strong></summary>

1. Restart the client fully (Claude Desktop needs a real quit, not a window reload).
2. Run the exact command from your config by hand in a terminal. If
   `canvas-mcp: command not found`, your client doesn't share your shell `PATH` —
   use an absolute path from `which canvas-mcp`, or switch to the `uvx` form.
3. Check the client's MCP logs. Claude Desktop: `~/Library/Logs/Claude/` on macOS.
</details>

<details>
<summary><strong>The assistant can see some tools but not the one I want</strong></summary>

Run `canvas-mcp tools`. The **WITHHELD** section at the bottom lists every suppressed
tool and the exact reason — a tier gate, a group filter, an allowlist, or a
denylist.
</details>

<details>
<summary><strong>Results are cut off / say `_truncated`</strong></summary>

Two separate caps. `_truncated` in a list means pagination stopped at
`CANVAS_MAX_PAGES`. A trailing truncation notice means the text hit
`CANVAS_MAX_CHARS`. Raise whichever one you hit — but they exist to protect your
context window, so prefer narrowing the request (one assignment, one section) over
raising the cap.
</details>

<details>
<summary><strong>Uploads fail</strong></summary>

Check the file is under `CANVAS_MAX_UPLOAD_MB` (default 100) and that
`CANVAS_UPLOAD_TIMEOUT` is generous enough for your connection. Canvas uploads are a
three-step handshake through a separate storage host; a timeout in the middle leaves
nothing behind, so it's safe to retry.
</details>

---

## FAQ

**Do I need to be a Canvas administrator?**
No. A plain teacher account and a self-service access token. If your institution has
disabled self-service tokens, you'll need an admin to issue one — that's the only
admin involvement.

**Will this work with my school's Canvas?**
If it's Canvas and you can reach it over HTTPS, yes. It uses only the public REST
API v1. Self-hosted Canvas works too; just point `CANVAS_BASE_URL` at it.

**Can it see other instructors' courses?**
Only what your own Canvas account can see. It has exactly your permissions — no more.

**Can students use it?**
It'll run, but most tools need teacher permissions. It isn't designed for that.

**Does it store anything?**
No database, no cache, no telemetry. The only things written to disk are files you
explicitly download, into `CANVAS_DOWNLOAD_DIR`.

**What if the AI grades something wrong?**
Canvas keeps grade history — you can see and revert every change in the gradebook's
grade-change log. That's precisely why grading is in the `write` tier and not gated
behind `destructive`: it's recoverable. Still: review before posting.

**Can I use it with a local model (Ollama, LM Studio)?**
Yes, if your client speaks MCP. Note that the 47 tool descriptions are a meaningful
chunk of context for a small model — use `CANVAS_TOOL_GROUPS` to publish only what
you need.

**Why doesn't it support Canvas's New Quizzes?**
New Quizzes lives behind a separate API with different auth. `canvas_list_quizzes`
sees Classic Quizzes. PRs welcome.

**Does it work with Canvas Free for Teachers?**
Yes — `https://canvas.instructure.com` as your base URL.

---

## Contributing

Issues and PRs welcome, particularly from instructors who hit something this doesn't
cover. See [`CONTRIBUTING.md`](CONTRIBUTING.md).

```bash
git clone https://github.com/zhenghh04/canvas-mcp
cd canvas-mcp
pip install -e ".[dev]"
pytest        # 163 tests, fully offline
ruff check .
```

The test suite never contacts a real Canvas tenant — the fixture transport raises on
any unexpected call, so a test that escapes fails loudly instead of mutating
somebody's live course.

---

## License

MIT. See [`LICENSE`](LICENSE).

Not affiliated with or endorsed by Instructure. "Canvas" is their trademark.

TDQS

A3.7/5.0

Scored across 42 tools

Disambiguation4/5

Each tool targets a distinct resource and action, and descriptions actively head off confusion (e.g. canvas_list_students returns users while canvas_list_enrollments returns enrollment ids; canvas_list_submissions is metadata-only while canvas_get_submission returns the work). A few pairs sit close together (download_submissions vs download_file, get_gradebook vs list_submissions, api_get as a catch-all), but none is genuinely ambiguous.

Naming Consistency4/5

Nearly all tools follow a canvas_<verb>_<noun> pattern (get_assignment, list_courses, create_page, update_module, download_file, grade_submission). Minor deviations exist — canvas_auth_status is noun-noun and canvas_api_get inverts the pattern — but the convention is largely predictable.

Tool Count3/5

42 tools is heavy and pushes past the comfortable range, but the Canvas teaching domain is genuinely broad (gradebook, submissions, pages, modules, files, discussions, calendar, messaging). Each tool maps to a distinct operation and an api_get escape hatch absorbs the long tail, so the count is defensible though on the borderline of unwieldy.

Completeness3/5

Create/read/update coverage is strong across assignments, pages, modules, discussions, and grading, but deletions are almost entirely absent (no delete page/module/assignment/file/discussion). Notably, canvas_list_enrollments references a canvas_remove_enrollment tool that is not published, leaving a dangling handle that would trap an agent trying to drop a student.

Maintenance

ActivityMaintained
ResponsivenessNo issues