The full upstream README, mirrored here for reference. Install config, tool schemas, adoption signals, and an original overview live on the Canvas Lms MCP listing page.
The TypeScript MCP server for Canvas LMS.
MCP server for Canvas LMS. Read courses, assignments, submissions, rubrics, quizzes; grade, comment, manage course content, and handle Canvas admin workflows from any AI agent.
165 tools across Canvas courses, assignments, submissions, gradebook history, rubrics, quizzes, New Quizzes (LTI), files, users, groups, enrollments, discussions, modules, pages, calendar, conversations, peer reviews, accounts, analytics, outcomes, grading standards, grade projection, link audit, accessibility audit, content exports, content migrations, quiz accommodations, appointment groups, student workflows, student search, dashboard, instructor attention workflows, and health checks. Three deployment modes: stdio, HTTP, and library import.
canvas-lms-mcp.mcpb from the latest release.https://school.instructure.com (do not append /api/v1). Teachers and staff handling student data can also flip FERPA mode — pseudonymize students on in the same dialog (what it does).No terminal, no Node.js install, no config-file editing — Claude Desktop bundles the runtime and handles config for you. The same .mcpb works in Claude Code and MCP for Windows.
Prefer the terminal? Use the Quick Start below.
Click a badge to open Cursor or VS Code with canvas-lms-mcp pre-configured (placeholder credentials filled in — replace with your actual Canvas API token and base URL after install). For manual config-file setup, see docs/manual-setup.md.
Installs the MCP server (via npx canvas-lms-mcp) and all 16 Agent Skills in a single step, versioned and updatable through Claude Code's plugin manager. On enable, Claude Code prompts for your Canvas API token and base URL (and the optional FERPA pseudonymization settings). See the Claude Code plugins reference for how marketplaces and plugin manifests work.
| canvas-lms-mcp | vishalsachdev/canvas-mcp | DMontgomery40/mcp-canvas-lms | |
|---|---|---|---|
| Language | TypeScript | Python | TypeScript |
| Tools | 165 | 80+ | 54 |
| License | |||
| Last commit |
The wizard detects your installed AI clients (Claude Desktop, Cursor, VS Code, Windsurf, Codex, Continue, Claude Code), prompts for your Canvas token and base URL, validates the credentials against your Canvas instance, and writes the config for every client you select.
add-mcp is also supported as a generic alternative: npx add-mcp canvas-lms-mcp.
For clients not yet supported by the wizard, or if you prefer editing config files by hand, see docs/manual-setup.md.
Install reusable Canvas workflows into Claude Code, Cursor, GitHub Copilot, Cline, and 40+ other AI agents:
| Skill | Description |
|---|---|
canvas-at-risk-students | Surface students with missing assignments or declining grades and send targeted outreach |
canvas-gradebook-audit | Inspect the full grade-change audit trail — who changed what grade, when, and by how much |
canvas-outcome-tracker | Track learning outcome mastery and class-wide proficiency for accreditation and program review |
canvas-accessibility-sweep | Pre-launch WCAG accessibility and broken-link sweep of a course, with a prioritised remediation list |
canvas-office-hours | Create, publish, and manage Canvas Scheduler office-hour sign-up slots and see who reserved |
Skills are markdown workflow files (no extra dependencies). They work with the MCP server you already have installed. See the skills/ directory for the full list.
Once configured, try these prompts with your AI client:
| Category | Tools |
|---|---|
| Health | health_check |
| Courses | list_courses, get_course, get_syllabus, create_course, update_course |
| Assignments | list_assignments, get_assignment, list_assignment_groups, create_assignment, update_assignment, delete_assignment |
| Assignment Overrides | list_assignment_overrides, create_assignment_override, set_student_assignment_dates |
| Submissions | list_submissions, get_submission, grade_submission, comment_on_submission |
| Submissions Awaiting Grading | list_submissions_awaiting_grading |
| Submission Files | list_course_submission_files |
| Rubrics | list_rubrics, get_rubric, get_rubric_assessment, submit_rubric_assessment, create_rubric |
| Quizzes | list_quizzes, get_quiz, list_quiz_submissions, list_quiz_questions, get_quiz_submission_answers, score_quiz_question, get_quiz_submission_events |
| Quiz Question Responses | get_quiz_question_responses |
| Quiz Accommodations | list_student_quiz_accommodations, set_student_quiz_accommodation |
| New Quizzes (LTI) | create_new_quiz, update_new_quiz, delete_new_quiz, list_new_quiz_items, get_new_quiz_item, create_new_quiz_item, update_new_quiz_item, delete_new_quiz_item |
| New Quiz Accommodations | list_student_new_quiz_accommodations, set_student_new_quiz_accommodation |
| Files | list_files, list_folders, get_file, upload_file, download_file, delete_file, find_duplicate_files |
| Gradebook History | list_gradebook_history_days, get_gradebook_history_day, list_gradebook_history_submissions, get_gradebook_history_feed |
| Grade Explanation | explain_grade |
| Grading Policy | explain_grading_policy |
| Grade Projection | project_grade |
| Grading Standards | list_grading_standards, create_grading_standard, apply_grading_standard_to_course |
| Users | list_students, get_user, get_profile, search_users, list_course_users |
| Groups | list_groups, list_group_members |
| Enrollments | list_enrollments, list_course_enrollments, enroll_user, remove_enrollment |
| Discussions | list_discussions, get_discussion, list_announcements, post_discussion_entry, create_discussion, update_discussion, delete_discussion |
| Modules | list_modules, get_module, list_module_items, get_course_structure, view_course_structure, create_module, update_module, create_module_item |
| Pages | list_pages, get_page, create_page, update_page, delete_page |
| Calendar | list_calendar_events, create_calendar_event, update_calendar_event |
| Conversations | list_conversations, get_conversation, get_conversation_unread_count, send_conversation |
| Peer Reviews | list_peer_reviews, get_submission_peer_reviews, create_peer_review, delete_peer_review |
| Accounts | get_account, list_accounts, list_sub_accounts, list_account_courses, list_account_users, get_account_reports, list_account_notifications, view_account_notifications |
| Analytics | search_course_content, get_course_analytics, get_student_analytics, get_course_activity_stream, get_assignment_analytics |
| Outcomes | get_root_outcome_group, list_outcome_groups, list_outcome_group_links, get_outcome_group, list_outcome_group_outcomes, list_outcome_group_subgroups, get_outcome, get_outcome_alignments, get_outcome_results, get_outcome_rollups, get_outcome_contributing_scores, get_outcome_mastery_distribution |
| Content Exports | list_content_exports, get_content_export, create_content_export |
| Course Setup | check_course_setup |
| Link Audit | audit_course_links |
| Accessibility Audit | audit_course_accessibility |
| Appointment Groups | list_appointment_groups, get_appointment_group, create_appointment_group, update_appointment_group, delete_appointment_group, list_appointment_group_users, list_appointment_group_groups, next_appointment |
| Student | get_my_courses, get_my_grades, get_my_submissions, get_my_upcoming_assignments, get_my_submission_feedback |
| Student Search | find_student_across_courses |
| Dashboard | get_dashboard_cards, get_todo_items, get_upcoming_events, get_missing_submissions |
| Attention | list_submission_comments_needing_attention, list_students_needing_attention |
| FERPA (conditional) | resolve_pseudonym — registered only when CANVAS_PSEUDONYMIZE_STUDENTS=true |
117 tools are read-only and 48 tools perform Canvas write operations. When FERPA mode is enabled, resolve_pseudonym is registered as the 166th tool overall (118th read tool).
All write tools require appropriate Canvas permissions. Canvas enforces its own permission model -- the MCP server does not bypass it.
Canvas applies rate limits per-user. When creating many New Quizzes items (e.g., RAG-generated quizzes), call the tools serially rather than in parallel. For >50 items, chunk and pause between batches. If you hit a rate-limit error, wait a few seconds and retry.
| Resource | URI Template | Type |
|---|---|---|
| Course Syllabus | canvas://course/{courseId}/syllabus | text/html |
| Assignment Description | canvas://course/{courseId}/assignment/{assignmentId}/description | text/html |
Some tools return machine-readable structuredContent alongside their text content, validated against an outputSchema the server advertises in tools/list. Migration is per tool, so the two surfaces coexist:
| Client behaviour | Migrated tool | Unmigrated tool |
|---|---|---|
Reads content[0].text | Works, byte-identical to before | Works |
Reads structuredContent | Works | Field is absent |
Validates against outputSchema | Works | No schema advertised |
Three guarantees hold for every migrated tool:
content[0].text carries exactly the bytes it did before migration. structuredContent is added alongside it, never in place of it, so text-only clients and the interactive widgets are unaffected.isError: true with plain text, exactly as before.A tool returning a list wraps it under a single plural key, since MCP requires an output schema to be an object:
Which tools are migrated is recorded per tool as structuredOutput in docs/generated/tool-manifest.json (manifest schema 1.1). Currently: the five pages tools.
view_course_structure is an MCP Apps tool: hosts that support the spec render an interactive tree explorer (collapsible modules, type-filter chips, title search, published/unpublished badges, links open in a new tab); hosts that don't fall back transparently to the same JSON payload that get_course_structure returns. The widget is self-contained — no external scripts, fonts, or network calls — and is shipped inline with the tool definition.
| Tool | UI resource URI | Fallback |
|---|---|---|
view_course_structure | ui://canvas-lms-mcp/course-structure.html | Same JSON payload as get_course_structure |
Host verification (Claude Desktop, ChatGPT, Codex fallback) is performed manually after each release, since it requires real Canvas credentials. A screenshot will be added once the first verified host pass lands.
For local AI clients like Claude Desktop, Cursor, and VS Code. The server communicates over stdin/stdout.
For web-based clients or hosted services. Starts an HTTP server with Streamable HTTP transport.
Endpoints:
POST /mcp -- MCP protocol endpointGET /health -- Health check (returns {"status":"ok"})Requires CANVAS_API_TOKEN and CANVAS_BASE_URL environment variables. See docker-compose.yml.
Use the server factory directly in your own Node.js application:
Or use the Canvas client standalone (no MCP dependency):
| Flag | Env Variable | Default | Description |
|---|---|---|---|
--token | CANVAS_API_TOKEN | (required) | Canvas personal access token |
--base-url | CANVAS_BASE_URL | (required) | Canvas instance URL |
serve | -- | stdio mode | Switch to HTTP mode |
--port | -- | 3001 | HTTP server port |
--allowed-origin | CANVAS_ALLOWED_ORIGIN | http://localhost:3000 | CORS allowed origin |
--role | CANVAS_ROLE | (all tools) | Filter tools by Canvas role: student, teacher, or admin (see Role-based tool filtering) |
--destructive-tools=<mode> | CANVAS_DESTRUCTIVE_TOOLS | allow | allow or block. block unregisters the seven irreversible delete tools (see Destructive tool policy) |
| Variable | Required | Description |
|---|---|---|
CANVAS_API_TOKEN | Yes | Canvas personal access token |
CANVAS_BASE_URL | Yes | Canvas instance URL (e.g., https://school.instructure.com) |
CANVAS_ALLOWED_ORIGIN | No | CORS origin for HTTP mode (default: http://localhost:3000) |
CANVAS_ROLE | No | Filter the tool list by role: student, teacher, or admin (see Role-based tool filtering) |
CANVAS_ENABLE_ASSIGNMENT_SUBMISSION | No | Set to true to register the opt-in assignment submission tools |
CANVAS_PSEUDONYMIZE_STUDENTS | No | Set to true to enable FERPA mode |
CANVAS_PSEUDONYMIZE_REVERSE_LOOKUP | No | Set to true (with CANVAS_PSEUDONYMIZE_STUDENTS=true) to register the resolve_pseudonym audit tool |
CANVAS_PSEUDONYM_DIR | No | Absolute path that overrides the default pseudonym map directory |
CANVAS_PSEUDONYM_AUDIT_LOG | No | Path to an append-only file that mirrors resolve_pseudonym audit lines (stderr is always written) |
CANVAS_PROVENANCE_FENCING | No | On by default. Set to exactly false to disable provenance fencing |
CANVAS_DESTRUCTIVE_TOOLS | No | allow (default) or block. Set to exactly block to unregister the seven irreversible delete tools (see Destructive tool policy) |
Canvas has no undo. This server cannot restore anything it deletes -- every recovery
story for a mistaken delete is something you do outside this tooling, in Canvas or
with your institution's admin. CANVAS_DESTRUCTIVE_TOOLS=block removes the seven
irreversible delete tools from the server entirely, so no amount of model confusion
or prompt injection can reach them.
| Mode | Behaviour |
|---|---|
allow | Default. Every tool is registered -- unchanged from previous releases. |
block | The seven tools below are not registered at all. They are absent from tools/list, and a call naming one is refused by the MCP protocol layer before any Canvas request is made. |
Blocked by block:
| Tool | What is lost |
|---|---|
delete_assignment | The assignment plus its submissions and gradebook column |
delete_new_quiz | The quiz, all its items, and all student results |
delete_new_quiz_item | One question and its responses; re-authoring is manual |
delete_discussion | The whole reply thread, including student-authored posts |
delete_page | Page body and revision history (keyed by URL slug, not a numeric ID) |
delete_file | A file, addressed by a global ID with no course scoping in the call |
delete_appointment_group | Every reservation -- and it emails every signed-up student |
Not blocked: delete_peer_review. It is the only delete this server can itself
undo (create_peer_review recreates the row) and it destroys no authored content.
Notes:
Block, BLOCK, block and an empty value are all errors, not a silent fall-back to allow. A kill switch that fails open on a typo is worse than none.--destructive-tools is present, CANVAS_DESTRUCTIVE_TOOLS is not read or validated at all -- so a host that exports a typo'd value cannot stop you overriding it on the command line. Precedence is last-writer-wins, not strictest-wins: --destructive-tools=allow really does override CANVAS_DESTRUCTIVE_TOOLS=block.confirm is reserved but not implemented. Setting it is a startup error naming it as such, so it can never be mistaken for protection you do not have.CANVAS_ROLE, there is no request header for this in HTTP mode -- a client that could pick the mode could switch the gate off.CANVAS_ROLE hides tools from a listing; block means the handler is never registered.Canvas free text is authored by third parties — including the students an educator is grading — and a read tool returns it into model context with the same standing as the operator's own request. Provenance fencing wraps that text in a marker so the trust boundary is legible to the model:
On by default. A safety default that has to be enabled is off in practice.
What is fenced today (slice 1 — long-form bodies only, short labels like titles are deliberately not fenced):
| Field(s) | Tools |
|---|---|
body, submission_comments[].comment | get_submission, list_submissions, list_submissions_awaiting_grading, get_my_submission_feedback |
message | get_discussion, list_discussions |
last_message, message body | get_conversation, list_conversations |
body, syllabus_body | get_page, list_pages, get_syllabus |
The canvas://course/{id}/syllabus and canvas://course/{id}/assignment/{id}/description resources are fenced too, in a block form on their own lines.
Also:
[[ / ]], which stops fenced text from forging its own closing marker._meta.untrusted_content naming the fields and explaining the marker.destructiveHint: true tool refuses content containing a fence marker, so server annotations are never published into your Canvas course.Turning it off — the switch is byte-exact, because every normalisation step widens the set of strings that accidentally disable a safety feature:
Any other value — including False, FALSE, 0, no, off, empty, or unset — leaves fencing on.
Fencing marks provenance; it does not enforce obedience. It makes third-party text distinguishable from your instructions, which is a precondition for a model treating it as data — not a guarantee that it will.
Two write tools — upload_submission_file and submit_assignment — let a student submit their own work via the MCP server. They are off by default and must be explicitly enabled:
Supported submission types: online_text_entry, online_url, online_upload.
Two-step workflow for file uploads:
upload_submission_file(course_id, assignment_id, name, content_base64, content_type) once per file — returns a CanvasFile with an id.submit_assignment(course_id, assignment_id, submission_type: 'online_upload', file_ids: [...]) with the collected ids.Why off by default: submissions are irreversible (Canvas has no unsubmit API) and may consume a limited attempt. An explicit opt-in makes agentic submission a deliberate, documented choice. The destructiveHint: true annotation on both tools also triggers the MCP host's own confirmation prompt. Before calling, the model shows the user exactly what will be submitted and asks for explicit confirmation.
Role filtering: with CANVAS_ROLE=teacher or admin, these tools are hidden (they act on the token holder's own student enrollment and are meaningless for staff tokens).
Opt-in, server-side mode that replaces student names and contact info in tool output with stable pseudonyms (Student 1, Student 2, …) so structured PII never reaches the LLM. Designed for teacher / staff tokens — students running their own MCP should leave the flag off, otherwise their own data is replaced too.
What it does:
name, short_name, sortable_name, email, login_id, sis_user_id, integration_id, avatar_url, bio, pronouns, and last_login on student users.(canvas-base-url, course_id) and persisted to disk under ${XDG_DATA_HOME:-~/.local/share}/canvas-lms-mcp/pseudonyms (Linux), ~/Library/Application Support/canvas-lms-mcp/pseudonyms (macOS), or %APPDATA%\canvas-lms-mcp\pseudonyms (Windows). Override the location with CANVAS_PSEUDONYM_DIR.Student 7 in March is still Student 7 in October. Dropped students are marked historical; their slot is never reused._meta.pseudonymized: true so the agent can mention it in summaries.What it does NOT do:
canvas-lms-mcp/canvas library import — pseudonymization is a tool-layer concern. Embedders that use the raw Canvas client get raw data.Conversation participants are pseudonymized as Person N from a cross-course pool. If you chat with a colleague, they appear as Person 1 rather than their name — conservative because conversations span courses and we cannot infer their role.
Optional resolve_pseudonym reverse-lookup tool: register it only by also setting CANVAS_PSEUDONYMIZE_REVERSE_LOOKUP=true. Every call is audit-logged to stderr (and to CANVAS_PSEUDONYM_AUDIT_LOG if set). When the flag is off the tool is absent from tools/list — a prompt-injection attempt to call it fails at the protocol layer.
Threat model and design rationale in docs/superpowers/specs/2026-05-25-ferpa-pseudonymization.md.
Optionally narrow the tool list to a single Canvas role so an agent sees only the tools relevant to its user. This is a client-side UX / context-reduction filter only — Canvas still enforces real permissions server-side. Setting CANVAS_ROLE=admin does not grant admin powers; a 403 still comes from Canvas if the token lacks the scope.
Three roles, plus the default of "unset = every tool":
CANVAS_ROLE | Tools exposed | Typical use |
|---|---|---|
| (unset) | all (~165) | default; backwards-compatible |
student | ~58 | a student's own courses, grades, submissions, and read-only course content |
teacher | ~145 | grading, roster, content authoring, analytics |
admin | ~157 | everything teacher sees plus account-level tools (enroll_user, list_account_users, …) |
Notes:
CANVAS_ROLE in vishalsachdev/canvas-mcp — set the same value to migrate.all is accepted as an explicit "no filter". An unrecognised value logs a warning to stderr and registers all tools (a config typo never stops the server).teacher / admin do not see the student-only get_my_* tools in v1 — they should use list_submissions / get_submission etc. instead.resolve_pseudonym tool is teacher/admin-only and is never exposed to student, even when reverse lookup is enabled.X-Canvas-Role header, falling back to CANVAS_ROLE from the server config. A valid header (or all) overrides the configured default; an invalid header is ignored with a warning.tests/tools/audience-coverage.test.ts).Design rationale in BRU-1530 (role taxonomy, why three roles, auto-detect deferred to v2).
The pnpm.overrides.hono entry pins hono to ^4.12.27 as a
belt-and-suspenders guard. @modelcontextprotocol/sdk@1.30.0 pulls in
@hono/node-server@2.0.11, which already declares
peerDependencies: { hono: "^4.12.27" } — a floor above the
vulnerability threshold (4.12.14). The override is therefore redundant
but harmless and can be removed once you have confirmed your resolved
hono version is ≥ 4.12.27.
See CONTRIBUTING.md for the full contribution and validation workflow.
git checkout -b feat/my-feature)feat:, fix:, chore:, test:, docs:)pnpm lint && pnpm typecheck && pnpm test passcanvas-lms-mcp runs entirely on your own machine. The maintainers operate no
servers and collect no telemetry or analytics — your Canvas API token and all
Canvas data stay local and travel only between your machine and your own Canvas
instance. Optional FERPA pseudonymization runs locally; the only data written to
disk is the optional pseudonym map and audit log. Full details — data
collection, usage, storage, third-party sharing, retention, and contact — are in
PRIVACY.md.
MIT