Selected work

Letting my AI read my coursework

I extended an open-source Canvas LMS MCP server with Firefox session auth and inline file reads, then ran it headless on a Linux server so any assistant can see what’s due.

Type
Open-source contribution
Code
Blake192/canvasmcp
Upstream
ynbh/canvasmcp
Built with
Python, FastMCP, Docker, noVNC

What it does

Canvas is the learning-management system my university runs on: courses, assignments, due dates, syllabi, files. canvasmcp1 is an open-source Model Context Protocol2 server that lets an AI assistant read it. Instead of clicking through five course pages, I can ask “What’s due this week?” or “Summarize the syllabus for EECS 678.”

The original authenticates by reading Canvas session cookies out of Chrome on the same machine, which suits a laptop. I wanted it running around the clock on my home server: a headless Linux box with no Chrome profile to read.

What I changed

One focused commit to my fork,3 kept compatible with upstream behavior: Chrome stays the default, and everything new is opt-in.

  • Firefox as a cookie source. With CANVAS_COOKIE_BROWSER=firefox, the server reads the canvas_session and _csrf_token cookies for the configured Canvas host using browser_cookie3,4 prefers the most specific matching domain, and ignores expired cookies.
  • An explicit override. CANVAS_SESSION_COOKIE and CANVAS_CSRF_TOKEN can be supplied directly, and setting one without the other is an error rather than a silent half-login. The README tells users to treat them as credentials.
  • Files straight into the chat. A new read_course_file tool returns a Canvas file as an MCP embedded binary resource, so a compatible client can read a PDF inline instead of downloading it to disk first.
  • Tests. About 270 lines of new tests cover the auth paths and inline file reads.

A size limit that holds

Inlining files into a chat needs a ceiling. The limit defaults to 25 MB and is capped at 50 MB, and it’s enforced twice: once against Canvas’s file metadata, and again while reading, because metadata can be missing or wrong. Reading at most one byte past the limit keeps memory bounded either way, and anything larger points the user to the existing download tool.

def read_course_file(course_id, file_id, max_size_mb=25):
    max_size_mb = max(1, min(int(max_size_mb), 50))
    max_bytes = max_size_mb * 1024 * 1024
    info = client.get_file(course_id=course_id, file_id=file_id)
    if isinstance(info.get("size"), int) and info["size"] > max_bytes:
        raise ValueError("... Use download_course_file instead.")
    ...
    # Bound memory even when Canvas metadata is missing or inaccurate.
    data = downloaded_file.read(max_bytes + 1)
    if len(data) > max_bytes:
        raise ValueError("... Use download_course_file instead.")
    return ToolResult(content=[..., EmbeddedResource(...)])
Figure 1. The size gate in read_course_file, condensed from the fork.

Running it on a server

The fork reads cookies from a browser session, so the server needs a browser someone has logged into. My Docker image builds the fork pinned to a specific commit and runs Firefox ESR on a virtual display, reachable only through noVNC5 over an SSH tunnel. I log into Canvas there once. The MCP server reads that profile’s cookies, and a keepalive runs every 20 minutes to keep the session fresh. No secrets are baked into the image: the browser profile and all state live in a mounted data volume.

One-time login

Me, over an SSH tunnelnoVNC in a browser tab
Firefox ESRvirtual display, persistent profile

Docker on my server

canvas-mcp (my fork)reads session cookies, pinned commit
Keepaliveevery 20 minutes

Assistants

Claudestdio over SSH
ChatGPTSecure MCP Tunnel, outbound-only
Figure 2. The deployment. The only interactive step is logging into Canvas once, in a browser that lives on the server.

Limits

Session cookies are credentials, and anyone with them is logged in as me, which is why the browser profile never leaves the server’s data volume. Inline display depends on the MCP client: an embedded resource is not guaranteed to appear as a native chat attachment. And upstream’s scheduled-submission feature relies on macOS launchd, so it doesn’t work on the Linux server; I haven’t needed it.

References

  1. ynbh/canvasmcp: MCP server for Canvas.
  2. Anthropic. Model Context Protocol.
  3. Blake192/canvasmcp: my fork, with Firefox cookie auth and inline MCP file resources.
  4. browser_cookie3: load browser cookies into Python.
  5. noVNC: an HTML VNC client.