Skip to main content
Glama

CanvasPilot

Agentic Canvas LMS tools for LLMs — an MCP server + CLI that talks to Canvas's real REST API using the session your browser already has.

Most Canvas MCPs need a Personal Access Token. Many schools disable student PATs and hide Canvas behind SSO/MFA. CanvasPilot solves that with a stay-open session broker: you log in once in a real browser (Playwright, persistent profile), then every tool call rides that authenticated session — headless, for as long as the cookies live.

Same architecture as OpenCLI-style web agents (persistent browser session → site's real HTTP/JSON API), purpose-built for Canvas.

Features

  • Auth designed for real schools — session broker (SSO/MFA, no PAT needed) or classic CANVAS_API_TOKEN

  • Headless after one headed login — session start --headless reuses the saved profile

  • Full REST surface — courses, assignments, modules, pages, files, discussions, announcements, planner, inbox, calendar, activity stream, submissions, and quizzes (list/questions/submissions/start/complete)

  • Agent-shaped digests — assignment_brief (cleaned prompt + rubric), sync_summary (courses + upcoming), submission_status

  • Submission feedback — self submission comments and rubric assessments alongside current-attempt and grading metadata

  • Submission history — inspect returned attempts and submitted text/file metadata without assigning current grades or comments to earlier versions (guide)

  • Submission comparison — read two explicitly selected returned records side by side in a new offline HTML report, with exact source JSON (guide)

  • Course grade review — reported totals, assignment-group rules and each returned assignment's own grade/status, with hidden and unknown values kept explicit

  • Fixture mode — offline dict backend for tests and CI; no Canvas required

  • 40 MCP tools, one stdio server, one env var for the host (CANVAS_BASE_URL) plus either a PAT or a running session broker

  • Course folder browsing — select nested folders and inspect bounded file-metadata pages through CLI, MCP or Python

  • Module progress checklist — review reported completion, remaining requirements and module locks through CLI, MCP or Python

Related MCP server: Canvas MCP

Install

Not published to PyPI yet — install from a checkout:

git clone https://github.com/Jacob-Met/canvaspilot.git
cd canvaspilot
pip install -e ".[browser]"
playwright install chromium

Requires Python 3.11+.

Try it offline first

From an installed package or an editable source checkout:

python -m canvaspilot.offline_demo
# Optional: save to a NEW path (existing files are protected)
python -m canvaspilot.offline_demo --out synthetic-review.json

This exercises the high-level course and assignment-brief API on two explicitly synthetic courses. It strips prompt markup, preserves a missing due date as unknown, and returns readable JSON. No Canvas account, browser, token, session broker, model call, submission, or runtime network connection is needed. The example rejects writes and missing fixture routes instead of falling back to live access. Installing package dependencies requires network access separately.

This is a small, inspectable behavior demonstration, not evidence of live pagination, working school authentication, or student outcomes. The tests in tests/test_offline_demo.py protect these boundaries.

Quick start

export CANVAS_BASE_URL=https://<school>.instructure.com

# 1) Log in once (headed — finish SSO/MFA in the window), leave the broker running
canvaspilot session start

# 2) Later: restart headless on the same saved profile
canvaspilot session start --headless

# 3) Use it
canvaspilot whoami
canvaspilot courses
canvaspilot sync
canvaspilot brief <course_id> <assignment_id>
canvaspilot feedback <course_id> <assignment_id>
canvaspilot submission-history <course_id> <assignment_id>
canvaspilot grade-review <course_id>

canvaspilot whoami writes its existing mode, base_url and native profile values to stdout on a successful read and exits 0. Expected authentication, HTTP or malformed-JSON read failures exit 1 with one JSON error on stderr and no identity result. See identity read error handling for diagnostic fields, text/null responses and the existing setup guidance.

On a successful read, canvaspilot courses writes the complete active-course list to stdout and exits 0. Expected authentication, HTTP or pagination failures exit 1 with one JSON error on stderr and no partial course list. See course-list error handling for diagnostic fields and the existing setup guidance.

A completed canvaspilot assignments <course_id> read writes the selected course's compact assignment list to stdout and exits 0. The default bucket is upcoming; --bucket= removes that filter. Expected authentication, HTTP or pagination failures exit 1 with one JSON error on stderr and no partial list. See assignment-list error handling for filter, submission and diagnostic details.

canvaspilot sync orders upcoming assignments by deadline across the selected courses. It includes up to 10 courses and 5 assignments per course by default, and reports omitted rows, unknown dates, and per-course read failures. Adjust the overview with --limit-courses and --limit-assignments-per-course; see the sync guide for CLI, Python, and MCP examples and count meanings.

Assignment lists and sync rows also include the current user's reported submission state, attempt, and late/missing/excused flags when Canvas supplies them. Missing data remains unknown; the existing has_submitted_submissions flag describes submissions by any student. See the assignment submission guide for field meanings, selection examples and malformed-data warnings.

Read selected courses' calendar events and assignment deadlines together with canvaspilot agenda 42 77 --start 2026-10-08 --end 2026-10-15. The course agenda guide explains timed, all-day and unavailable timing, source identity and read limits. The same report is available through canvas_course_agenda in MCP.

Read your own planner items with canvaspilot planner --start-date 2026-10-08 --end-date 2026-10-15. The planner CLI guide explains native date filters, personal notes, supplied completion metadata and complete-list error behavior. The command uses the existing planner API and does not change any planner state.

Save that same selection as an offline view with course/source-date/search controls and a printable current view: canvaspilot export-agenda 42 77 --start 2026-10-08 --end 2026-10-15 --out agenda.html. The original native JSON remains available byte-identically. Review and print a saved agenda.

Read your reported enrollment roles, states and grades with canvaspilot enrollments. The enrollments CLI guide explains the default active filter, native state selection and the complete returned JSON collection.

To take selected course deadlines into a calendar application, save an explicit local snapshot with canvaspilot export-calendar 42 77 --out deadlines.ics. The calendar export guide explains selection, undated omissions, snapshot limits and safe re-import expectations.

To take explicitly selected course pages offline, use canvaspilot export-pages 42 course-introduction week-two-reading --out reading.html. The page export guide explains numeric page IDs, the inert original-source record, selection limits and protection of existing output. With a PAT instead:

export CANVAS_API_TOKEN=...   # broker not needed
canvaspilot courses

To inspect Canvas-reported course totals and assignment grades together, run canvaspilot grade-review 42 or call the read-only MCP canvas_grade_review tool. The grade review guide explains the original Canvas field names, group/drop-rule context, visibility rules and returned-row limits. No replacement course grade or missing-work status is inferred.

Save that same grade review for offline reading or printing with canvaspilot export-grade-review 42 --out course-grades.html. The saved grade-review guide explains reported totals, assignment context, withheld/unknown values, the complete JSON download and new-file protection.

Save submission attempts for offline review

Save the existing self-history report as a readable standalone HTML file:

canvaspilot export-submission-history 71 902 --out submission-history.html

The report keeps the current submission, every returned historical record, and top-level comments separate. Returned order and missing/empty distinctions stay visible; current grades are never assigned to earlier attempts. It includes the complete normalized JSON and works with the browser's Print command. Existing files are protected. See offline submission history for scope, provenance, limits and CLI/Python usage.

Assignment briefs

canvaspilot brief <course_id> <assignment_id>, the Python CanvasAPI.assignment_brief() method, and the MCP canvas_assignment_brief tool return the same JSON brief: the cleaned prompt, due date, assignment points, submission types, Canvas link, and the rubric supplied with the assignment.

  • rubric preserves the supplied criterion and rating order, IDs, descriptions, long descriptions, points (including zero), range/scoring flags, and outcome identifiers. Rubric text is not rewritten. Only these canonical rubric fields are projected; get_assignment() retains the complete original rubric payload.

  • rubric_settings preserves the supplied settings object, including any display flags such as hide_points, hide_score_total, and free-form criterion comments. The CLI and MCP return data, without rendering a Canvas rubric UI. Consumers should honor the distinct display flags when rendering it.

  • use_rubric_for_grading preserves Canvas's boolean: false means the rubric is advisory; null means the assignment response did not supply a usable value.

  • rubric_warnings is normally empty. Malformed optional containers, entries, or canonical field values are omitted locally with a warning that names their location, while the ordinary brief and usable neighboring entries remain.

A missing or null rubric remains null; a supplied empty list remains []. Missing criterion fields are not filled in, and missing ratings remain unknown. No criteria, score totals, or grading requirements are inferred. Assignment points_possible and rubric settings' points_possible remain separate supplied values. This uses the existing assignment GET request, without another rubric request or a change to authentication.

The field meanings follow Canvas's Assignments API and Rubrics API. The public-path tests in tests/test_assignment_brief_rubric.py use a clearly synthetic assignment and forbid HTTP and broker access.

To read selected briefs alongside your own notes, use canvaspilot export-study 42 1001 1008 --out study.html. The self-contained workspace supports local review checks, downloaded working copies, and printing. See the assignment study workspace guide for the selection, source, and note-saving boundaries.

MCP server

canvaspilot mcp          # stdio

Cursor / Claude Desktop / any MCP client:

{
  "mcpServers": {
    "canvas": {
      "command": "canvaspilot",
      "args": ["mcp"],
      "env": { "CANVAS_BASE_URL": "https://<school>.instructure.com" }
    }
  }
}

Tools exposed (all prefixed canvas_):

Area

Tools

Identity

whoami, sync_summary, planner_items, activity_stream, list_todo_items, list_enrollments

Courses

list_courses, get_course, grade_review, list_modules, module_progress, list_pages, get_page, list_files, browse_files, list_announcements

Assignments

list_assignments, get_assignment, assignment_brief, submission_status, submission_feedback, submission_history, submit_assignment_text

Discussions

list_discussion_topics, get_discussion, discussion_thread, post_discussion_reply

Quizzes

list_quizzes, get_quiz, list_quiz_questions, list_quiz_submissions, start_quiz_submission, complete_quiz_submission

Inbox / Calendar

list_conversations, get_conversation, reply_conversation, list_calendar_events, course_agenda

Full REST

canvas_api_request, canvas_api_paginated — any /api/v1/... path, including paginated reads through a current session broker (see Auth modes for collection requirements)

Programmatic API bundle: from canvaspilot.bundle import make_api, tool_inventory.

Read classic quizzes from the terminal

List a course’s classic quizzes, then inspect one returned quiz ID:

canvaspilot quizzes 42
canvaspilot quiz 42 9

Both commands accept the existing --base-url, --profile and --token options. Course and quiz IDs must be positive numeric Canvas IDs. quizzes uses the existing paginated reader and compact list fields; quiz returns the original detail JSON, including any supplied timing, attempt-policy, lock and differentiated-date fields. Null dates and false/zero values keep their original meaning. A failed read exits nonzero with a JSON error on stderr and no partial success output.

These commands call only the existing classic-quiz GET endpoints. They do not start or complete an attempt, read questions, or change submissions. New Quizzes and LTI assessments retain the existing separate support boundary.

Submission feedback

Use canvaspilot feedback <course_id> <assignment_id>, the MCP tool canvas_submission_feedback, or CanvasAPI.submission_feedback(course_id, assignment_id) to review your submission comments and rubric evidence together. The report uses the assignment and self submission GET endpoints, requesting submission_comments and rubric_assessment. It uses the selected client's existing authentication and returns only what Canvas exposes to that user.

The JSON report contains:

  • assignment: the assignment's identity, title, URL, due date, and possible points.

  • submission: Canvas's score, grade, attempt, workflow state, grader ID, and submission/grading timestamps. A false grade_matches_current_submission means the student resubmitted since grading; the displayed grade may describe an earlier attempt. A missing flag remains null. Negative grader IDs are retained because Canvas can identify an autograder that way.

  • rubric.criteria: each original criterion beside its assessment, joined only by a unique exact string criterion ID. Criterion descriptions, ratings, ignore_for_scoring, and criterion_use_range remain available. Missing or ambiguous IDs leave the assessment in rubric.unmatched_assessments, keyed by its original ID. Assessment points and comments are preserved as returned.

  • rubric.use_rubric_for_grading and rubric.settings: Canvas's grading/advisory distinction and rubric settings. The report does not sum rubric points or infer a grade. assessment_returned says whether Canvas returned an assessment object, including an empty object; it does not mean the submission has been graded.

  • submission_comments: original comment objects, including authors, timestamps, media comments, and attachments when supplied. Students and peers can also author these comments. Media-only comments remain present.

Zero marks remain zero. Missing/null comments or rubric data remain null, while explicitly returned empty arrays/maps remain empty. A criterion with no matching assessment has assessment: null; omitted assessment points or comments remain omitted. An inaccessible submission or malformed response is reported as an error, so it cannot be mistaken for a submission with no feedback. This operation performs two GETs and does not mark comments read, submit work, or change a grade.

Read a discussion

After choosing a topic with canvaspilot discussions <course_id>, open it with canvaspilot discussion <course_id> <topic_id>. Add --unread-only to keep known unread replies together with their parent context. The same report is available through CanvasAPI.discussion_thread() and canvas_discussion_thread in MCP.

The report keeps original entry fields and the returned reply structure, alongside cleaned text, unambiguous author attribution and reported read markers. Missing facts remain unknown; absent or ambiguous unread metadata does not become a false empty result. Canvas's cached-view boundary and unmatched unread identifiers remain visible. See the discussion reader guide for fields, selection rules and error behavior.

Announcements

Read selected courses from the terminal with canvaspilot announcements 42 77. Choose --detail full for complete cleaned message text and optionally supply --start-date 2026-10-01. The command preserves the existing course context, pagination and date-window rules; see terminal announcement review for fields, defaults and error behavior.

canvas_list_announcements and CanvasAPI.list_announcements() use the existing client paginator in both compact and full detail modes. Course and optional start-date filters are retained, and results keep Canvas's page order and announcement metadata. Compact message text remains limited to 400 characters plus an ellipsis; full detail retains the complete stripped text. A failed page request raises an error instead of returning the announcements collected before the failure. The selected client's existing transport and pagination limits still apply.

Module contents

canvas_list_modules and CanvasAPI.list_modules() retrieve module items even when Canvas omits the optional inline items array. Missing, null, malformed, or shorter inline contents are fetched through the existing paginated module-items route; valid complete inline arrays and modules reporting an integer zero need no extra request. Compact output retains its existing item fields; detail=full preserves Canvas's module metadata and the retrieved item payloads. A failed secondary request or malformed item response is reported to the caller instead of being presented as an empty module.

This follows the Canvas Modules API contract. Requests use the selected client's existing fixture, token, or session broker transport and pagination limits. Listing does not mark module items read or complete.

Review module progress

canvaspilot module-progress 42
canvaspilot module-progress 42 --module-id 7

Review Canvas-declared module states and completed, unfinished or unknown item requirements. The checklist keeps all-versus-one requirements, prerequisites and sequential-progress context. Missing student fields stay unknown; counts cover returned rows. This read-only workflow never marks items read or complete. See the module-progress guide for Python/MCP examples and coverage meanings.

Save that same report for offline reading or printing:

canvaspilot export-module-progress 42 --out module-progress.html
canvaspilot export-module-progress 42 --module-id 7 --out module-7-progress.html

The self-contained HTML keeps the reader's states, all-versus-one rules, thresholds, prerequisites, unknowns and coverage diagnostics visible. It includes the complete normalized report as a JSON download and protects existing output paths. See offline module-progress reports.

Course files

canvaspilot files COURSE_ID, CanvasAPI.list_files(COURSE_ID), and canvas_list_files read the existing paginated course file collection. Authentication, request, and pagination errors propagate without a successful partial result or an automatic change to a root-folder listing. This includes errors on the first page. An empty successful collection remains an empty list; the existing field projection and returned page order are unchanged. Use the explicit folder browser below when you want a selected folder's direct children instead of the course collection.

Browse course folders

canvaspilot browse-files 42
canvaspilot browse-files 42 --folder-id 110 --files-page 2 --per-page 20

Start at the course root, then choose a returned child folder ID. Each result contains the selected folder and one requested page each of direct child folders and files. Only metadata is returned; file bodies and download/preview URLs are excluded. A foreign folder or permission failure returns an error, not an empty listing. has_more is unknown because the existing request interface omits pagination headers; next_page_to_try is an optional probe, not a completeness claim. See folder-browser usage and contract for the MCP/Python interfaces, page limits and examples.

Review your inbox

canvaspilot inbox --scope unread
canvaspilot conversation 901

List your conversations and select a returned ID to inspect its messages. The CLI, Python API and existing MCP conversation reader explicitly preserve unread state. Original message, participant and attachment metadata are returned; linked content is not downloaded. See inbox review for scope choices, pagination and error behavior.

Save course syllabi

Use canvaspilot export-syllabus 42 57 --out syllabi.html to keep explicitly selected syllabus bodies in one readable, printable offline HTML packet. Missing, unavailable and explicitly empty bodies stay distinct; linked files and media remain clearly marked external resources. The command protects existing output paths and refuses the whole file if a requested course cannot be read or validated. Source, limits and usage.

To compare two source-text downloads you have already saved, run this standalone standard-library command from the checkout:

python src/canvaspilot/syllabus_source_compare.py before-source.txt after-source.txt --out new-source-review.txt

Choose a new output path. The saved source comparison guide explains exact source lines and endings, hashes, input limits and output protection. A difference in saved source bytes does not establish a policy change or same-course identity.

Auth modes

Mode

How

When

session (default)

Playwright persistent profile + local broker on 127.0.0.1:18765

School disables student PATs / SSO+MFA

token

CANVAS_API_TOKEN → Authorization: Bearer

School allows PATs

fixture

CanvasClient(fixture={...})

Tests, offline demos

Mode resolution is fixture > token > live broker > session, decided at runtime: CANVAS_API_TOKEN always wins and skips the broker; otherwise a live GET 127.0.0.1:18765/health probe decides whether the session broker is used. Bare "session" mode with no running broker raises on every request — "session (default)" means "broker if it's up", not "works with no setup".

Pagination follows Canvas's response Link headers in token mode and in current session brokers. A session broker advertises "link_pagination": true in GET /health and forwards only the Link response header alongside each fetch result. The client follows an opaque rel=next URL even when a page is shorter than the requested per_page; it stops when no next link remains, without inventing a numeric page or requesting an extra empty page. Initial filters and repeated include[] values apply only to the first request; subsequent URLs retain the server's exact parameters. Ordinary single-request results keep their existing decoded JSON/text shape.

Token collection reads validate both the initial URL and each next URL against the client's configured Canvas scheme, host and effective port before dispatch. Initial absolute URLs retain their authored query and receive the initial filters once. Session continuation stays on the broker's configured Canvas origin and retains the exact absolute continuation URL.

Each applicable Link entry must carry a valid relation. Anchored links are ignored as whole entries because they change the context; registered relation names such as next are matched without regard to case. Quoted parameter values and absolute URI extension relations remain supported. Invalid or ambiguous metadata, repeated continuation URLs, a later-page error or non-list body, and a known continuation beyond the 40-page limit raise an error. A terminal first single-object response keeps its existing convention. CanvasPaginationError is available from canvaspilot.client.

An older running broker without the capability flag produces an actionable incomplete-collection error. Restart it with the current CanvasPilot version and retry the read. Ordinary single requests remain compatible with older brokers. The Canvas pagination contract explains why clients must check Link even when they request a page size.

The broker only listens on loopback — and session_broker.main() now refuses to start on any non-loopback bind address (fail-closed: a future host change cannot silently expose the unauthenticated /fetch//shutdown endpoints to the network). Cookies never leave the Playwright profile directory; the MCP/CLI process never sees them — it asks the broker to make the request.

Configuration

Env

Default

Purpose

CANVAS_BASE_URL

https://canvas.instructure.com

Your school's Canvas host

CANVAS_API_TOKEN

—

PAT (skips the broker)

CANVAS_PROFILE

~/.canvaspilot/profile

Playwright persistent profile dir

CANVAS_SESSION_PORT

18765

Broker port

CANVASPILOT_REPORTS

~/.canvaspilot/reports

Output dir for scripts/ sweeps

Session broker commands

canvaspilot session start [--headless] [--base-url URL] [--profile DIR] [--port N] [--read-only]
canvaspilot session status
canvaspilot session stop

--read-only is for unattended agent use: the broker rejects non-GET/HEAD /fetch ops with 403, so a runaway agent cannot mutate Canvas through the session. GET /health advertises the mode ("read_only": true|false), so clients can discover it. It is orthogonal to broker authentication (see issue #5) — it limits what the broker can do, not who can call it.

Scripts

  • scripts/readonly_sweep.py — live read-only smoke across the whole tool surface (needs a running broker)

  • scripts/inventory_pass.py — per-course inventory: tabs, LTI/external tools, modules, third-party mentions

Development

pip install -e ".[dev]"
pytest

Tests use offline fixtures and disposable loopback HTTP servers. Feedback tests also exercise CLI subprocesses and the real MCP stdio interface; no Canvas account or running browser is needed.

The optional native-browser check executes the production in-page fetch against an authored loopback response in a fresh Chromium profile. It uses no school session and verifies that the fixture cookie remains in the browser while only Link metadata reaches the caller:

CANVASPILOT_CHROMIUM_BIN=/path/to/chromium python -m pytest -q tests/test_browser_link_metadata.py

CANVASPILOT_CHROMIUM_PROFILE_ROOT can select a writable parent for temporary profiles (useful for a confined browser package). The browser test skips only when CANVASPILOT_CHROMIUM_BIN is unset; an invalid configured executable fails. All regular HTTP/client tests run offline without a browser binary.

Responsible use

CanvasPilot gives an agent the same access you have — including submitting assignments and starting quiz attempts. Write tools are clearly named; wire approval gates in your agent if you don't want autonomous submits. Follow your institution's academic integrity policy.

Write caveats: the write path has no CSRF handling and has not been validated against live Canvas session authentication — submitting from session-broker mode may fail or behave unexpectedly. The author has not used the write tools against a live school; they exist as capability, not as a tested workflow.

Origin

Extracted from the Suite monorepo (packages/canvaspilot), where it's synced via git subtree. (The monorepo is not publicly accessible; the link resolves for authorized collaborators.)

License

MIT

Maintainer

Jacob Metoyer — software and research tooling. Upstream and collaborator credit are retained.

Find a literal phrase in the complete titles and cleaned messages for selected courses:

canvaspilot find-announcements 42 77 --text "room change" --start-date 2026-10-01

The result preserves ordered full announcement rows and identifies which fields matched. See announcement text search for matching, output, and complete-read behavior.

Read course pages from the terminal

Use canvaspilot pages 42 to discover returned page locators, then canvaspilot page 42 course-introduction to read one page's original HTML and existing cleaned text projection. The same locator can be passed to export-pages. See terminal page reading for numeric IDs, field meanings, complete-read failures and the existing API projection boundary.

Related MCP Connectors

Related MCP Servers