canvas-mcp

canvas-mcp

A teacher-facing MCP server that wraps the Canvas LMS REST API, enabling AI assistants to create and manage course content across multiple Canvas courses.

Category
Visit Server

README

canvas-mcp

A teacher-facing Model Context Protocol (MCP) server that wraps the Canvas LMS REST API. Designed for an instructor who wants to use an AI assistant (e.g., Claude) to create and manage course content across multiple Canvas courses.

Security & Privacy: When installed correctly (following the step-by-step setup), this MCP server automatically blinds Personally Identifiable Information (PII) from the AI assistant and provides robust security for your data in compliance with FERPA and institutional policies (including UC/CSU). See FERPA.md and SECURITY_ARCHITECTURE.md for details.

Note: The Canvas API integration is general-purpose. Module templates are user-editable JSON/Handlebars files stored in ~/.config/mcp/canvas-mcp/templates/ — defaults are seeded on first run and can be customized or replaced for any course structure.

Table of Contents

What it does

Connect this server to an AI assistant (like Claude Desktop) and ask it to:

  • Switch between your courses ("switch to my algorithms course")
  • List your courses and see which one is active
  • List modules, get grade summaries, report on missing and late assignments
  • Create assignments, quizzes, pages, discussions, announcements, and files
  • Create and associate rubrics with assignments
  • Scaffold a full week's module from a template in one call
  • Reset a sandbox course with a confirmation gate
  • Find any item by name across pages, assignments, quizzes, modules, discussions, and announcements
  • Search course content semantically using Canvas Smart Search (beta)
  • Import attendance from Zoom participant CSVs with fuzzy name matching

Requirements

Step-by-step setup (first time)

These instructions assume no prior developer experience. Follow each step in order.

1. Install Node.js

Go to nodejs.org and download the LTS version (the left-hand button). Run the installer and accept all defaults. To confirm it worked, open Terminal (macOS: press ⌘+Space, type "Terminal") and run:

node --version

You should see something like v22.0.0. Any version 20 or higher is fine.

2. Install Git

On macOS, Git is often already installed. Run git --version in Terminal to check. If you see a version number, skip to step 3. If not, download Git from git-scm.com and install it.

3. Download the project

In Terminal, run:

git clone https://github.com/you/canvas-mcp
cd canvas-mcp

This creates a folder called canvas-mcp in your home directory and places you inside it.

4. Install dependencies and build

Still in Terminal, inside the canvas-mcp folder:

npm install
npm run build

npm install downloads the required packages (~1 minute, requires internet). npm run build compiles the TypeScript source to runnable JavaScript in packages/teacher/dist/. You should see no errors.

5. Get your Canvas API token

  1. Log in to your institution's Canvas (e.g., https://yourschool.instructure.com)
  2. Click your profile picture → AccountSettings
  3. Scroll down to Approved Integrations and click New Access Token
  4. Give it a name (e.g., "Claude MCP") and set an expiration date
  5. Click Generate Token and copy the token — you won't see it again

6. Create the configuration file

Create the directory and file the server reads on startup. In Terminal:

macOS/Linux:

mkdir -p ~/.config/mcp/canvas-mcp

Then open a text editor and create the file ~/.config/mcp/canvas-mcp/config.json with this content (substitute your real values):

{
  "canvas": {
    "instanceUrl": "https://yourschool.instructure.com",
    "apiToken": "YOUR_CANVAS_API_TOKEN_HERE"
  },
  "program": {
    "activeCourseId": null,
    "courseCodes": ["ENG101", "ENG102"],
    "courseCache": {}
  },
  "defaults": {
    "assignmentGroup": "Assignments",
    "submissionType": "online_url",
    "pointsPossible": 100
  },
  "attendance": {
    "hostName": "Your Name",
    "defaultPoints": 10,
    "defaultMinDuration": 0
  }
}

Replace yourschool.instructure.com with your school's Canvas domain and YOUR_CANVAS_API_TOKEN_HERE with the token from step 5. Update courseCodes to your actual course codes (or leave the array empty to see all your courses).

attendance.hostName — your Zoom display name (without (Host) suffix). Rows matching this name are filtered out of Zoom CSVs before matching. Case-insensitive.

attendance.defaultPoints — default points to award for attendance (default: 10).

attendance.defaultMinDuration — minimum minutes to count as present (default: 0, meaning no filter).

canvas.instanceUrl and canvas.apiToken are required — the server exits immediately with a clear error if either is missing.

program.courseCodes filters list_courses to show only matching courses (e.g., "ENG101" matches "ENG101-003"). Leave it empty ([]) to see all teacher-enrolled courses.

program.activeCourseId and program.courseCache are managed automatically by set_active_course — do not hand-edit them.

7. Connect to your AI Assistant

The server works with any MCP-compatible AI client. Choose your preferred assistant for setup instructions:

8. Start using it

In your assistant's chat window, try:

"List my Canvas courses"

The assistant will call list_courses and show your courses. Then set the active course:

"Switch to my ENG101 course"


Configuration reference

The server reads ~/.config/mcp/canvas-mcp/config.json on startup by default. The setup instructions in step 6 above use this default path.

Using a custom config location

If you need to store the config file somewhere other than the default — for example, to maintain separate configs for different schools or environments — you can point the server at any path using the --config flag in the server args:

"args": ["--secure-heap=65536", "/path/to/canvas-mcp/packages/teacher/dist/index.js", "--config", "/your/custom/path/config.json"]

If you use a custom location, replace ~/.config/mcp/canvas-mcp with your chosen directory path everywhere it appears in the setup instructions — including the mkdir command in step 6 and the config file path you create there.

Platform-Specific Setup

The ~/.config/mcp/canvas-mcp/config.json file created in step 6 is shared across all clients — you only configure it once.

NOTE: Gemini-CLI is the preferred client at this point because it is one of the only clients out there which allows for seamless PII blinding to the LLM and unblinding to the console window. If there are other clients that support this feature, I am not aware of them.

<details> <summary><a id="gemini-cli-googles-gemini-cli"></a>Gemini CLI (Google's <code>gemini</code> CLI)</summary>

Edit ~/.gemini/settings.json (create it if it doesn't exist):

{
  "mcpServers": {
    "canvas-mcp": {
      "command": "node",
      "args": ["--secure-heap=65536", "/path/to/canvas-mcp/packages/teacher/dist/index.js"]
    }
  }
}

If you have other servers already configured, add the "canvas-mcp" entry inside the existing "mcpServers" object. Restart Gemini CLI after saving.

To limit the server to a single project instead of all sessions, place the same JSON in .gemini/settings.json inside that project's folder.

Hooks (PII blinding): To enable automatic student-name blinding in Gemini CLI — so real names never reach the model — set up the canvas-mcp hooks. See clients/gemini/docs/SETUP.md for step-by-step instructions. Note: A local patch to Gemini CLI is currently required for the hooks to work correctly with student-specific queries — the setup guide covers this. </details>

<details> <summary><a id="claude-desktop"></a>Claude Desktop</summary>

  1. Download and install Claude Desktop if you haven't already
  2. Open Finder (macOS) and press ⌘+Shift+G, then paste: ~/Library/Application Support/Claude/
  3. Open (or create) the file claude_desktop_config.json in a text editor
  4. Add the following, replacing /path/to/canvas-mcp with the actual path to the folder you cloned in step 3 (e.g., /Users/yourname/canvas-mcp):
{
  "mcpServers": {
    "canvas-mcp": {
      "command": "node",
      "args": ["--secure-heap=65536", "/path/to/canvas-mcp/packages/teacher/dist/index.js"]
    }
  }
}
  1. Save the file and restart Claude Desktop (quit completely and reopen)
  2. Look for a hammer icon (🔨) in the Claude Desktop chat window — this confirms MCP tools are connected. Click it to see the tool list.

If Claude Desktop was already open with other MCP servers configured, merge the "canvas-mcp" entry into your existing "mcpServers" object rather than replacing it. </details>

<details> <summary><a id="claude-code-the-claude-cli"></a>Claude Code (Anthropic's <code>claude</code> CLI)</summary>

Add the server to your user-level MCP config with a single command:

claude mcp add canvas-mcp -- node --secure-heap=65536 /path/to/canvas-mcp/packages/teacher/dist/index.js

Replace /path/to/canvas-mcp with the actual folder path from step 3. This writes to ~/.claude.json (user scope) and makes the server available in all Claude Code sessions.

To add it to a specific project only (so it isn't available globally), run the command from inside that project's folder and add --scope project:

claude mcp add --scope project canvas-mcp -- node --secure-heap=65536 /path/to/canvas-mcp/packages/teacher/dist/index.js

Verify the server was added:

claude mcp list

</details>

<details> <summary><a id="codex-cli-openais-codex-cli"></a>Codex CLI (OpenAI's <code>codex</code> CLI)</summary>

Edit ~/.codex/config.toml (create it if it doesn't exist):

[mcp_servers.canvas-mcp]
command = "node"
args = ["--secure-heap=65536", "/path/to/canvas-mcp/packages/teacher/dist/index.js"]

To limit it to a single project, place the same TOML in .codex/config.toml inside that project's folder (the project must be trusted). </details>

Tools

19 tools total. All tools accept an optional course_id to override the active course.

Course context

Tool Description
list_courses List your Canvas courses. Filters to program.courseCodes by default; pass all: true to see everything.
set_active_course Set the active course by fuzzy-matching a query string (e.g. "ENG101", "english spring").
get_active_course Returns the currently active course from local config. No Canvas API call.

Reporting

Student names and Canvas IDs in reporting tool responses are automatically replaced with session tokens ([STUDENT_001], [STUDENT_002], …) before they reach the AI. See Privacy below.

Tool Description
get_module_summary Full structure of a module: item types, titles, points, due dates. Accepts module_id or module_name (partial match).
get_grades Grade data scoped by scope: "class" (every student's score + missing/late counts, supports sort_by), "assignment" (per-student breakdown for one assignment), or "student" (all assignments for one student via student_token). Student names replaced with session tokens.
get_submission_status Missing or late assignments by student. type: "missing" supports an optional since_date filter. Student names replaced with session tokens.
student_pii PII lookup. action: "resolve" reveals the real name and Canvas ID for a session token (shown to you only). action: "list" returns all tokens registered in the current session.

Create & list

Tool Description
create_item Create a course item. type: page, assignment, quiz, discussion, announcement, module, or module_item. Supports optional template_name/template_data for template-rendered pages. Pass dry_run: true to preview the resolved inputs without calling Canvas.
list_items List course items by type: modules, assignments, quizzes, pages, discussions, announcements, rubrics, assignment_groups, module_items (requires module_name), or templates (lists available module templates).

Find, update & delete

Tool Description
find_item Find any course item by partial name and return its full details. Types: page (with body), assignment (with description), quiz (with questions), module, module_item, discussion, announcement, syllabus. Returns first case-insensitive partial match with a warning if multiple items matched.
update_item Find a course item by name then update it. Types: page, assignment, quiz, module, module_item, syllabus. Provide only the fields to change.
delete_item Find a course item by name then delete or remove it. Types: page, assignment, quiz, module, module_item, discussion, announcement. Deleting a module_item only removes it from the module — underlying content is not deleted.

Files & rubrics

Tool Description
upload_file Upload a local file to the course Files section via Canvas's 3-step upload protocol.
delete_file Permanently delete a file. Irreversible — no recycle bin via API.
create_rubric Create a rubric and associate it with an assignment. See rubric notes below.

Module creation (high-level)

Tool Description
build_module Build a module by mode: "blueprint" (render a named template from ~/.config/mcp/canvas-mcp/templates/), "manual" (explicit item list), "solution" (solution module linked to a lesson module), or "clone" (copy a module from any course with optional week number substitution).

Destructive operations

Tool Description
reset_course Preview or execute a full course content reset.

NOTES

dry_run: Defaults to dry_run=false. You will be provided with a token that expires after 5 minuets. You must provide the token to the LLM to confirm reset. If dry_run=true, no confirmation is required; returns counts of what would be deleted.

Alternative confirmation: The user may instead provide confirmation_text exactly matching the Canvas course name (case-sensitive), skipping the token flow entirely.

Always preserved: Enrollments, course settings, and navigation tabs are never touched.

Attendance

Tool Description
import_attendance Import attendance from a Zoom participant CSV. Two-step workflow: action="parse" reads CSV, matches Zoom names to Canvas roster via a 4-step pipeline (persistent map → exact → fuzzy with part-to-part Levenshtein → unmatched), and returns tokenized results for review. action="submit" posts grades for matched students. Supports min_duration filtering, dry_run preview, and a persistent zoom-name-map.json for remembered name mappings. Results are FERPA-blinded.

Smart search (Canvas beta feature)

Tool Description
search_course Search course content using Canvas Smart Search (AI-powered semantic search). Returns results with distance scores — lower = closer match. Supports content type filtering, distance threshold, result limit, and optional body inclusion. Pass save_threshold: true to persist the threshold to config as the new default. Requires Canvas Smart Search beta to be enabled on your instance.

Privacy / FERPA

Student names and Canvas numeric IDs are FERPA-protected PII. When this server is used with cloud-hosted AI assistants (like Claude Desktop), every tool response passes through the assistant's infrastructure. To prevent student data from leaving your machine, all reporting tools automatically replace student identity information with opaque session tokens before the response reaches the AI:

  • [STUDENT_001], [STUDENT_002], … are assigned in the order students are first seen, and reset on every server restart.
  • The AI reasons about tokens only — it never sees real names or Canvas IDs.
  • A human-readable lookup table ([STUDENT_001] → Jane Smith) is shown to you in the assistant's UI alongside the AI's response, so you always know who's who without needing to ask.
  • To explicitly look up a token, call student_pii(action='resolve', student_token='[STUDENT_001]') — the result is shown to you only, not added to the AI's context.
  • Blinding is always on and cannot be disabled.

The session key used to protect the in-memory token map is:

  • Freshly generated at startup (never stored to disk)
  • Pinned in RAM via mlock where the OS permits (prevents swap-file exposure)
  • Zeroed on process exit (SIGINT, SIGTERM, SIGHUP)

The --secure-heap=65536 flag in the AI assistant config allocates a locked memory region for cryptographic operation intermediates. Include it in your config as shown in the Platform-Specific Setup.

For full details on the threat model and compliance posture, see docs/FERPA.md, docs/SECURITY_ARCHITECTURE.md, and docs/PII_ARCHITECTURE.md.


Canvas API notes

  • Pagination is handled automatically — all list operations follow Link: rel="next" headers.
  • Rate limiting — the client watches X-Rate-Limit-Remaining and adds a 500ms delay when it drops below 10.
  • Retries — HTTP 429 responses trigger exponential backoff, up to 3 attempts.
  • Classic Quizzes only — New Quizzes (Canvas Quiz Engine) has a different API and is out of scope.
  • Quiz creation returns 200, not 201 — a known Canvas API quirk the client handles correctly.

Rubrics require an assignment (Canvas limitation)

Canvas does not support standalone rubrics. A rubric must be associated with at least one assignment, or it enters a broken "zombie" state: it appears in the course rubric list but returns 404 on individual GET and 500 on DELETE.

To prevent this, create_rubric always requires an assignment_id and creates the rubric and its association in a single API call. You cannot create a rubric without linking it to an assignment.

During reset_course, the rubric cleanup step (step 8) handles any pre-existing zombie rubrics (e.g., created manually via the Canvas UI) by creating a temporary assignment, associating the zombie rubric with it, deleting the rubric (now deletable), and then deleting the temp assignment. Rubrics that cannot be recovered are reported in the response under rubrics_failed.

Other notes:

  • The front page is automatically unset before page deletion.
  • File deletion is irreversible — the preview shows file counts explicitly.

Development

Running tests

# Unit tests (no credentials required)
npm run test:unit

# Integration tests (requires .env.test — see below)
npm run test:integration

Unit tests mock all HTTP with msw. Integration tests run against a real Canvas instance.

Setting up integration tests

Create .env.test in the project root:

# Core Canvas configuration
CANVAS_INSTANCE_URL=https://canvas.instructure.com
CANVAS_API_TOKEN=your_test_teacher_token
CANVAS_TEST_COURSE_ID=12345

# Student tokens for the seed script (requires 5 students)
STUDENT0_API_TOKEN=token_0
STUDENT1_API_TOKEN=token_1
STUDENT2_API_TOKEN=token_2
STUDENT3_API_TOKEN=token_3
STUDENT4_API_TOKEN=token_4

Use a free canvas.instructure.com account with a course named TEST SANDBOX — keep it completely separate from your production courses.

  1. Create the .env.test file with the core and student tokens.
  2. Run the seed script to establish the test state: npm run seed
  3. The script automatically populates the remaining IDs (Module, Assignments, etc.) into your .env.test file.
  4. Run the integration tests: npm run test:integration

Token overhead analysis

npm run count-tokens                       # exact (requires ANTHROPIC_API_KEY)
npm run count-tokens -- --no-api           # estimate without API key
npm run count-tokens -- --dump             # also writes raw payloads to tmp/token-dump/

Build

npm run build   # compiles packages/core and packages/teacher → their dist/ folders

Project structure

clients/
└── gemini/                       # Gemini CLI hooks for PII blinding (see clients/gemini/docs/SETUP.md)
    ├── src/
    │   ├── before_model.ts       # BeforeModel hook — 3-phase fuzzy name matching (case-insensitive, partial, Levenshtein)
    │   ├── after_model.ts        # AfterModel hook — unblinds tokens in model response
    │   └── after_tool.ts         # AfterTool hook — progress indicator for canvas-mcp tool calls
    ├── docs/
    │   ├── SETUP.md              # Step-by-step hook installation guide
    │   ├── HOOK_REFERENCE.md     # Hook API reference & behavioral findings
    │   ├── FUZZY_MATCHING_REQUIREMENTS.md  # 3-phase fuzzy matching specification
    │   └── TESTING.md            # Hook testing strategy
    └── patches/
        └── gemini-cli-hookTranslator.patch.md  # Required local patch for Gemini CLI (see SETUP.md Step 2b)
packages/
├── core/                         # @canvas-mcp/core — shared Canvas API layer
│   ├── src/
│   │   ├── canvas/
│   │   │   ├── client.ts         # HTTP client (auth, pagination, rate limiting, retry)
│   │   │   ├── courses.ts        # Course & enrollment API calls
│   │   │   ├── modules.ts        # Module & module item API calls
│   │   │   ├── assignments.ts    # Assignment & assignment-group API calls
│   │   │   ├── quizzes.ts        # Classic Quiz API calls
│   │   │   ├── pages.ts          # Page API calls (CRUD + front page handling)
│   │   │   ├── discussions.ts    # Discussion topic & announcement API calls
│   │   │   ├── files.ts          # File upload (3-step Canvas/S3 flow) & delete
│   │   │   ├── rubrics.ts        # Rubric CRUD + association API calls
│   │   │   ├── submissions.ts    # Grade & submission API calls
│   │   │   └── search.ts         # Canvas Smart Search API
│   │   ├── config/
│   │   │   ├── schema.ts         # Config types and DEFAULT_CONFIG
│   │   │   └── manager.ts        # Read/write ~/.config/mcp/canvas-mcp/config.json
│   │   ├── security/
│   │   │   ├── secure-store.ts   # AES-256-GCM in-memory PII store (session tokens, mlock)
│   │   │   └── sidecar-manager.ts# Writes/deletes the PII sidecar file for Gemini CLI hooks
│   │   ├── attendance/
│   │   │   ├── zoom-csv-parser.ts# Parse Zoom participant CSV (column filtering, pronoun/host stripping)
│   │   │   ├── name-matcher.ts   # 4-step name matching pipeline (map → exact → fuzzy → unmatched)
│   │   │   ├── zoom-name-map.ts  # Persistent JSON map from Zoom names to Canvas user IDs
│   │   │   ├── review-file.ts    # Writes ambiguous/unmatched entries for human review
│   │   │   └── types.ts          # ZoomParticipant, RosterEntry, MatchResult, ReviewEntry
│   │   ├── matching/
│   │   │   └── levenshtein.ts    # Levenshtein edit distance (shared by attendance + Gemini hooks)
│   │   ├── templates/
│   │   │   ├── index.ts          # Re-exports from service.ts
│   │   │   ├── service.ts        # TemplateService — manifest parsing, Handlebars rendering
│   │   │   ├── seed.ts           # Seeds default templates to user config dir on first run
│   │   │   └── defaults/         # Bundled default templates (later-standard, later-review, etc.)
│   │   └── tools/
│   │       └── context.ts        # list_courses, set_active_course, get_active_course
│   └── tests/
│       ├── fixtures/
│       │   └── zoom-report-sample.csv  # Real Zoom export for parser tests
│       ├── setup/
│       │   └── msw-server.ts     # Shared MSW server setup for core unit tests
│       └── unit/
│           ├── attendance/       # name-matcher, zoom-csv-parser, zoom-name-map, review-file tests
│           ├── canvas/           # submissions tests
│           ├── config/           # schema, manager tests
│           ├── matching/         # levenshtein tests
│           ├── templates/        # service tests
│           └── tools/
│               └── context.test.ts
└── teacher/                      # @canvas-mcp/teacher — MCP server entry point
    ├── src/
    │   ├── index.ts              # MCP server wiring and startup
    │   └── tools/
    │       ├── attendance.ts     # import_attendance (parse + submit, per-server WeakMap state)
    │       ├── content.ts        # upload_file, create_rubric, delete_file
    │       ├── modules.ts        # build_module (blueprint / manual / solution / clone)
    │       ├── reporting.ts      # get_module_summary, get_grades, get_submission_status, student_pii
    │       ├── reset.ts          # reset_course (dry_run + confirmation gate)
    │       └── find.ts           # create_item, list_items, find_item, update_item, delete_item, search_course
    └── tests/
        ├── setup/
        │   ├── msw-server.ts     # Shared MSW server setup for unit tests
        │   └── integration-env.ts# Integration test environment loader
        ├── unit/tools/
        │   ├── attendance.test.ts
        │   ├── content.test.ts
        │   ├── find.test.ts
        │   ├── modules.test.ts
        │   ├── reporting.test.ts
        │   └── reset.test.ts
        └── integration/          # Real Canvas API tests, requires .env.test

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured