Metadata-Version: 2.4
Name: canvasctrl
Version: 0.4.2
Summary: Canvas in your terminal: a Canvas LMS companion CLI with a local vault, a bring-your-own-key AI chat agent (Claude-first, OpenAI-compatible), and an MCP server.
Author: CanvasCtrl contributors
License-Expression: MIT
Keywords: canvas,lms,cli,education
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Education
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Education
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.31
Provides-Extra: agent
Requires-Dist: anthropic>=0.117; extra == "agent"
Requires-Dist: openai>=1.60; extra == "agent"
Provides-Extra: mcp
Requires-Dist: mcp>=1.28; extra == "mcp"
Provides-Extra: all
Requires-Dist: canvasctrl[agent,mcp]; extra == "all"
Provides-Extra: dev
Requires-Dist: pytest>=7.4; extra == "dev"
Requires-Dist: pytest-cov>=4.1; extra == "dev"
Requires-Dist: responses>=0.24; extra == "dev"
Requires-Dist: ruff>=0.4; extra == "dev"
Dynamic: license-file

# 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 `.ics` calendar.
- Keep the original Python vault, `ask`/`chat`, and vault-only MCP workflows available through `canvasctrl-vault` and the compatibility command `canvasctl`.

### 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_read` for 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:

1. The profile has explicitly enabled the confirmed-write scope.
2. A preparation call produces an exact sanitized preview and one-time, short-lived confirmation.
3. The MCP host asks the user before calling the confirmation tool.
4. 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.
5. 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

```text
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](docs/architecture.md), [mcp.md](docs/mcp.md), [security.md](docs/security.md), [distribution.md](docs/distribution.md), and the [CanvasCtrl migration runbook](docs/canvasctrl-migration.md).

## Public install

Requirements:

- macOS 13.5 or newer on Apple Silicon or Intel
- Google Chrome for browser-session authentication

```bash
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.

```bash
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:

```bash
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:

```bash

# 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)

```bash
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:

```bash
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

```bash
canvasctrl serve --stdio
```

Claude Code:

```bash
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:

```text
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:

```json
{
  "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:

```bash
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:

```bash
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`:

```bash
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](docs/mcp.md) for the full commands and boundaries.

## Verification

```bash
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](docs/security.md) before enabling remote access or writes.

## License and disclaimer

[MIT](LICENSE) © 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.
