canvas-mcp
# canvas-mcp
An MCP server that exposes Canvas LMS data (courses, assignments, announcements) to any MCP-compatible client (Claude Desktop, Claude Code, etc.).
> **This repository ships with no credentials.** There is no Canvas token and no
> email address anywhere in it. You supply your own token in a local `.env` file
> that is git-ignored and never leaves your machine. See
> [Where your token goes](#where-your-token-goes).
## Prerequisites
- [uv](https://docs.astral.sh/uv/) (Python package manager)
- A Canvas LMS account with an API token
## Setup
### 1. Generate a Canvas API token
1. Log in to your school's Canvas site (for example `https://canvas.fen.uchile.cl`)
2. Go to **Account → Settings → Approved Integrations**
3. Click **+ New Access Token**, give it a name, and copy the token
### 2. Configure the environment
<a id="where-your-token-goes"></a>
**Where your token goes:** in a file named exactly `.env`, in the root of this
project folder (right next to `main.py`). That file is listed in `.gitignore`,
so git will never commit or push it.
Copy the template:
```bash
cp .env.example .env
```
Then open `.env` and fill in the blanks:
```
# Your Canvas instance URL -- change this to your school's Canvas address
CANVAS_URL=https://canvas.fen.uchile.cl
# Paste the token from step 1 here
CANVAS_API_TOKEN=
# Optional second account (use account="udd" in the tools). Leave blank if unused.
CANVAS_UDD_URL=
CANVAS_UDD_TOKEN=
```
Only `CANVAS_URL` and `CANVAS_API_TOKEN` are required. The `CANVAS_UDD_*` pair is
for an optional second Canvas account; leave both blank if you only have one.
> Never commit `.env`, never paste your token into a chat, and never put it in a
> file that gets shared. It grants full access to your Canvas account. If it
> leaks, delete the token in Canvas (**Account -> Settings -> Approved
> Integrations**) and generate a new one.
### 3. Install dependencies
```bash
uv sync
```
### 4. Run the server manually (optional test)
```bash
uv run python main.py
```
The server communicates over stdio, so it won't print anything until an MCP client connects.
## Connecting to Claude Desktop
Add the following to your Claude Desktop config file:
**Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
**macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
```json
{
"mcpServers": {
"canvas": {
"command": "uv",
"args": ["run", "--project", "/absolute/path/to/canvas-mcp", "python", "main.py"]
}
}
}
```
Replace `/absolute/path/to/canvas-mcp` with the real path to this folder. Use
forward slashes `/` even on Windows.
> No token goes in this file. It is read from your `.env` (see
> [Where your token goes](#where-your-token-goes)) -- `python-dotenv` loads it
> automatically.
## Connecting to Claude Code
Add the MCP server in your Claude Code settings or run:
```bash
claude mcp add canvas -- uv run --project /absolute/path/to/canvas-mcp python main.py
```
## Available Tools
| Tool | Description |
|------|-------------|
| `get_courses` | Lists all active enrolled courses |
| `get_assignments(days_ahead=30)` | Lists upcoming assignments sorted by due date |
| `get_announcements` | Lists recent announcements per course |
## Project structure
```
canvas-mcp/
├── main.py # MCP server
├── .env # Your token lives here (git-ignored, never pushed)
├── .env.example # Template
├── pyproject.toml # Project metadata & dependencies
└── uv.lock # Locked dependency versions
```
TDQS
Scored across 10 tools
Every tool targets a distinct resource and action. get_courses vs get_assignments vs get_announcements are clearly separated, and even similar tools like get_course_files (listing) and read_course_file (reading content) are unambiguous.
All tools follow a verb_noun pattern with 'get_' prefix, except read_course_file which uses 'read_'. This is a minor deviation but the rest are consistent, and the naming is predictable overall.
The 10 tools are well-scoped for a read-only Canvas LMS server. They cover the main course-related resources without being excessive, fitting comfortably within the ideal 3-15 range.
For a read-only surface, it covers the major resources: courses, assignments, announcements, files, modules, people, grades, discussions, and pages. Minor gaps exist (e.g., no single-course detail endpoint, no calendar events), but the core read workflows are covered without dead ends.