···11-# Canvas MCP Server Setup
22-33-This guide explains how to set up the Canvas MCP server to work with your Canvas LMS institution(s).
44-55-## Multi-Institution Support
66-77-The server supports three configuration modes:
88-99-### 1. **Global/Wildcard Configuration** (Easiest)
1010-1111-If your Canvas instance supports OAuth applications that work across different domains, or if you're only supporting one institution:
1212-1313-```bash
1414-CANVAS_CLIENT_ID=your_client_id
1515-CANVAS_CLIENT_SECRET=your_client_secret
1616-```
1717-1818-This will accept logins from **any Canvas domain** using the same OAuth credentials.
1919-2020-### 2. **Multiple Specific Institutions**
2121-2222-If you need different OAuth credentials for different institutions:
2323-2424-```bash
2525-CANVAS_INSTITUTIONS='[
2626- {
2727- "domain": "canvas.harvard.edu",
2828- "clientId": "xxx",
2929- "clientSecret": "yyy",
3030- "name": "Harvard University"
3131- },
3232- {
3333- "domain": "canvas.mit.edu",
3434- "clientId": "aaa",
3535- "clientSecret": "bbb",
3636- "name": "MIT"
3737- }
3838-]'
3939-```
4040-4141----
4242-4343-## How to Get Canvas OAuth Credentials
4444-4545-Each Canvas institution must register your MCP server as an OAuth application. Here's how:
4646-4747-### For Canvas Administrators:
4848-4949-1. **Go to Canvas Admin Panel**
5050- - Navigate to: Admin → Developer Keys
5151-5252-2. **Create a new Developer Key**
5353- - Click "+ Developer Key" → "+ API Key"
5454-5555-3. **Fill in the details:**
5656- - **Key Name**: Canvas MCP Server
5757- - **Owner Email**: Your email
5858- - **Redirect URIs**: `https://canvas.dunkirk.sh/api/auth/callback`
5959- - **Scopes**: Select the following:
6060- - `url:GET|/api/v1/courses`
6161- - `url:GET|/api/v1/assignments`
6262- - `url:GET|/api/v1/users/self`
6363-6464-4. **Save and Enable**
6565- - Copy the **Client ID** and **Client Secret**
6666- - Set the key state to "On"
6767-6868-5. **Provide credentials to the server**
6969- - Add the Client ID and Client Secret to your `.env` file
7070-7171-### For Users (Self-Service):
7272-7373-If you don't have admin access to your Canvas instance:
7474-7575-1. Contact your Canvas LMS administrator
7676-2. Ask them to register an OAuth application with the redirect URI: `https://canvas.dunkirk.sh/api/auth/callback`
7777-3. Request the Client ID and Client Secret
7878-4. Provide these to the Canvas MCP server administrator
7979-8080----
8181-8282-## Canvas Cloud & Inherited Developer Keys
8383-8484-Some Canvas Cloud institutions support **inherited developer keys** across a consortium. If your institution is part of a Canvas Cloud consortium, a single OAuth application might work across multiple domains.
8585-8686-Ask your Canvas administrator if this is available.
8787-8888----
8989-9090-## Environment Variables
9191-9292-Copy `.env.example` to `.env` and fill in your values:
9393-9494-```bash
9595-cp .env.example .env
9696-```
9797-9898-### Required Variables:
9999-100100-```bash
101101-# Server
102102-PORT=3000
103103-HOST=localhost
104104-BASE_URL=https://canvas.dunkirk.sh
105105-106106-# Encryption key (generate with: openssl rand -base64 32)
107107-ENCRYPTION_KEY=your_encryption_key_here
108108-109109-# Canvas OAuth (choose one of the options above)
110110-CANVAS_CLIENT_ID=your_client_id
111111-CANVAS_CLIENT_SECRET=your_client_secret
112112-113113-# Database
114114-DATABASE_PATH=./canvas-mcp.db
115115-```
116116-117117----
118118-119119-## Installation
120120-121121-```bash
122122-# Install dependencies
123123-bun install
124124-125125-# Run development server
126126-bun dev
127127-128128-# Build for production
129129-bun run build
130130-131131-# Run production server
132132-bun start
133133-```
134134-135135----
136136-137137-## Usage
138138-139139-1. **Users visit**: `https://canvas.dunkirk.sh`
140140-2. **Enter their Canvas domain**: e.g., `canvas.harvard.edu`
141141-3. **Authenticate via Canvas OAuth**
142142-4. **Receive an MCP API key** on their dashboard
143143-5. **Configure their MCP client** with the API key
144144-145145----
146146-147147-## MCP Client Configuration
148148-149149-After getting an API key, users should add this to their MCP client config:
150150-151151-```json
152152-{
153153- "mcpServers": {
154154- "canvas": {
155155- "command": "bunx",
156156- "args": ["canvas-mcp-client"],
157157- "env": {
158158- "CANVAS_MCP_API_KEY": "cmcp_...",
159159- "CANVAS_MCP_URL": "https://canvas.dunkirk.sh"
160160- }
161161- }
162162- }
163163-}
164164-```
165165-166166----
167167-168168-## Security Notes
169169-170170-- **API keys are hashed** before storage using Argon2id
171171-- **Canvas tokens are encrypted** at rest using AES-256-GCM
172172-- **OAuth state parameters** prevent CSRF attacks
173173-- **HTTPS required** in production
174174-- **Session cookies** are HttpOnly and SameSite=Lax
175175-176176----
177177-178178-## Deployment
179179-180180-Deploy to any platform that supports Bun:
181181-182182-- **Railway**: `railway up`
183183-- **Fly.io**: `fly launch`
184184-- **Docker**: See Dockerfile
185185-- **VPS**: Run with systemd or PM2
186186-187187-Make sure to:
188188-- Set `BASE_URL` to your production domain
189189-- Use HTTPS (required for OAuth)
190190-- Set a strong `ENCRYPTION_KEY`
191191-- Configure Canvas OAuth redirect URI to your production URL
192192-193193----
194194-195195-## Troubleshooting
196196-197197-### "Canvas domain is not configured"
198198-199199-The server doesn't have OAuth credentials for that Canvas instance. Either:
200200-- Use a global wildcard configuration (`CANVAS_CLIENT_ID` + `CANVAS_CLIENT_SECRET`)
201201-- Add the specific domain to `CANVAS_INSTITUTIONS`
202202-203203-### "OAuth token exchange failed"
204204-205205-- Verify the Client ID and Client Secret are correct
206206-- Check that the redirect URI in Canvas matches exactly: `https://canvas.dunkirk.sh/api/auth/callback`
207207-- Ensure the Canvas domain is correct (no `https://`, just `canvas.university.edu`)
208208-209209-### "Invalid API key"
210210-211211-- The API key might have been regenerated
212212-- Copy the new API key from the dashboard
213213-- Update your MCP client configuration
-156
TEST_MCP.md
···11-# Testing the MCP Server
22-33-## Method 1: Direct JSON-RPC Test (Quick)
44-55-Test the MCP endpoint directly with curl:
66-77-```bash
88-# Set your MCP token (get this from the dashboard)
99-TOKEN="cmcp_your_token_here"
1010-1111-# Test: List available tools
1212-curl -X POST https://canvas.bore.dunkirk.sh/mcp \
1313- -H "Authorization: Bearer $TOKEN" \
1414- -H "Content-Type: application/json" \
1515- -H "Accept: application/json, text/event-stream" \
1616- -d '{
1717- "jsonrpc": "2.0",
1818- "id": 1,
1919- "method": "tools/list",
2020- "params": {}
2121- }'
2222-2323-# Test: List courses
2424-curl -X POST https://canvas.bore.dunkirk.sh/mcp \
2525- -H "Authorization: Bearer $TOKEN" \
2626- -H "Content-Type: application/json" \
2727- -H "Accept: application/json, text/event-stream" \
2828- -d '{
2929- "jsonrpc": "2.0",
3030- "id": 2,
3131- "method": "tools/call",
3232- "params": {
3333- "name": "list_courses",
3434- "arguments": {
3535- "enrollment_state": "active"
3636- }
3737- }
3838- }'
3939-```
4040-4141-## Method 2: Claude Desktop (Real Usage)
4242-4343-1. Get your MCP token from the dashboard
4444-2. Open Claude Desktop config:
4545- - Mac: `~/Library/Application Support/Claude/claude_desktop_config.json`
4646- - Windows: `%APPDATA%\Claude\claude_desktop_config.json`
4747-4848-3. Add this configuration:
4949-5050-```json
5151-{
5252- "mcpServers": {
5353- "canvas": {
5454- "url": "https://canvas.bore.dunkirk.sh/mcp",
5555- "headers": {
5656- "Authorization": "Bearer YOUR_MCP_TOKEN_HERE"
5757- }
5858- }
5959- }
6060-}
6161-```
6262-6363-4. Restart Claude Desktop
6464-6565-5. Test by asking Claude:
6666- - "What courses am I enrolled in?"
6767- - "What assignments do I have due this week?"
6868- - "Show me details about assignment ID 12345 in course 6789"
6969-7070-## Method 3: MCP Inspector (Visual Debugging)
7171-7272-```bash
7373-# Install MCP Inspector
7474-npm install -g @modelcontextprotocol/inspector
7575-7676-# Create a config file
7777-cat > mcp-config.json <<EOF
7878-{
7979- "mcpServers": {
8080- "canvas": {
8181- "url": "https://canvas.bore.dunkirk.sh/mcp",
8282- "headers": {
8383- "Authorization": "Bearer YOUR_MCP_TOKEN_HERE"
8484- }
8585- }
8686- }
8787-}
8888-EOF
8989-9090-# Run inspector
9191-mcp-inspector mcp-config.json
9292-```
9393-9494-## Expected Responses
9595-9696-### tools/list
9797-```json
9898-{
9999- "jsonrpc": "2.0",
100100- "id": 1,
101101- "result": {
102102- "tools": [
103103- {
104104- "name": "list_courses",
105105- "description": "List Canvas courses...",
106106- "inputSchema": {...}
107107- },
108108- {
109109- "name": "search_assignments",
110110- ...
111111- },
112112- {
113113- "name": "get_assignment",
114114- ...
115115- }
116116- ]
117117- }
118118-}
119119-```
120120-121121-### tools/call (list_courses)
122122-```json
123123-{
124124- "jsonrpc": "2.0",
125125- "id": 2,
126126- "result": {
127127- "content": [
128128- {
129129- "type": "text",
130130- "text": "[{\"id\": 123, \"name\": \"Biology 101\", ...}]"
131131- }
132132- ]
133133- }
134134-}
135135-```
136136-137137-## Troubleshooting
138138-139139-**Error: "Missing session token"**
140140-- Make sure you're including the `Authorization: Bearer YOUR_TOKEN` header
141141-142142-**Error: "Invalid or expired session token"**
143143-- Your MCP token expired or was regenerated
144144-- Get a new token from the dashboard
145145-146146-**Error: "Not authenticated"**
147147-- The MCP token doesn't match any user in the database
148148-- Log in again via the web interface
149149-150150-**Error: "Unknown tool"**
151151-- Check the tool name spelling (case-sensitive)
152152-- Run `tools/list` to see available tools
153153-154154-**Error: Canvas API errors**
155155-- Your Canvas Personal Access Token may have expired (max 120 days)
156156-- Generate a new Canvas token and update via the web interface
+6-22
src/index.ts
···11-import { randomBytes } from "crypto";
11+import { randomBytes } from "node:crypto";
22import { $ } from "bun";
33-import DB from "./lib/db.js";
43import { CanvasClient } from "./lib/canvas.js";
44+import DB from "./lib/db.js";
55+import Mailer from "./lib/email.js";
56import {
66- handleMcpRequest,
77 getProtectedResourceMetadata,
88+ handleMcpRequest,
89} from "./lib/mcp-transport.js";
99-import Mailer from "./lib/email.js";
10101111-// Import HTML pages
1111+// import HTML pages
1212+import dashboardPage from "./public/dashboard.html";
1213import indexPage from "./public/index.html";
1313-import dashboardPage from "./public/dashboard.html";
14141515// Get git commit hash
1616let gitHash = "dev";
···879879 },
880880 },
881881882882- // Admin endpoint to manually trigger cleanup
883883- "/api/admin/cleanup": {
884884- POST(req: Request) {
885885- const results = DB.runAllCleanups();
886886- return Response.json({
887887- success: true,
888888- removed: results,
889889- timestamp: new Date().toISOString(),
890890- });
891891- },
892892- },
893893-894882 // Git version endpoint
895883 "/api/version": {
896884 GET() {
···920908921909// Background cleanup job - runs every 5 minutes
922910const CLEANUP_INTERVAL = 5 * 60 * 1000; // 5 minutes
923923-924924-console.log(
925925- `[Cleanup] Starting background cleanup job (interval: ${CLEANUP_INTERVAL / 1000}s)`,
926926-);
927911928912// Run initial cleanup on startup
929913DB.runAllCleanups();