Skip to main content
Glama
README.md
# Canvas MCP Server

An MCP (Model Context Protocol) server that exposes UBC Canvas LMS data as tools consumable by Claude Desktop, Claude Code, n8n AI Agent nodes, or any MCP-compatible client.

```
  AI Agent / Claude Desktop / n8n
           │ MCP tool calls
           ▼
     canvas-mcp (this server)
           │ REST
           ▼
 https://ubc.instructure.com/api/v1/
```

This server is **read-only**. It surfaces Canvas data; it never writes, drafts, or submits anything.

---

## Prerequisites

- Node.js 18 or higher (or Docker — see below)
- A UBC Canvas personal access token (see below)

---

## Token Generation

1. Log in to Canvas at https://ubc.instructure.com
2. Click your avatar (top-left) → **Account** → **Settings**
3. Scroll to **Approved Integrations** → click **+ New Access Token**
4. Give it a descriptive purpose (e.g. "MCP Server")
5. Copy the token — it will not be shown again

> **Security note:** This token can read your grades, submissions, and course communications. Never share it, never commit it, and revoke it immediately if your machine is compromised.

---

## Setup

```bash
# 1. Clone the repository
git clone <repo-url>
cd Canvas-MCP-Server

# 2. Install dependencies
npm install

# 3. Configure environment
cp .env.example .env
# Edit .env and paste your token:
#   CANVAS_TOKEN=your_token_here
#   CANVAS_BASE_URL=https://ubc.instructure.com/api/v1

# 4. Build
npm run build

# 5. Verify your token works
echo '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}},"id":1}' | node dist/index.js
```

---

## Available Tools

| Tool | Description |
|------|-------------|
| `whoami` | Verify your token and return your Canvas profile (name, email, login ID) |
| `list_courses` | List all active courses. Returns course IDs required by other tools |
| `list_assignments` | List upcoming assignments for a course (default: next 14 days, max 90) |
| `get_assignment` | Full assignment details with plain-text description (HTML stripped) |
| `list_announcements` | Recent announcements from one or more courses (default: last 14 days) |
| `list_upcoming` | Unified calendar view of upcoming assignment due dates across all courses |
| `get_submission` | Your submission status for an assignment: score, grade, late/missing flags |

---

## Fresh Ubuntu Setup (Docker)

Follow these three steps on a brand-new Ubuntu machine. No Node.js installation needed.

### Step 1 — Install Docker Engine

```bash
sudo apt-get update
sudo apt-get install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
  https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
  | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
```

Allow running Docker without `sudo`:

```bash
sudo usermod -aG docker $USER
newgrp docker           # apply group change in current shell without logging out
docker run hello-world  # confirm Docker is working
```

### Step 2 — Clone the repo and set your token

```bash
git clone https://github.com/terraceonhigh/Canvas-MCP-Server.git
cd Canvas-MCP-Server
cp .env.example .env
nano .env   # paste your CANVAS_TOKEN value
```

### Step 3 — Build the image and verify

```bash
docker build -t canvas-mcp .

# Smoke-test: should print a valid JSON-RPC response to stdout
echo '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}},"id":1}' \
  | docker run --rm -i \
    -e CANVAS_TOKEN=$(grep CANVAS_TOKEN .env | cut -d= -f2) \
    -e CANVAS_BASE_URL=https://ubc.instructure.com/api/v1 \
    canvas-mcp
```

> The token is read from `.env` only for this test command and passed via `-e` at runtime — it is never baked into the image.

---

## Docker

You can run the server in a container instead of installing Node.js locally.

```bash
# Build the image
docker build -t canvas-mcp .

# Verify your token (one-shot test)
echo '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}},"id":1}' \
  | docker run --rm -i \
    -e CANVAS_TOKEN=your_token_here \
    -e CANVAS_BASE_URL=https://ubc.instructure.com/api/v1 \
    canvas-mcp
```

The container reads MCP messages from stdin and writes responses to stdout, so always pass `-i` to `docker run`. Never use `-t` (TTY), as it would corrupt the binary stdio stream.

### Claude Desktop with Docker

```json
{
  "mcpServers": {
    "canvas": {
      "command": "docker",
      "args": [
        "run", "--rm", "-i",
        "-e", "CANVAS_TOKEN=your_token_here",
        "-e", "CANVAS_BASE_URL=https://ubc.instructure.com/api/v1",
        "canvas-mcp"
      ]
    }
  }
}
```

---

## Claude Desktop Configuration

Add this to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):

```json
{
  "mcpServers": {
    "canvas": {
      "command": "node",
      "args": ["dist/index.js"],
      "cwd": "/absolute/path/to/Canvas-MCP-Server",
      "env": {
        "CANVAS_TOKEN": "your_token_here",
        "CANVAS_BASE_URL": "https://ubc.instructure.com/api/v1"
      }
    }
  }
}
```

Replace `/absolute/path/to/Canvas-MCP-Server` with the actual path on your machine.

---

## n8n AI Agent Node

In any n8n AI Agent node:

1. Add a new **MCP Tool** credential
2. Set the command to `node` and args to `["dist/index.js"]`
3. Set the working directory to this project's path
4. Set `CANVAS_TOKEN` and `CANVAS_BASE_URL` as environment variables

The agent can then call tools like `list_assignments`, filter results, and pipe them to downstream services (task managers, calendars, notification systems) in the same workflow.

---

## Architecture

```
src/
├── index.ts            # MCP server entry point — registers all tools
├── canvas-client.ts    # Authenticated fetch wrapper with automatic pagination
└── tools/
    ├── courses.ts      # list_courses
    ├── assignments.ts  # list_assignments, get_assignment
    ├── announcements.ts# list_announcements
    ├── calendar.ts     # list_upcoming
    └── submissions.ts  # get_submission, whoami
```

Key design decisions:
- **Pagination is automatic** — all list endpoints follow `Link: rel="next"` headers until exhausted
- **HTML is always stripped** — descriptions and announcement bodies are returned as clean plain text
- **Token is never logged** — the `Authorization` header is excluded from all error messages and debug output
- **Stdout is sacred** — all diagnostic output goes to stderr; stdout carries only MCP protocol messages

---

## Known Limitations

- **Rate limits:** Canvas enforces undocumented rate limits. Polling workflows should add delays between requests if 403s appear.
- **Token scope:** A personal token reads everything you can see in Canvas. Treat it like a password.
- **Announcement interpretation:** Pattern-matching (e.g. detecting class cancellations) is the consuming agent's job — this server returns clean text only.