CanvasCtrl
CanvasCtrl is a local-first Canvas LMS toolkit with two authentication paths:
- Browser session for schools that do not allow Personal Access Tokens (PATs). You complete normal SSO/MFA in visible Chrome; Canvas credentials are stored in macOS Keychain.
- PAT for users whose Canvas account allows one. The token is read from standard input and stored in Keychain by the session MCP, while the original Python CLI remains compatible with its existing configuration.
The unified repository preserves the original CLI, local JSON vault, ask/chat agent, and read-only Python MCP while adding the session-backed MCP, direct live Canvas reads, file extraction, guarded uploads/submissions, full same-origin API access, and optional private Cloudflare mobile access.
The macOS installer also installs the bundled CanvasCtrl skill for Codex and Claude when it can do so without replacing a user-owned skill directory. The skill routes Canvas work through the MCP, materializes multi-file evidence in the private vault, and carries the quiz consent and New Quizzes boundary rules.
This unified build is currently an access-controlled source beta. The informational website is public; the repository and install artifacts remain private until a reviewed release is approved.
What it can do
Local vault and CLI
- Mirror courses, assignments, classic quizzes, modules, module items, files, and announcements into owner-private JSON under
~/canvas-vault/. - Show today/upcoming work, changes, announcements, dashboards, and individual records.
- Download course files and export due dates as an
.icscalendar. - Keep the original Python vault,
ask/chat, and vault-only MCP workflows available throughcanvasctrl-vaultand the compatibility commandcanvasctl.
Live MCP
- Read profiles, courses, syllabi, assignments, modules, pages, files, submissions, grades, calendars, todos, announcements, and discussions.
- Extract bounded text from authorized PDF, HTML, and UTF-8 Canvas files without returning signed URLs.
- Inspect a 164-operation catalog spanning 29 Canvas domains and invoke 73 enabled deterministic reads. The latest 21-read expansion covers course progress/settings/permissions, effective and differentiated dates, appointment groups, content shares, observer links, and richer self-submission details.
- Use
canvas_api_readfor an arbitrary relative/api/v1/...or/api/quiz/v1/...route on the configured Canvas origin when a dedicated tool is unavailable. - Preview and submit supported file, text-entry, and URL coursework through dedicated state-bound workflows. File upload remains a separate approval before a later file submission.
- Prepare and execute non-reserved same-origin Canvas API writes that the authenticated Canvas user is allowed to perform. Only allowlisted self-service routes use ordinary confirmed-write authorization; grading, enrollment, SIS, course/account administration, and unrecognized mutations require a separate locally approved privileged capability. Submissions, uploads, Classic Quiz attempts, and credential-bearing routes remain behind dedicated workflows or unavailable.
- Run a documented Classic Quiz through one exact pre-start consent: the user says
Yes, I want you to attempt this quiz., completes one native owner authentication, and that scoped session covers start, answer saves, and completion without per-question prompts.
Canvas—not CanvasCtrl—ultimately decides whether a route is available to a user, course, role, institution, or installed feature. New Quizzes does not expose a documented public student attempt lifecycle; CanvasCtrl does not bypass that boundary.
Write policy
Canvas writes are available but never automatic. A generic write requires all of these:
- The profile has explicitly enabled the confirmed-write scope.
- A preparation call produces an exact sanitized preview and one-time, short-lived confirmation.
- The MCP host asks the user before calling the confirmation tool.
- The local CanvasCtrl process asks macOS to authenticate the device owner for that exact action. Touch ID approves with a successful touch when available; macOS falls back to the Mac account password when Touch ID cannot be used.
- Only successful system authentication performs one request; cancellation, authentication failure, timeouts, mismatches, and retries fail closed.
CanvasCtrl uses macOS LocalAuthentication and receives only a success or failure; it never receives the fingerprint or Mac password. Depending on the user's Mac settings, macOS may also offer an approved Apple Watch. There is no unauthenticated button-only approval path.
Generic grading, enrollment, SIS, course/account administration, and unknown
mutation routes additionally require canvasctrl authorize-privileged-writes.
That profile-level grant has its own native authentication prompt, is never implied by ordinary
confirmed-write authorization, and does not approve any later Canvas request;
each privileged write still needs its own preview, chat confirmation, and native
approval.
File upload preparation performs no Canvas write. Upload preflight and byte transfer happen only after native owner authentication. Uploading a file never submits coursework; file, text-entry, and URL submissions each have their own exact preview and authentication.
Classic Quiz attempts use a narrower workflow-specific gate. One exact preview and pre-start approval covers start, answer saves, and final completion only for the returned short-lived quiz session. It cannot authorize another quiz or attempt, does not expose Canvas's validation token, and never retries a mutation.
Architecture
CLI / AI client / mobile connector
│
deterministic tools
│
Canvas application services
├── local JSON vault
├── read and file service
├── guarded write service
└── session/PAT broker
│
same-origin Canvas client
│
your Canvas account
The browser is used only for user-controlled authentication or reauthentication. Normal Canvas calls run through the standalone HTTP client.
See architecture.md, mcp.md, security.md, distribution.md, and the CanvasCtrl migration runbook.
Public install
Requirements:
- macOS 13.5 or newer on Apple Silicon or Intel
- Google Chrome for browser-session authentication
curl -fsSL https://canvasctrl.vivek-patel.com/install.sh | sh
The public bootstrap installs a private, pinned Node.js 24 LTS runtime under ~/.local/lib/canvasctrl, then installs a prebuilt CanvasCtrl runtime and universal Keychain and LocalAuthentication approval helpers. Both downloads are SHA-256 verified before activation. It does not use or alter a system Node.js installation and requires no npm, Homebrew, Apple Command Line Tools, Apple Developer account, or administrator access. At the end it asks for the school's bare HTTPS Canvas URL and saves that non-secret origin as the default profile; credentials and MCP client configuration remain untouched. Press Return to skip, or set CANVASCTRL_SKIP_ONBOARDING=1 for a noninteractive install. If an existing Codex configuration still launches an older source-tree build, the installer prints the exact launcher replacement and restart guidance without editing the file.
Updater-enabled releases check the signed stable channel on MCP start and then at most once every 24 hours while it remains running. A newer immutable runtime is downloaded, signature/checksum verified, validated, and atomically selected for the next MCP start. Canvas profiles, Keychain credentials, browser profiles, vault files, and MCP client configuration are outside the versioned application directory and remain unchanged. Existing pre-updater installations must run the public installer one final time to receive this bootstrap.
canvasctrl update check
canvasctrl update apply
canvasctrl update rollback
canvasctrl update channel stable
canvasctrl update automatic on
Development install
An approved source checkout retains the system-Node build path for contributors. It requires Node.js 20.16 or newer, npm, and Apple Command Line Tools; Python 3.10 or newer is needed only for the preserved legacy CLI/chat surface.
Run:
git clone "$CANVASCTRL_REPOSITORY_URL" canvasctrl
cd canvasctrl
./scripts/install-macos.sh
This developer installer builds and verifies both cores with the contributor's system Node.js, compiles the local Keychain and LocalAuthentication approval helpers, and installs an immutable runtime under ${CANVASCTRL_INSTALL_PREFIX:-$HOME/.local}/lib/canvasctrl. It offers the same local school-URL prompt, but does not edit an MCP client configuration or start authentication automatically.
Equivalent manual setup:
# Session MCP and unified TypeScript core
npm install
npm run build:native:macos
npm run build
npm_config_prefix="$HOME/.local" npm link
# Preserved Python CLI, vault, chat, and compatibility MCP
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[agent,mcp,dev]"
The unified TypeScript executable is canvasctrl. Compatibility launchers named
canvas-mcp and canvasctl are retained when they do not overwrite a
user-owned executable. The preserved Python surface installs as
canvasctrl-vault; its former canvasctl command remains available for
existing scripts. From that Python surface, live forwards to canvasctrl and
mcp --live starts the unified MCP, while plain mcp starts the vault-only MCP.
Authentication
Browser session (no PAT)
canvasctrl setup --origin https://canvas.your-school.edu --auth session
canvasctrl login
canvasctrl status
The first school becomes the default, so later commands do not need --profile. setup reuses an existing profile with the same normalized Canvas origin instead of creating a second record or resetting its capabilities. Use canvasctrl setup --profile NAME --origin URL to add another account and canvasctrl set-default --profile NAME to switch the implicit school. canvasctrl profiles lists the configured choices.
You enter passwords and MFA only in visible Chrome. They are never accepted by an MCP tool or returned to an AI model. The saved session persists in macOS Keychain until Canvas expires/revokes it or you log out.
Personal Access Token
Pass the token through stdin, never the command line:
printf '%s' "$CANVAS_PAT" | canvasctrl setup \
--origin https://canvas.your-school.edu \
--auth pat \
--pat-stdin
canvasctrl status
The token is stored in Keychain and is attached only to the configured Canvas origin. It is stripped from off-origin file-download requests and redirects.
Local MCP setup
canvasctrl serve --stdio
Claude Code:
claude mcp add canvasctrl -- canvasctrl serve --stdio
The public website also offers reviewed setup links for Codex/ChatGPT desktop and Claude Code, plus native MCP install links for Cursor and VS Code. Guided links prefill an installation task and do not execute it until the user sends the task and approves the commands. Cursor and VS Code links add only the local stdio configuration, so run the installer first. Other local MCP clients can download the generic configuration from:
https://canvasctrl.vivek-patel.com/canvasctrl-mcp.json
ChatGPT web, Claude.ai, and mobile clients cannot reach a local stdio process. They require an optional user-owned remote gateway; the public distribution never embeds the maintainer's Cloudflare endpoint or credentials.
Claude Desktop configuration:
{
"mcpServers": {
"canvasctrl": {
"command": "/absolute/path/to/canvasctrl",
"args": ["serve", "--stdio"]
}
}
}
For the preserved Python vault-only MCP, use canvasctrl-vault mcp; existing
canvasctl mcp configurations remain supported.
Enabling writes
Writes start disabled. Explicitly enable confirmation-gated Canvas API writes:
canvasctrl authorize-confirmed-writes
This permits authorized Classic Quiz actions and other same-origin API writes. Generic mutations still require their own short-lived preview, explicit user confirmation, and native owner-authentication prompt. A Classic Quiz instead requires one exact approval before the attempt starts; that scoped session covers its answer saves and completion without prompting on every question.
This ordinary authorization never enables generic grading, enrollment, SIS, or course/account-administration mutations. When a user with the necessary Canvas role intentionally needs those routes, they must separately run:
canvasctrl authorize-privileged-writes
The capability grant performs no Canvas write and requires its own native device-owner authentication prompt. Every later privileged request remains one-time, previewed, confirmed, natively approved, and subject to Canvas permissions.
Text-entry and URL coursework submissions are enabled by
authorize-confirmed-writes and use dedicated prepare/confirm tools. They do
not need a staged file, but still
require an exact preview, explicit confirmation, native owner authentication, and post-write
verification. File uploads and file-based coursework submissions additionally
require a one-time upload verification. Use the actual profile name shown by
canvasctrl profiles:
CANVAS_PROFILE_NAME=your-school
mkdir -p "$HOME/Documents/Canvas MCP/Uploads/$CANVAS_PROFILE_NAME"
cp /path/to/a/test-file.pdf "$HOME/Documents/Canvas MCP/Uploads/$CANVAS_PROFILE_NAME/"
canvasctrl verify-upload \
--profile "$CANVAS_PROFILE_NAME" \
--course-id 12345 \
--source "$HOME/Documents/Canvas MCP/Uploads/$CANVAS_PROFILE_NAME/test-file.pdf"
The verification upload itself requires native device-owner authentication and creates a file in the selected course Files area. Use a harmless test file and course. The command does not submit an assignment.
canvasctrl doctor checks the native helper and macOS owner-authentication
availability without opening a prompt. canvasctrl test-approval-dialog starts
a harmless local system-authentication test that never contacts Canvas or
authorizes a future write. Canceling it is a denial. A connected MCP client can call
canvas_get_runtime_info to verify the server version, selected profile,
exposure, and whether the native approval broker is actually attached.
Private mobile access
Optional remote mode uses a tunnel initiated by the user's Mac and a Cloudflare Access JWT restricted to one email, one device identity, and one Canvas profile. Canvas credentials never go to Cloudflare or the mobile MCP client. The Mac must remain awake and online.
This project does not ship the maintainer's hostname, tunnel token, Access audience, account ID, credentials, or Canvas data. Each installer must configure their own endpoint. Remote mode is opt-in and read-only unless the server is explicitly started with --confirmed-writes; even then, writes still require successful device-owner authentication on the Mac.
See mcp.md for the full commands and boundaries.
Verification
npm run typecheck
npm test
uv run --with pytest --with pytest-cov --with responses pytest -q
npm run privacy:check
npm run website:check
npm run distribution:check
Privacy summary
- No CanvasCtrl-hosted backend is required.
- Session cookies and PATs remain in the local OS credential vault.
- Course data and downloaded files are private local files with owner-only permissions.
- Signed file URLs, cookies, CSRF values, upload parameters, and authentication headers are removed from MCP results and safe errors.
- AI clients receive only the Canvas data needed for requested tool results. If you use the original BYOK chat agent, its prompt and selected tool results go directly to the AI provider you configured.
- The optional Cloudflare tunnel transports MCP requests to your own Mac; it does not store or distribute Canvas credentials.
- Repository privacy checks reject maintainer-specific identifiers and common secret patterns.
Read security.md before enabling remote access or writes.
License and disclaimer
MIT © 2026 CanvasCtrl contributors.
CanvasCtrl is an independent, unofficial tool. It is not affiliated with, endorsed by, or sponsored by Instructure, Inc. Canvas and Instructure are trademarks of Instructure, Inc.
Release files for canvasctrl 0.4.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| canvasctrl-0.4.2.tar.gz | 63.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| canvasctrl-0.4.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 122.4 kB
Release files / canvasctrl-0.4.2.tar.gz
| Download URL | canvasctrl-0.4.2.tar.gz |
|---|---|
| Size | 63.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4c9f8eeb48a2f80b5e3ec9c3a87ac80bebc5093ef408482c1dff6454dd319eaa
|
|
BLAKE2b-256 checksum How to use checksums |
5ac0f0a9f6c86ea8453116316d1a8fd2e7b906e226bc484a3513ed41827db09d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 6, 2026.
Transparency logRelease files / canvasctrl-0.4.2-py3-none-any.whl
| Download URL | canvasctrl-0.4.2-py3-none-any.whl |
|---|---|
| Size | 59.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a34ce4454e4670ab27206c1bd29467ea14c7d24227984262618599af9d02639e
|
|
BLAKE2b-256 checksum How to use checksums |
24c051a99d005e30a7a4e80e2a6cae3a48fda938fb033ad263844b233027c2a8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 6, 2026.
Transparency log