···11-# Canvas MCP Authentication
22-33-The Canvas MCP Server supports two authentication methods:
44-55-## 1. Personal Access Tokens (Recommended for Students)
66-77-**Best for:** Individual students, anyone without Canvas admin access
88-99-### How it works:
1010-1. Users generate their own Personal Access Token from Canvas
1111-2. Paste it into the web app
1212-3. Get an MCP API key instantly
1313-4. No admin access required
1414-1515-### Setup Instructions:
1616-1717-**For Users:**
1818-1. Log in to your Canvas account
1919-2. Go to **Account → Settings**
2020-3. Scroll to **"Approved Integrations"**
2121-4. Click **"+ New Access Token"**
2222-5. Fill in:
2323- - **Purpose:** "MCP Server" (or anything you want)
2424- - **Expires:** (optional - max 120 days, leave blank for 120 days)
2525-6. Click **"Generate Token"**
2626-7. **Copy the token** (you won't see it again!)
2727-8. Paste it into the Canvas MCP login page
2828-2929-**Security Notes:**
3030-- Personal Access Tokens have the same permissions as your Canvas account
3131-- The token is encrypted before being stored in the database
3232-- You can revoke the token anytime from Canvas Settings
3333-- Canvas limits token expiration to a maximum of 120 days
3434-- When your token expires, just generate a new one and update it on the dashboard
3535-3636----
3737-3838-## 2. OAuth 2.1 (For Institution-Wide Deployment)
3939-4040-**Best for:** Canvas administrators deploying for entire institution
4141-4242-### How it works:
4343-1. Admin registers OAuth application in Canvas
4444-2. Users click "Sign in with Canvas"
4545-3. Canvas OAuth flow handles authentication
4646-4. Server stores encrypted tokens
4747-4848-### Setup Instructions:
4949-5050-**For Canvas Administrators:**
5151-5252-See [SETUP.md](./SETUP.md) for detailed OAuth configuration instructions.
5353-5454-**Quick summary:**
5555-1. Go to Canvas Admin → Developer Keys → "+ API Key"
5656-2. Set redirect URI: `https://canvas.dunkirk.sh/api/auth/callback`
5757-3. Request scopes: courses, assignments, users (read)
5858-4. Copy Client ID and Client Secret
5959-5. Add to server `.env` file
6060-6161----
6262-6363-## Comparison
6464-6565-| Feature | Personal Access Token | OAuth |
6666-|---------|----------------------|-------|
6767-| **Setup Complexity** | Simple (2 minutes) | Complex (requires admin) |
6868-| **Who Can Use** | Any Canvas user | Requires admin setup |
6969-| **Token Management** | User manages their own | Server manages via refresh tokens |
7070-| **Expiration** | Max 120 days (user sets) | Typically 1 hour (auto-refreshed) |
7171-| **Revocation** | User revokes in Canvas | User revokes in Canvas |
7272-| **Best For** | Individual students | Institution-wide deployment |
7373-7474----
7575-7676-## Hybrid Deployment
7777-7878-The server supports **both methods simultaneously**:
7979-8080-- Students can use Personal Access Tokens (no admin needed)
8181-- Institutions can set up OAuth for easier onboarding
8282-- Users choose their preferred method on login
8383-8484-This provides maximum flexibility.
8585-8686----
8787-8888-## Security
8989-9090-Both methods are secure:
9191-9292-**Personal Access Tokens:**
9393-- Encrypted at rest using AES-256-GCM
9494-- Never logged or exposed in API responses
9595-- Only decrypted when making Canvas API calls
9696-9797-**OAuth Tokens:**
9898-- Encrypted at rest using AES-256-GCM
9999-- Automatically refreshed before expiration
100100-- Follow OAuth 2.1 best practices with PKCE
101101-102102-**MCP API Keys:**
103103-- Hashed using Argon2id before storage
104104-- Cannot be recovered (only verified)
105105-- Can be regenerated anytime by user
106106-107107----
108108-109109-## Which Method Should I Use?
110110-111111-**Use Personal Access Tokens if:**
112112-- You're a student or individual user
113113-- You don't have Canvas admin access
114114-- You want to get started in 2 minutes
115115-- You're okay managing your own token
116116-117117-**Use OAuth if:**
118118-- You're deploying for an entire institution
119119-- You have Canvas admin access
120120-- You want users to have a simpler login flow (just click a button)
121121-- You want tokens to auto-refresh
122122-123123-**Recommendation:** Start with Personal Access Tokens. They're simpler and work for everyone.
-194
MULTI_INSTITUTION.md
···11-# Multi-Institution Support - How It Works
22-33-## Your Question: "Can we make the Canvas thing work at any institution?"
44-55-**Answer: Yes!** ✅
66-77-The server is designed to work with **any Canvas institution**. Here's how:
88-99----
1010-1111-## How Multi-Institution Support Works
1212-1313-### 1. **User-Provided Domain**
1414-1515-When users visit the login page, they enter their Canvas domain (e.g., `canvas.harvard.edu`, `canvas.mit.edu`, `instructure.university.edu`).
1616-1717-The server:
1818-- Accepts **any valid Canvas domain**
1919-- Uses that domain for the OAuth flow
2020-- Stores the user's specific Canvas instance in the database
2121-- Makes API calls to **their** Canvas instance (not a shared one)
2222-2323-### 2. **OAuth Configuration Options**
2424-2525-You have three ways to configure OAuth:
2626-2727-#### Option A: **Global/Wildcard** (Simplest)
2828-```bash
2929-CANVAS_CLIENT_ID=xxx
3030-CANVAS_CLIENT_SECRET=yyy
3131-```
3232-3333-This uses **one set of OAuth credentials for all institutions**.
3434-3535-✅ **Works if**:
3636-- You have a Canvas Cloud account with inherited developer keys
3737-- Your OAuth app is registered as a "global" developer key
3838-- All institutions in your consortium share OAuth apps
3939-4040-#### Option B: **Per-Institution** (Most Flexible)
4141-```bash
4242-CANVAS_INSTITUTIONS='[
4343- {"domain":"canvas.harvard.edu","clientId":"aaa","clientSecret":"bbb"},
4444- {"domain":"canvas.mit.edu","clientId":"ccc","clientSecret":"ddd"}
4545-]'
4646-```
4747-4848-This allows **different OAuth credentials per institution**.
4949-5050-✅ **Use when**:
5151-- Each institution requires separate OAuth app registration
5252-- You want fine-grained control per school
5353-- Supporting multiple independent Canvas instances
5454-5555-#### Option C: **Hybrid** (Recommended)
5656-```bash
5757-# Global fallback
5858-CANVAS_CLIENT_ID=global_id
5959-CANVAS_CLIENT_SECRET=global_secret
6060-6161-# Specific overrides
6262-CANVAS_INSTITUTIONS='[
6363- {"domain":"canvas.special-school.edu","clientId":"xxx","clientSecret":"yyy"}
6464-]'
6565-```
6666-6767-This supports **most institutions with fallback + specific overrides**.
6868-6969----
7070-7171-## The OAuth Challenge
7272-7373-The **only requirement** is that each Canvas institution must have your OAuth application registered.
7474-7575-### Who Registers the OAuth App?
7676-7777-**Option 1: Canvas Administrators**
7878-- Each institution's Canvas admin registers your app
7979-- Provides you with Client ID and Client Secret
8080-- You add these to your server config
8181-8282-**Option 2: Canvas Cloud Inherited Keys**
8383-- Some Canvas Cloud consortiums support "inherited" developer keys
8484-- One registration works across multiple institutions
8585-- Ask Canvas support if this is available
8686-8787-**Option 3: User Self-Registration** (Not common)
8888-- Some Canvas instances allow users to create their own OAuth apps
8989-- Users would need to configure their own MCP server instance
9090-- Not practical for a shared service
9191-9292----
9393-9494-## Practical Deployment Strategy
9595-9696-### For a Public Service (like canvas.dunkirk.sh):
9797-9898-**Phase 1: Start with Major Institutions**
9999-```bash
100100-CANVAS_INSTITUTIONS='[
101101- {"domain":"canvas.harvard.edu","clientId":"...","clientSecret":"..."},
102102- {"domain":"canvas.mit.edu","clientId":"...","clientSecret":"..."},
103103- {"domain":"canvas.stanford.edu","clientId":"...","clientSecret":"..."}
104104-]'
105105-```
106106-107107-**Phase 2: Add Institutions on Request**
108108-- Users request support for their institution
109109-- Contact their Canvas admin to register OAuth app
110110-- Add credentials to `CANVAS_INSTITUTIONS`
111111-112112-**Phase 3: Global Fallback** (if possible)
113113-- Get a Canvas Cloud global developer key
114114-- Set as `CANVAS_CLIENT_ID` + `CANVAS_CLIENT_SECRET`
115115-- All institutions automatically supported
116116-117117----
118118-119119-## User Experience
120120-121121-1. **User visits** `https://canvas.dunkirk.sh`
122122-2. **Enters domain**: `canvas.myschool.edu`
123123-3. **Server checks**: Is this domain configured?
124124- - ✅ Yes → Redirect to Canvas OAuth
125125- - ❌ No → Show error: "Contact admin to add your institution"
126126-4. **After OAuth**: User gets an API key specific to their institution
127127-5. **MCP calls**: Go to **their specific Canvas instance** (not shared)
128128-129129----
130130-131131-## Technical Flow
132132-133133-```
134134-User (canvas.harvard.edu)
135135- ↓
136136-Server checks OAuth config for "canvas.harvard.edu"
137137- ↓
138138-Redirects to https://canvas.harvard.edu/login/oauth2/auth
139139- ↓
140140-Harvard Canvas authenticates user
141141- ↓
142142-Redirects back with code
143143- ↓
144144-Server exchanges code for token (at Harvard's Canvas)
145145- ↓
146146-Stores Harvard token (encrypted) + generates API key
147147- ↓
148148-MCP client uses API key
149149- ↓
150150-Server proxies requests to canvas.harvard.edu with user's token
151151-```
152152-153153-**Key point**: Each user's API calls go to **their own institution's Canvas instance**, using **their own OAuth token**.
154154-155155----
156156-157157-## What You Need to Support "Any Institution"
158158-159159-### Technically:
160160-✅ **Already built!** The server accepts any domain and handles per-institution OAuth.
161161-162162-### Practically:
163163-You need OAuth credentials for each institution. Options:
164164-165165-1. **Contact Canvas Cloud** → Ask about global developer keys
166166-2. **Start with your institution** → Get it working for one school first
167167-3. **Add institutions incrementally** → As users request support
168168-4. **Open registration** → Allow users to provide their own OAuth credentials (advanced)
169169-170170----
171171-172172-## Recommendation
173173-174174-**Start with Option A** (Global/Wildcard) if possible:
175175-- Contact Canvas support about Cloud global keys
176176-- Mention you're building a cross-institution MCP service
177177-- Ask if inherited developer keys are available
178178-179179-**If not available**, use **Option B** (Per-Institution):
180180-- Start with your own Canvas instance
181181-- Add others as requested
182182-- Build a self-service OAuth registration flow (future feature)
183183-184184----
185185-186186-## Summary
187187-188188-✅ **Yes, the server works with any Canvas institution**
189189-✅ **Users can enter any Canvas domain**
190190-✅ **Server handles institution-specific OAuth**
191191-✅ **Each user's API calls go to their own Canvas instance**
192192-193193-❓ **Only requirement**: OAuth app must be registered with each institution
194194-💡 **Solution**: Start with global config, add institutions as needed, or contact Canvas about Cloud global keys
+21
src/index.ts
···11import { randomBytes } from "crypto";
22+import { $ } from "bun";
23import DB from "./lib/db.js";
34import { CanvasClient } from "./lib/canvas.js";
45import {
···1011// Import HTML pages
1112import indexPage from "./public/index.html";
1213import dashboardPage from "./public/dashboard.html";
1414+1515+// Get git commit hash
1616+let gitHash = "dev";
1717+let gitShortHash = "dev";
1818+try {
1919+ gitHash = (await $`git rev-parse HEAD`.text()).trim();
2020+ gitShortHash = (await $`git rev-parse --short HEAD`.text()).trim();
2121+} catch (e) {
2222+ console.warn("Could not get git hash:", e);
2323+}
13241425// Configuration
1526const PORT = parseInt(process.env.PORT || "3000", 10);
···876887 success: true,
877888 removed: results,
878889 timestamp: new Date().toISOString(),
890890+ });
891891+ },
892892+ },
893893+894894+ // Git version endpoint
895895+ "/api/version": {
896896+ GET() {
897897+ return Response.json({
898898+ hash: gitHash,
899899+ shortHash: gitShortHash,
879900 });
880901 },
881902 },
+29-22
src/public/dashboard.html
···55 <meta charset="UTF-8">
66 <meta name="viewport" content="width=device-width, initial-scale=1.0">
77 <title>Dashboard - Canvas MCP</title>
88- <meta name="description" content="Manage your Canvas MCP Server connection and API credentials. Connect your Canvas LMS to AI assistants.">
88+ <meta name="description"
99+ content="Manage your Canvas MCP Server connection and API credentials. Connect your Canvas LMS to AI assistants.">
910 <link rel="icon" type="image/x-icon" href="./favicon.ico">
1011 <link rel="canonical" href="https://canvas.dunkirk.sh/dashboard" id="canonical-url">
1112 <meta name="theme-color" content="#0066cc">
···1516 <meta property="og:url" content="https://canvas.dunkirk.sh/dashboard" id="og-url">
1617 <meta property="og:title" content="Dashboard - Canvas MCP">
1718 <meta property="og:site_name" content="Canvas MCP Server">
1818- <meta property="og:description" content="Manage your Canvas MCP Server connection and API credentials. Connect your Canvas LMS to AI assistants.">
1919+ <meta property="og:description"
2020+ content="Manage your Canvas MCP Server connection and API credentials. Connect your Canvas LMS to AI assistants.">
1921 <meta property="og:image" content="https://canvas.dunkirk.sh/og.png" id="og-image">
2022 <meta property="og:image:width" content="1200">
2123 <meta property="og:image:height" content="630">
···2527 <meta property="twitter:card" content="summary_large_image">
2628 <meta property="twitter:url" content="https://canvas.dunkirk.sh/dashboard" id="twitter-url">
2729 <meta property="twitter:title" content="Dashboard - Canvas MCP">
2828- <meta property="twitter:description" content="Manage your Canvas MCP Server connection and API credentials. Connect your Canvas LMS to AI assistants.">
3030+ <meta property="twitter:description"
3131+ content="Manage your Canvas MCP Server connection and API credentials. Connect your Canvas LMS to AI assistants.">
2932 <meta property="twitter:image" content="https://canvas.dunkirk.sh/og.png" id="twitter-image">
30333134 <script>
···288291 <section id="mcpConnectionSection" style="display: none;">
289292 <h2>MCP Server Connection</h2>
290293 <p style="color: #666; margin-bottom: 1.5rem;">
291291- Use these credentials to connect your MCP client (Claude Desktop, Cursor, etc.) to your Canvas account.
294294+ Use these credentials to connect your MCP client (Claude Desktop, Poke, etc.) to your Canvas account.
292295 </p>
293296294297 <!-- MCP Endpoint URL -->
···301304 <span id="mcpUrl"></span>
302305 </div>
303306 <p style="font-size: 0.85rem; color: #666; margin-top: 0.5rem;">
304304- This is your MCP server endpoint URL. You'll need this for Claude Desktop configuration.
307307+ This is your MCP server endpoint URL.
305308 </p>
306309 </div>
307310···339342 <section id="quickSetupSection" style="display: none;">
340343 <h2>Quick Setup</h2>
341344 <p style="color: #666; margin-bottom: 1rem;">
342342- Add this configuration to your Claude Desktop config file:
345345+ Add the MCP url to the claude connections page as a custom connection and then authorize it. Poke and some other
346346+ clients require both the api key and the mcp url.
343347 </p>
344344- <div style="background: #f9f9f9; padding: 1rem; border-radius: 4px; margin-bottom: 1rem;">
345345- <p style="font-size: 0.9rem; margin-bottom: 0.5rem;">
346346- <strong>Config File Location:</strong>
347347- </p>
348348- <code style="display: block; padding: 0.5rem; background: white; border-radius: 4px; font-size: 0.85rem;">
349349- ~/Library/Application Support/Claude/claude_desktop_config.json
350350- </code>
351351- </div>
352352- <pre class="config-block" id="configBlock"></pre>
353353- <button id="copyConfigBtn" style="margin-top: 1rem;">Copy Configuration</button>
354348 </section>
355349356350 <section id="usageStatsSection" style="display: none;">
···365359366360 <div class="notification" id="notification"></div>
367361362362+ <footer style="margin-top: 4rem; padding-top: 2rem; border-top: 1px solid #ddd; font-size: 0.9rem;">
363363+ <div style="display: flex; justify-content: space-between; align-items: center;">
364364+ <span style="color: #666;">made by <a href="https://dunkirk.sh" style="color: #666; text-decoration: none;">kieran
365365+ klukas</a></span>
366366+ <a id="git-hash-link" href="#"
367367+ style="color: #999; text-decoration: none; font-family: monospace; font-size: 0.85rem;">...</a>
368368+ </div>
369369+ </footer>
370370+368371 <script type="module">
372372+ // Load git hash
373373+ fetch('/api/version')
374374+ .then(r => r.json())
375375+ .then(data => {
376376+ const link = document.getElementById('git-hash-link');
377377+ link.href = `https://tangled.org/dunkirk.sh/canvas-mcp/commit/${data.hash}`;
378378+ link.textContent = data.shortHash;
379379+ })
380380+ .catch(() => { });
381381+369382 let userData = null;
370383 let apiKeyVisible = false;
371384···500513 document.getElementById('copyKeyBtn').addEventListener('click', async () => {
501514 await navigator.clipboard.writeText(userData.api_key);
502515 showNotification('API token copied to clipboard');
503503- });
504504-505505- document.getElementById('copyConfigBtn').addEventListener('click', async () => {
506506- const configText = document.getElementById('configBlock').textContent;
507507- await navigator.clipboard.writeText(configText);
508508- showNotification('Configuration copied to clipboard');
509516 });
510517511518 let regenerateState = 'initial'; // 'initial', 'confirm', 'show-token'
+23-5
src/public/index.html
···55 <meta charset="UTF-8">
66 <meta name="viewport" content="width=device-width, initial-scale=1.0">
77 <title>Canvas MCP Server</title>
88- <meta name="description" content="Connect your Canvas LMS to AI assistants via the Model Context Protocol. Ask questions about your courses with Claude Desktop.">
88+ <meta name="description"
99+ content="Connect your Canvas LMS to AI assistants via the Model Context Protocol. Ask questions about your courses with Claude Desktop.">
910 <link rel="icon" type="image/x-icon" href="./favicon.ico">
1011 <link rel="canonical" href="https://canvas.dunkirk.sh/" id="canonical-url">
1112 <meta name="theme-color" content="#0066cc">
···1516 <meta property="og:url" content="https://canvas.dunkirk.sh/" id="og-url">
1617 <meta property="og:title" content="Canvas MCP Server">
1718 <meta property="og:site_name" content="Canvas MCP Server">
1818- <meta property="og:description" content="Connect your Canvas LMS to AI assistants via the Model Context Protocol. Ask questions about your courses with Claude Desktop.">
1919+ <meta property="og:description"
2020+ content="Connect your Canvas LMS to AI assistants via the Model Context Protocol. Ask questions about your courses with Claude Desktop.">
1921 <meta property="og:image" content="https://canvas.dunkirk.sh/og.png" id="og-image">
2022 <meta property="og:image:width" content="1200">
2123 <meta property="og:image:height" content="630">
···2527 <meta property="twitter:card" content="summary_large_image">
2628 <meta property="twitter:url" content="https://canvas.dunkirk.sh/" id="twitter-url">
2729 <meta property="twitter:title" content="Canvas MCP Server">
2828- <meta property="twitter:description" content="Connect your Canvas LMS to AI assistants via the Model Context Protocol. Ask questions about your courses with Claude Desktop.">
3030+ <meta property="twitter:description"
3131+ content="Connect your Canvas LMS to AI assistants via the Model Context Protocol. Ask questions about your courses with Claude Desktop.">
2932 <meta property="twitter:image" content="https://canvas.dunkirk.sh/og.png" id="twitter-image">
30333134 <script>
···189192 <ul>
190193 <li>Sign in with your email (no password needed)</li>
191194 <li>Connect your Canvas account with OAuth 2.1</li>
192192- <li>Use Claude Desktop to ask questions about your courses</li>
195195+ <li>Use your LLM choice ask questions about your courses</li>
193196 <li>Works with any Canvas institution</li>
194197 </ul>
195198 </section>
···211214 </section>
212215213216 <footer>
214214- <p>Canvas MCP Server · <a href="https://modelcontextprotocol.io">MCP Documentation</a></p>
217217+ <div style="display: flex; justify-content: space-between; align-items: center;">
218218+ <span style="color: #666;">made by <a href="https://dunkirk.sh" style="color: #666; text-decoration: none;">kieran
219219+ klukas</a></span>
220220+ <a id="git-hash-link" href="#"
221221+ style="color: #999; text-decoration: none; font-family: monospace; font-size: 0.85rem;">...</a>
222222+ </div>
215223 </footer>
216224217225 <script type="module">
226226+ // Load git hash
227227+ fetch('/api/version')
228228+ .then(r => r.json())
229229+ .then(data => {
230230+ const link = document.getElementById('git-hash-link');
231231+ link.href = `https://tangled.org/dunkirk.sh/canvas-mcp/commit/${data.hash}`;
232232+ link.textContent = data.shortHash;
233233+ })
234234+ .catch(() => { });
235235+218236 // Check if already logged in
219237 fetch('/api/user/me', {credentials: 'include'})
220238 .then(r => {