a fancy canvas mcp server!
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add commit hash

+73 -344
-123
AUTHENTICATION.md
··· 1 - # Canvas MCP Authentication 2 - 3 - The Canvas MCP Server supports two authentication methods: 4 - 5 - ## 1. Personal Access Tokens (Recommended for Students) 6 - 7 - **Best for:** Individual students, anyone without Canvas admin access 8 - 9 - ### How it works: 10 - 1. Users generate their own Personal Access Token from Canvas 11 - 2. Paste it into the web app 12 - 3. Get an MCP API key instantly 13 - 4. No admin access required 14 - 15 - ### Setup Instructions: 16 - 17 - **For Users:** 18 - 1. Log in to your Canvas account 19 - 2. Go to **Account → Settings** 20 - 3. Scroll to **"Approved Integrations"** 21 - 4. Click **"+ New Access Token"** 22 - 5. Fill in: 23 - - **Purpose:** "MCP Server" (or anything you want) 24 - - **Expires:** (optional - max 120 days, leave blank for 120 days) 25 - 6. Click **"Generate Token"** 26 - 7. **Copy the token** (you won't see it again!) 27 - 8. Paste it into the Canvas MCP login page 28 - 29 - **Security Notes:** 30 - - Personal Access Tokens have the same permissions as your Canvas account 31 - - The token is encrypted before being stored in the database 32 - - You can revoke the token anytime from Canvas Settings 33 - - Canvas limits token expiration to a maximum of 120 days 34 - - When your token expires, just generate a new one and update it on the dashboard 35 - 36 - --- 37 - 38 - ## 2. OAuth 2.1 (For Institution-Wide Deployment) 39 - 40 - **Best for:** Canvas administrators deploying for entire institution 41 - 42 - ### How it works: 43 - 1. Admin registers OAuth application in Canvas 44 - 2. Users click "Sign in with Canvas" 45 - 3. Canvas OAuth flow handles authentication 46 - 4. Server stores encrypted tokens 47 - 48 - ### Setup Instructions: 49 - 50 - **For Canvas Administrators:** 51 - 52 - See [SETUP.md](./SETUP.md) for detailed OAuth configuration instructions. 53 - 54 - **Quick summary:** 55 - 1. Go to Canvas Admin → Developer Keys → "+ API Key" 56 - 2. Set redirect URI: `https://canvas.dunkirk.sh/api/auth/callback` 57 - 3. Request scopes: courses, assignments, users (read) 58 - 4. Copy Client ID and Client Secret 59 - 5. Add to server `.env` file 60 - 61 - --- 62 - 63 - ## Comparison 64 - 65 - | Feature | Personal Access Token | OAuth | 66 - |---------|----------------------|-------| 67 - | **Setup Complexity** | Simple (2 minutes) | Complex (requires admin) | 68 - | **Who Can Use** | Any Canvas user | Requires admin setup | 69 - | **Token Management** | User manages their own | Server manages via refresh tokens | 70 - | **Expiration** | Max 120 days (user sets) | Typically 1 hour (auto-refreshed) | 71 - | **Revocation** | User revokes in Canvas | User revokes in Canvas | 72 - | **Best For** | Individual students | Institution-wide deployment | 73 - 74 - --- 75 - 76 - ## Hybrid Deployment 77 - 78 - The server supports **both methods simultaneously**: 79 - 80 - - Students can use Personal Access Tokens (no admin needed) 81 - - Institutions can set up OAuth for easier onboarding 82 - - Users choose their preferred method on login 83 - 84 - This provides maximum flexibility. 85 - 86 - --- 87 - 88 - ## Security 89 - 90 - Both methods are secure: 91 - 92 - **Personal Access Tokens:** 93 - - Encrypted at rest using AES-256-GCM 94 - - Never logged or exposed in API responses 95 - - Only decrypted when making Canvas API calls 96 - 97 - **OAuth Tokens:** 98 - - Encrypted at rest using AES-256-GCM 99 - - Automatically refreshed before expiration 100 - - Follow OAuth 2.1 best practices with PKCE 101 - 102 - **MCP API Keys:** 103 - - Hashed using Argon2id before storage 104 - - Cannot be recovered (only verified) 105 - - Can be regenerated anytime by user 106 - 107 - --- 108 - 109 - ## Which Method Should I Use? 110 - 111 - **Use Personal Access Tokens if:** 112 - - You're a student or individual user 113 - - You don't have Canvas admin access 114 - - You want to get started in 2 minutes 115 - - You're okay managing your own token 116 - 117 - **Use OAuth if:** 118 - - You're deploying for an entire institution 119 - - You have Canvas admin access 120 - - You want users to have a simpler login flow (just click a button) 121 - - You want tokens to auto-refresh 122 - 123 - **Recommendation:** Start with Personal Access Tokens. They're simpler and work for everyone.
-194
MULTI_INSTITUTION.md
··· 1 - # Multi-Institution Support - How It Works 2 - 3 - ## Your Question: "Can we make the Canvas thing work at any institution?" 4 - 5 - **Answer: Yes!** ✅ 6 - 7 - The server is designed to work with **any Canvas institution**. Here's how: 8 - 9 - --- 10 - 11 - ## How Multi-Institution Support Works 12 - 13 - ### 1. **User-Provided Domain** 14 - 15 - When users visit the login page, they enter their Canvas domain (e.g., `canvas.harvard.edu`, `canvas.mit.edu`, `instructure.university.edu`). 16 - 17 - The server: 18 - - Accepts **any valid Canvas domain** 19 - - Uses that domain for the OAuth flow 20 - - Stores the user's specific Canvas instance in the database 21 - - Makes API calls to **their** Canvas instance (not a shared one) 22 - 23 - ### 2. **OAuth Configuration Options** 24 - 25 - You have three ways to configure OAuth: 26 - 27 - #### Option A: **Global/Wildcard** (Simplest) 28 - ```bash 29 - CANVAS_CLIENT_ID=xxx 30 - CANVAS_CLIENT_SECRET=yyy 31 - ``` 32 - 33 - This uses **one set of OAuth credentials for all institutions**. 34 - 35 - ✅ **Works if**: 36 - - You have a Canvas Cloud account with inherited developer keys 37 - - Your OAuth app is registered as a "global" developer key 38 - - All institutions in your consortium share OAuth apps 39 - 40 - #### Option B: **Per-Institution** (Most Flexible) 41 - ```bash 42 - CANVAS_INSTITUTIONS='[ 43 - {"domain":"canvas.harvard.edu","clientId":"aaa","clientSecret":"bbb"}, 44 - {"domain":"canvas.mit.edu","clientId":"ccc","clientSecret":"ddd"} 45 - ]' 46 - ``` 47 - 48 - This allows **different OAuth credentials per institution**. 49 - 50 - ✅ **Use when**: 51 - - Each institution requires separate OAuth app registration 52 - - You want fine-grained control per school 53 - - Supporting multiple independent Canvas instances 54 - 55 - #### Option C: **Hybrid** (Recommended) 56 - ```bash 57 - # Global fallback 58 - CANVAS_CLIENT_ID=global_id 59 - CANVAS_CLIENT_SECRET=global_secret 60 - 61 - # Specific overrides 62 - CANVAS_INSTITUTIONS='[ 63 - {"domain":"canvas.special-school.edu","clientId":"xxx","clientSecret":"yyy"} 64 - ]' 65 - ``` 66 - 67 - This supports **most institutions with fallback + specific overrides**. 68 - 69 - --- 70 - 71 - ## The OAuth Challenge 72 - 73 - The **only requirement** is that each Canvas institution must have your OAuth application registered. 74 - 75 - ### Who Registers the OAuth App? 76 - 77 - **Option 1: Canvas Administrators** 78 - - Each institution's Canvas admin registers your app 79 - - Provides you with Client ID and Client Secret 80 - - You add these to your server config 81 - 82 - **Option 2: Canvas Cloud Inherited Keys** 83 - - Some Canvas Cloud consortiums support "inherited" developer keys 84 - - One registration works across multiple institutions 85 - - Ask Canvas support if this is available 86 - 87 - **Option 3: User Self-Registration** (Not common) 88 - - Some Canvas instances allow users to create their own OAuth apps 89 - - Users would need to configure their own MCP server instance 90 - - Not practical for a shared service 91 - 92 - --- 93 - 94 - ## Practical Deployment Strategy 95 - 96 - ### For a Public Service (like canvas.dunkirk.sh): 97 - 98 - **Phase 1: Start with Major Institutions** 99 - ```bash 100 - CANVAS_INSTITUTIONS='[ 101 - {"domain":"canvas.harvard.edu","clientId":"...","clientSecret":"..."}, 102 - {"domain":"canvas.mit.edu","clientId":"...","clientSecret":"..."}, 103 - {"domain":"canvas.stanford.edu","clientId":"...","clientSecret":"..."} 104 - ]' 105 - ``` 106 - 107 - **Phase 2: Add Institutions on Request** 108 - - Users request support for their institution 109 - - Contact their Canvas admin to register OAuth app 110 - - Add credentials to `CANVAS_INSTITUTIONS` 111 - 112 - **Phase 3: Global Fallback** (if possible) 113 - - Get a Canvas Cloud global developer key 114 - - Set as `CANVAS_CLIENT_ID` + `CANVAS_CLIENT_SECRET` 115 - - All institutions automatically supported 116 - 117 - --- 118 - 119 - ## User Experience 120 - 121 - 1. **User visits** `https://canvas.dunkirk.sh` 122 - 2. **Enters domain**: `canvas.myschool.edu` 123 - 3. **Server checks**: Is this domain configured? 124 - - ✅ Yes → Redirect to Canvas OAuth 125 - - ❌ No → Show error: "Contact admin to add your institution" 126 - 4. **After OAuth**: User gets an API key specific to their institution 127 - 5. **MCP calls**: Go to **their specific Canvas instance** (not shared) 128 - 129 - --- 130 - 131 - ## Technical Flow 132 - 133 - ``` 134 - User (canvas.harvard.edu) 135 - 136 - Server checks OAuth config for "canvas.harvard.edu" 137 - 138 - Redirects to https://canvas.harvard.edu/login/oauth2/auth 139 - 140 - Harvard Canvas authenticates user 141 - 142 - Redirects back with code 143 - 144 - Server exchanges code for token (at Harvard's Canvas) 145 - 146 - Stores Harvard token (encrypted) + generates API key 147 - 148 - MCP client uses API key 149 - 150 - Server proxies requests to canvas.harvard.edu with user's token 151 - ``` 152 - 153 - **Key point**: Each user's API calls go to **their own institution's Canvas instance**, using **their own OAuth token**. 154 - 155 - --- 156 - 157 - ## What You Need to Support "Any Institution" 158 - 159 - ### Technically: 160 - ✅ **Already built!** The server accepts any domain and handles per-institution OAuth. 161 - 162 - ### Practically: 163 - You need OAuth credentials for each institution. Options: 164 - 165 - 1. **Contact Canvas Cloud** → Ask about global developer keys 166 - 2. **Start with your institution** → Get it working for one school first 167 - 3. **Add institutions incrementally** → As users request support 168 - 4. **Open registration** → Allow users to provide their own OAuth credentials (advanced) 169 - 170 - --- 171 - 172 - ## Recommendation 173 - 174 - **Start with Option A** (Global/Wildcard) if possible: 175 - - Contact Canvas support about Cloud global keys 176 - - Mention you're building a cross-institution MCP service 177 - - Ask if inherited developer keys are available 178 - 179 - **If not available**, use **Option B** (Per-Institution): 180 - - Start with your own Canvas instance 181 - - Add others as requested 182 - - Build a self-service OAuth registration flow (future feature) 183 - 184 - --- 185 - 186 - ## Summary 187 - 188 - ✅ **Yes, the server works with any Canvas institution** 189 - ✅ **Users can enter any Canvas domain** 190 - ✅ **Server handles institution-specific OAuth** 191 - ✅ **Each user's API calls go to their own Canvas instance** 192 - 193 - ❓ **Only requirement**: OAuth app must be registered with each institution 194 - 💡 **Solution**: Start with global config, add institutions as needed, or contact Canvas about Cloud global keys
+21
src/index.ts
··· 1 1 import { randomBytes } from "crypto"; 2 + import { $ } from "bun"; 2 3 import DB from "./lib/db.js"; 3 4 import { CanvasClient } from "./lib/canvas.js"; 4 5 import { ··· 10 11 // Import HTML pages 11 12 import indexPage from "./public/index.html"; 12 13 import dashboardPage from "./public/dashboard.html"; 14 + 15 + // Get git commit hash 16 + let gitHash = "dev"; 17 + let gitShortHash = "dev"; 18 + try { 19 + gitHash = (await $`git rev-parse HEAD`.text()).trim(); 20 + gitShortHash = (await $`git rev-parse --short HEAD`.text()).trim(); 21 + } catch (e) { 22 + console.warn("Could not get git hash:", e); 23 + } 13 24 14 25 // Configuration 15 26 const PORT = parseInt(process.env.PORT || "3000", 10); ··· 876 887 success: true, 877 888 removed: results, 878 889 timestamp: new Date().toISOString(), 890 + }); 891 + }, 892 + }, 893 + 894 + // Git version endpoint 895 + "/api/version": { 896 + GET() { 897 + return Response.json({ 898 + hash: gitHash, 899 + shortHash: gitShortHash, 879 900 }); 880 901 }, 881 902 },
+29 -22
src/public/dashboard.html
··· 5 5 <meta charset="UTF-8"> 6 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 7 <title>Dashboard - Canvas MCP</title> 8 - <meta name="description" content="Manage your Canvas MCP Server connection and API credentials. Connect your Canvas LMS to AI assistants."> 8 + <meta name="description" 9 + content="Manage your Canvas MCP Server connection and API credentials. Connect your Canvas LMS to AI assistants."> 9 10 <link rel="icon" type="image/x-icon" href="./favicon.ico"> 10 11 <link rel="canonical" href="https://canvas.dunkirk.sh/dashboard" id="canonical-url"> 11 12 <meta name="theme-color" content="#0066cc"> ··· 15 16 <meta property="og:url" content="https://canvas.dunkirk.sh/dashboard" id="og-url"> 16 17 <meta property="og:title" content="Dashboard - Canvas MCP"> 17 18 <meta property="og:site_name" content="Canvas MCP Server"> 18 - <meta property="og:description" content="Manage your Canvas MCP Server connection and API credentials. Connect your Canvas LMS to AI assistants."> 19 + <meta property="og:description" 20 + content="Manage your Canvas MCP Server connection and API credentials. Connect your Canvas LMS to AI assistants."> 19 21 <meta property="og:image" content="https://canvas.dunkirk.sh/og.png" id="og-image"> 20 22 <meta property="og:image:width" content="1200"> 21 23 <meta property="og:image:height" content="630"> ··· 25 27 <meta property="twitter:card" content="summary_large_image"> 26 28 <meta property="twitter:url" content="https://canvas.dunkirk.sh/dashboard" id="twitter-url"> 27 29 <meta property="twitter:title" content="Dashboard - Canvas MCP"> 28 - <meta property="twitter:description" content="Manage your Canvas MCP Server connection and API credentials. Connect your Canvas LMS to AI assistants."> 30 + <meta property="twitter:description" 31 + content="Manage your Canvas MCP Server connection and API credentials. Connect your Canvas LMS to AI assistants."> 29 32 <meta property="twitter:image" content="https://canvas.dunkirk.sh/og.png" id="twitter-image"> 30 33 31 34 <script> ··· 288 291 <section id="mcpConnectionSection" style="display: none;"> 289 292 <h2>MCP Server Connection</h2> 290 293 <p style="color: #666; margin-bottom: 1.5rem;"> 291 - Use these credentials to connect your MCP client (Claude Desktop, Cursor, etc.) to your Canvas account. 294 + Use these credentials to connect your MCP client (Claude Desktop, Poke, etc.) to your Canvas account. 292 295 </p> 293 296 294 297 <!-- MCP Endpoint URL --> ··· 301 304 <span id="mcpUrl"></span> 302 305 </div> 303 306 <p style="font-size: 0.85rem; color: #666; margin-top: 0.5rem;"> 304 - This is your MCP server endpoint URL. You'll need this for Claude Desktop configuration. 307 + This is your MCP server endpoint URL. 305 308 </p> 306 309 </div> 307 310 ··· 339 342 <section id="quickSetupSection" style="display: none;"> 340 343 <h2>Quick Setup</h2> 341 344 <p style="color: #666; margin-bottom: 1rem;"> 342 - Add this configuration to your Claude Desktop config file: 345 + Add the MCP url to the claude connections page as a custom connection and then authorize it. Poke and some other 346 + clients require both the api key and the mcp url. 343 347 </p> 344 - <div style="background: #f9f9f9; padding: 1rem; border-radius: 4px; margin-bottom: 1rem;"> 345 - <p style="font-size: 0.9rem; margin-bottom: 0.5rem;"> 346 - <strong>Config File Location:</strong> 347 - </p> 348 - <code style="display: block; padding: 0.5rem; background: white; border-radius: 4px; font-size: 0.85rem;"> 349 - ~/Library/Application Support/Claude/claude_desktop_config.json 350 - </code> 351 - </div> 352 - <pre class="config-block" id="configBlock"></pre> 353 - <button id="copyConfigBtn" style="margin-top: 1rem;">Copy Configuration</button> 354 348 </section> 355 349 356 350 <section id="usageStatsSection" style="display: none;"> ··· 365 359 366 360 <div class="notification" id="notification"></div> 367 361 362 + <footer style="margin-top: 4rem; padding-top: 2rem; border-top: 1px solid #ddd; font-size: 0.9rem;"> 363 + <div style="display: flex; justify-content: space-between; align-items: center;"> 364 + <span style="color: #666;">made by <a href="https://dunkirk.sh" style="color: #666; text-decoration: none;">kieran 365 + klukas</a></span> 366 + <a id="git-hash-link" href="#" 367 + style="color: #999; text-decoration: none; font-family: monospace; font-size: 0.85rem;">...</a> 368 + </div> 369 + </footer> 370 + 368 371 <script type="module"> 372 + // Load git hash 373 + fetch('/api/version') 374 + .then(r => r.json()) 375 + .then(data => { 376 + const link = document.getElementById('git-hash-link'); 377 + link.href = `https://tangled.org/dunkirk.sh/canvas-mcp/commit/${data.hash}`; 378 + link.textContent = data.shortHash; 379 + }) 380 + .catch(() => { }); 381 + 369 382 let userData = null; 370 383 let apiKeyVisible = false; 371 384 ··· 500 513 document.getElementById('copyKeyBtn').addEventListener('click', async () => { 501 514 await navigator.clipboard.writeText(userData.api_key); 502 515 showNotification('API token copied to clipboard'); 503 - }); 504 - 505 - document.getElementById('copyConfigBtn').addEventListener('click', async () => { 506 - const configText = document.getElementById('configBlock').textContent; 507 - await navigator.clipboard.writeText(configText); 508 - showNotification('Configuration copied to clipboard'); 509 516 }); 510 517 511 518 let regenerateState = 'initial'; // 'initial', 'confirm', 'show-token'
+23 -5
src/public/index.html
··· 5 5 <meta charset="UTF-8"> 6 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 7 <title>Canvas MCP Server</title> 8 - <meta name="description" content="Connect your Canvas LMS to AI assistants via the Model Context Protocol. Ask questions about your courses with Claude Desktop."> 8 + <meta name="description" 9 + content="Connect your Canvas LMS to AI assistants via the Model Context Protocol. Ask questions about your courses with Claude Desktop."> 9 10 <link rel="icon" type="image/x-icon" href="./favicon.ico"> 10 11 <link rel="canonical" href="https://canvas.dunkirk.sh/" id="canonical-url"> 11 12 <meta name="theme-color" content="#0066cc"> ··· 15 16 <meta property="og:url" content="https://canvas.dunkirk.sh/" id="og-url"> 16 17 <meta property="og:title" content="Canvas MCP Server"> 17 18 <meta property="og:site_name" content="Canvas MCP Server"> 18 - <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."> 19 + <meta property="og:description" 20 + content="Connect your Canvas LMS to AI assistants via the Model Context Protocol. Ask questions about your courses with Claude Desktop."> 19 21 <meta property="og:image" content="https://canvas.dunkirk.sh/og.png" id="og-image"> 20 22 <meta property="og:image:width" content="1200"> 21 23 <meta property="og:image:height" content="630"> ··· 25 27 <meta property="twitter:card" content="summary_large_image"> 26 28 <meta property="twitter:url" content="https://canvas.dunkirk.sh/" id="twitter-url"> 27 29 <meta property="twitter:title" content="Canvas MCP Server"> 28 - <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."> 30 + <meta property="twitter:description" 31 + content="Connect your Canvas LMS to AI assistants via the Model Context Protocol. Ask questions about your courses with Claude Desktop."> 29 32 <meta property="twitter:image" content="https://canvas.dunkirk.sh/og.png" id="twitter-image"> 30 33 31 34 <script> ··· 189 192 <ul> 190 193 <li>Sign in with your email (no password needed)</li> 191 194 <li>Connect your Canvas account with OAuth 2.1</li> 192 - <li>Use Claude Desktop to ask questions about your courses</li> 195 + <li>Use your LLM choice ask questions about your courses</li> 193 196 <li>Works with any Canvas institution</li> 194 197 </ul> 195 198 </section> ··· 211 214 </section> 212 215 213 216 <footer> 214 - <p>Canvas MCP Server &middot; <a href="https://modelcontextprotocol.io">MCP Documentation</a></p> 217 + <div style="display: flex; justify-content: space-between; align-items: center;"> 218 + <span style="color: #666;">made by <a href="https://dunkirk.sh" style="color: #666; text-decoration: none;">kieran 219 + klukas</a></span> 220 + <a id="git-hash-link" href="#" 221 + style="color: #999; text-decoration: none; font-family: monospace; font-size: 0.85rem;">...</a> 222 + </div> 215 223 </footer> 216 224 217 225 <script type="module"> 226 + // Load git hash 227 + fetch('/api/version') 228 + .then(r => r.json()) 229 + .then(data => { 230 + const link = document.getElementById('git-hash-link'); 231 + link.href = `https://tangled.org/dunkirk.sh/canvas-mcp/commit/${data.hash}`; 232 + link.textContent = data.shortHash; 233 + }) 234 + .catch(() => { }); 235 + 218 236 // Check if already logged in 219 237 fetch('/api/user/me', {credentials: 'include'}) 220 238 .then(r => {