a fancy canvas mcp server!
0
fork

Configure Feed

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

at main 318 lines 8.7 kB view raw
1<!DOCTYPE html> 2<html lang="en"> 3 4<head> 5 <meta charset="UTF-8"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>Canvas MCP Server</title> 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."> 10 <link rel="icon" type="image/x-icon" href="./favicon.ico"> 11 <link rel="canonical" href="https://canvas.dunkirk.sh/" id="canonical-url"> 12 <meta name="theme-color" content="#0066cc"> 13 14 <!-- Open Graph / Facebook --> 15 <meta property="og:type" content="website"> 16 <meta property="og:url" content="https://canvas.dunkirk.sh/" id="og-url"> 17 <meta property="og:title" content="Canvas MCP Server"> 18 <meta property="og:site_name" content="Canvas MCP Server"> 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."> 21 <meta property="og:image" content="https://canvas.dunkirk.sh/og.png" id="og-image"> 22 <meta property="og:image:width" content="1200"> 23 <meta property="og:image:height" content="630"> 24 <meta property="og:image:alt" content="Canvas MCP Server - Connect Canvas LMS to AI assistants"> 25 26 <!-- Twitter --> 27 <meta property="twitter:card" content="summary_large_image"> 28 <meta property="twitter:url" content="https://canvas.dunkirk.sh/" id="twitter-url"> 29 <meta property="twitter:title" content="Canvas MCP Server"> 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."> 32 <meta property="twitter:image" content="https://canvas.dunkirk.sh/og.png" id="twitter-image"> 33 34 <script> 35 // Set dynamic URLs based on current host 36 const baseUrl = window.location.origin; 37 document.getElementById('canonical-url').setAttribute('href', `${baseUrl}/`); 38 document.getElementById('og-url').setAttribute('content', `${baseUrl}/`); 39 document.getElementById('og-image').setAttribute('content', `${baseUrl}/og.png`); 40 document.getElementById('twitter-url').setAttribute('content', `${baseUrl}/`); 41 document.getElementById('twitter-image').setAttribute('content', `${baseUrl}/og.png`); 42 </script> 43 <style> 44 * { 45 margin: 0; 46 padding: 0; 47 box-sizing: border-box; 48 } 49 50 body { 51 font-family: system-ui, -apple-system, sans-serif; 52 line-height: 1.6; 53 max-width: 600px; 54 margin: 4rem auto; 55 padding: 2rem; 56 color: #111; 57 } 58 59 h1 { 60 font-size: 2rem; 61 margin-bottom: 0.5rem; 62 font-weight: 600; 63 } 64 65 p { 66 color: #555; 67 margin-bottom: 2rem; 68 } 69 70 section { 71 margin: 2rem 0; 72 padding: 1.5rem; 73 border: 1px solid #ddd; 74 border-radius: 4px; 75 } 76 77 section h2 { 78 font-size: 1.1rem; 79 margin-bottom: 1rem; 80 font-weight: 600; 81 } 82 83 ul { 84 list-style: none; 85 padding-left: 1rem; 86 } 87 88 li { 89 padding: 0.25rem 0; 90 color: #555; 91 } 92 93 li::before { 94 content: "→ "; 95 margin-right: 0.5rem; 96 } 97 98 .login-form { 99 margin-top: 2rem; 100 } 101 102 label { 103 display: block; 104 margin-bottom: 0.5rem; 105 font-weight: 500; 106 color: #333; 107 } 108 109 input { 110 width: 100%; 111 padding: 0.75rem; 112 border: 1px solid #ddd; 113 border-radius: 4px; 114 font-size: 1rem; 115 font-family: inherit; 116 } 117 118 input:focus { 119 outline: none; 120 border-color: #0066cc; 121 } 122 123 button { 124 width: 100%; 125 margin-top: 1rem; 126 padding: 0.75rem; 127 background: #0066cc; 128 color: white; 129 border: none; 130 border-radius: 4px; 131 font-size: 1rem; 132 font-weight: 500; 133 cursor: pointer; 134 } 135 136 button:hover { 137 background: #0052a3; 138 } 139 140 button:disabled { 141 background: #ccc; 142 cursor: not-allowed; 143 } 144 145 .error { 146 margin-top: 1rem; 147 padding: 0.75rem; 148 background: #fee; 149 border: 1px solid #fcc; 150 border-radius: 4px; 151 color: #c33; 152 display: none; 153 } 154 155 .error.show { 156 display: block; 157 } 158 159 .success { 160 margin-top: 1rem; 161 padding: 0.75rem; 162 background: #d4edda; 163 border: 1px solid #c3e6cb; 164 border-radius: 4px; 165 color: #155724; 166 display: none; 167 } 168 169 .success.show { 170 display: block; 171 } 172 173 footer { 174 margin-top: 4rem; 175 padding-top: 2rem; 176 border-top: 1px solid #eee; 177 text-align: center; 178 color: #999; 179 font-size: 0.9rem; 180 } 181 </style> 182</head> 183 184<body> 185 <header> 186 <h1>Canvas MCP Server</h1> 187 <p>Connect your Canvas LMS to AI assistants via the Model Context Protocol</p> 188 </header> 189 190 <section> 191 <h2>How It Works</h2> 192 <ul> 193 <li>Sign in with your email (no password needed)</li> 194 <li>Connect your Canvas account with OAuth 2.1</li> 195 <li>Use your LLM choice ask questions about your courses</li> 196 <li>Works with any Canvas institution</li> 197 </ul> 198 </section> 199 200 <section class="login-form"> 201 <h2>Get Started</h2> 202 <form id="loginForm"> 203 <label for="email">Email Address</label> 204 <input type="email" id="email" name="email" placeholder="your.email@school.edu" autocomplete="email" required /> 205 206 <button type="submit">Send Sign-In Link</button> 207 <div id="error" class="error"></div> 208 <div id="success" class="success"></div> 209 </form> 210 211 <p style="margin-top: 1.5rem; font-size: 0.9rem; color: #666;"> 212 We'll send you a magic link to sign in. No password required. 213 </p> 214 </section> 215 216 <footer> 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> 223 </footer> 224 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 if (link) { 232 link.href = `https://tangled.org/dunkirk.sh/canvas-mcp/commit/${data.hash}`; 233 link.textContent = data.shortHash; 234 } 235 }) 236 .catch(() => { }); 237 238 // Check if already logged in 239 fetch('/api/user/me', {credentials: 'include'}) 240 .then(r => { 241 if (r.ok) { 242 console.log('[Index] User logged in, redirecting to dashboard'); 243 window.location.href = '/dashboard'; 244 } else { 245 console.log('[Index] User not logged in'); 246 } 247 }) 248 .catch(err => { 249 console.log('[Index] Error checking auth:', err); 250 }); 251 252 const form = document.getElementById('loginForm'); 253 const errorDiv = document.getElementById('error'); 254 const successDiv = document.getElementById('success'); 255 256 function showError(message) { 257 errorDiv.textContent = message; 258 errorDiv.classList.add('show'); 259 successDiv.classList.remove('show'); 260 } 261 262 function showSuccess(message) { 263 successDiv.textContent = message; 264 successDiv.classList.add('show'); 265 errorDiv.classList.remove('show'); 266 } 267 268 function hideMessages() { 269 errorDiv.classList.remove('show'); 270 successDiv.classList.remove('show'); 271 } 272 273 form.addEventListener('submit', async (e) => { 274 e.preventDefault(); 275 hideMessages(); 276 277 const email = document.getElementById('email').value.trim(); 278 279 if (!email) { 280 showError('Please enter your email address'); 281 return; 282 } 283 284 if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { 285 showError('Please enter a valid email address'); 286 return; 287 } 288 289 const submitBtn = form.querySelector('button'); 290 submitBtn.disabled = true; 291 submitBtn.textContent = 'Sending...'; 292 293 try { 294 const response = await fetch('/api/auth/request-magic-link', { 295 method: 'POST', 296 headers: {'Content-Type': 'application/json'}, 297 body: JSON.stringify({email}) 298 }); 299 300 const data = await response.json(); 301 302 if (!response.ok) { 303 throw new Error(data.error || 'Failed to send magic link'); 304 } 305 306 showSuccess(`Check your email! We sent a sign-in link to ${email}`); 307 form.reset(); 308 } catch (error) { 309 showError(error.message); 310 } finally { 311 submitBtn.disabled = false; 312 submitBtn.textContent = 'Send Sign-In Link'; 313 } 314 }); 315 </script> 316</body> 317 318</html>