uoft-quercus-mcp
Unofficial MCP server for University of Toronto students: Quercus (Canvas) tools via a personal access token, plus public Timetable Builder helpers for schedule search and conflict checks.
Disclaimer
This project is not affiliated with, endorsed by, or supported by the University of Toronto.
- Quercus tools use the Canvas API with a personal access token you create. That is not an official Quercus product.
- Timetable tools use the public Timetable Builder HTTP API (separate from Quercus). U of T may change, rate-limit, or block access at any time.
Use at your own risk and respect upstream terms of use.
What it does
Quercus (requires QUERCUS_ACCESS_TOKEN):
- Verify your Quercus identity
- List enrolled courses
- Todo items and upcoming events
- Assignments and announcements (announcements use term start, else last 365 days, through today)
- Modules and course files
- Download files and extract text from PDF/DOCX/plain text (no OCR)
Timetable Builder (no Quercus token):
- Discover current sessions, divisions, campuses, delivery modes, and course levels
- Search courses by code or title
- Fetch section, meeting, instructor, and enrolment details
- Deterministically check selected sections for time overlaps and transition gaps
What it does not do
- UTORid / password login, cookie scraping, or session hijacking
- Hosted OAuth / multi-user Quercus product (personal token only for now)
- Course enrolment or waitlist changes
- Degree / prerequisite / eligibility decisions
- Exam schedules
- Saved timetables, accounts, or persistence
- OCR for scanned PDFs/images
- Automatic schedule generation inside the server
Privacy
- Quercus: put your personal access token only in the MCP server
env(for example Cursormcp.json). The token is never logged or returned in tool output. Do not commit or share it. - Downloads stay on your machine under
QUERCUS_DOWNLOAD_DIR. - Timetable tools call the public Timetable Builder API only — no U of T credentials required.
Prerequisites
- Python 3.11+
uv(recommended) orpip
Installation
The published package on PyPI is the recommended install. uvx runs it without a local clone:
uvx uoft-quercus-mcp
Or install it:
uv tool install uoft-quercus-mcp
# or
pip install uoft-quercus-mcp
For a local checkout, see Development.
Create a Quercus personal access token
- Sign in to Quercus.
- Open Account → Settings → New Access Token (wording may vary by campus/theme).
- Copy the token once when it is shown.
- Put it only in MCP
envasQUERCUS_ACCESS_TOKEN— never in chat, commits, or screenshots.
Background on Canvas tokens: How do I manage API access tokens? (campus Quercus help may differ slightly).
Without a token, Timetable tools still work; Quercus tools return quercus_auth_missing.
Cursor MCP configuration
Add this to your Cursor MCP settings:
{
"mcpServers": {
"uoft-quercus": {
"command": "uvx",
"args": ["uoft-quercus-mcp"],
"env": {
"QUERCUS_ACCESS_TOKEN": "your-quercus-personal-access-token"
}
}
}
}
A deprecated console-script alias uoft-timetable-mcp still points at the same entry point for old configs. Prefer uoft-quercus-mcp.
After restarting MCP, Cursor should discover Quercus tools (token required) and Timetable Builder (TTB) tools:
- Quercus:
quercus_whoami,quercus_list_courses,quercus_list_todo,quercus_list_assignments,quercus_list_announcements,quercus_list_modules,quercus_list_files,quercus_get_file - Timetable Builder (TTB):
ttb_get_reference_data,ttb_search_courses,ttb_get_course_details,ttb_check_conflicts
Quick start prompts
- “What’s due on Quercus?”
- “List modules for MATA22 and open the past quiz PDF, then help with question 4.”
- “Search Fall timetable for CSC108H1 and check conflicts for these lecture/tutorial sections.”
Tools
Quercus tools
| Tool | Purpose |
|---|---|
quercus_whoami |
Verify the token / identity |
quercus_list_courses |
Canvas id + course_code + name |
quercus_list_todo |
Todo items and upcoming events |
quercus_list_assignments |
Course-scoped assignments (full list) |
quercus_list_announcements |
Course-scoped; term start (else last 365 days) through today |
quercus_list_modules |
Modules / materials map (file_id on File items) |
quercus_list_files |
Course Files area |
quercus_get_file |
mode=metadata|text|download (PDF/DOCX/text extraction; no OCR) |
Course-scoped tools accept a Canvas id, code fragment (MATA22), or name fragment. Prefer ids from quercus_list_courses when ambiguous.
Past quiz PDF flow: quercus_list_modules or quercus_list_files → quercus_get_file with mode=text.
Timetable Builder (TTB) tools
| Tool | Purpose |
|---|---|
ttb_get_reference_data |
Valid sessions, divisions, campuses, delivery modes, course levels (not hard-coded) |
ttb_search_courses |
Search by code or title; requires session(s) + division(s); concise paginated summaries |
ttb_get_course_details |
Full section/meeting details; optional section_code is term half (F/S/Y), not LEC/TUT |
ttb_check_conflicts |
Deterministic overlap / transition checks on selected sections |
Prefer full UofT course codes when known (e.g. CSC108H1, CSCA08H3). Short codes are expanded using the selected division from ttb_get_reference_data (ARTSC / APSC St. George, SCAR UTSC, ERIN UTM).
Before claiming a schedule is verified, call ttb_check_conflicts with every selected section and require has_conflicts false, empty transition_violations, and is_complete true.
Data sources and freshness
- Quercus:
https://q.utoronto.ca(Canvas API; needs personal access token) - Timetable Builder:
https://api.easi.utoronto.ca/ttb - Reference data is cached in memory for 15 minutes (
TIMETABLE_REFERENCE_CACHE_TTL_SECONDS) - Search / details / conflicts fetch current upstream data
- Enrolment numbers, rooms, and Quercus content can change; treat responses as point-in-time
Environment variables
| Variable | Default | Purpose |
|---|---|---|
QUERCUS_ACCESS_TOKEN |
(unset) | Quercus personal access token |
QUERCUS_BASE_URL |
https://q.utoronto.ca |
Quercus site root |
QUERCUS_CONNECT_TIMEOUT_SECONDS |
5 |
Quercus connect timeout |
QUERCUS_READ_TIMEOUT_SECONDS |
20 |
Quercus read timeout |
QUERCUS_MAX_ATTEMPTS |
3 |
Quercus retry attempts |
QUERCUS_MAX_PAGE_SIZE |
100 |
Canvas per_page cap |
QUERCUS_MAX_CONCURRENCY |
5 |
Max concurrent module-item fetches |
QUERCUS_DOWNLOAD_DIR |
~/.cache/uoft-quercus-mcp/files |
Local download directory |
QUERCUS_COURSE_CACHE_TTL_SECONDS |
120 |
Course-list resolver cache TTL |
QUERCUS_MAX_DOWNLOAD_BYTES |
26214400 |
Max file download size (25 MiB) |
QUERCUS_MAX_TEXT_CHARS |
200000 |
Max extracted text characters |
TIMETABLE_BASE_URL |
https://api.easi.utoronto.ca/ttb |
Upstream API root |
TIMETABLE_CONNECT_TIMEOUT_SECONDS |
5 |
Connect timeout |
TIMETABLE_READ_TIMEOUT_SECONDS |
20 |
Read timeout |
TIMETABLE_REFERENCE_CACHE_TTL_SECONDS |
900 |
Reference-data cache TTL |
TIMETABLE_MAX_PAGE_SIZE |
50 |
Max search page size |
TIMETABLE_MAX_CONCURRENCY |
5 |
Max concurrent course lookups |
Development
git clone git@github.com:skhdemo/uoft-quercus-mcp.git
cd uoft-quercus-mcp
uv sync
If your local folder is still named uoft-timetable-mcp, that is fine until you rename it.
Run the server from the checkout:
uv run uoft-quercus-mcp
# or
uv run python -m uoft_quercus_mcp
Local Cursor config (no PyPI install):
{
"mcpServers": {
"uoft-quercus": {
"command": "uv",
"args": [
"--directory",
"/absolute/path/to/uoft-quercus-mcp",
"run",
"uoft-quercus-mcp"
],
"env": {
"QUERCUS_ACCESS_TOKEN": "your-quercus-personal-access-token"
}
}
}
}
Lint and test:
uv run ruff format .
uv run ruff check .
uv run pyright
uv run pytest
uv run pytest --cov
Optional live API smoke tests (excluded by default):
uv run pytest -m live
uv run pytest -m live_quercus
Troubleshooting
Quercus auth missing / rejected
- Confirm
QUERCUS_ACCESS_TOKENis set in the MCP server environment (Cursormcp.jsonenvdoes not apply to a plain shellpytest) - Regenerate the token in Quercus if it was revoked
- Some Canvas features may return
quercus_forbiddendepending on course permissions
Timeouts / upstream unavailable
- Retry later; transient
429/5xx/ network timeouts are retried a few times - Check network access to
q.utoronto.caand/orapi.easi.utoronto.ca - Increase
QUERCUS_READ_TIMEOUT_SECONDSorTIMETABLE_READ_TIMEOUT_SECONDSif needed
No timetable search results
- Confirm session and division values via
ttb_get_reference_data(do not guess old term codes) - Try a broader title query, or a full course code like
CSC108H1 - Empty results (
courses: [],total: 0) are valid — not an error
Local folder still named uoft-timetable-mcp
The GitHub repo is uoft-quercus-mcp. Renaming your local checkout folder is optional; if you use the local Cursor config, update the --directory path.
License
See LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file uoft_quercus_mcp-0.1.0.tar.gz.
File metadata
- Download URL: uoft_quercus_mcp-0.1.0.tar.gz
- Upload date:
- Size: 40.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3f0b660e19b43e2d8df12ab3e58a6148e7c9bde66e80b33be4747634543db2f0
|
|
| MD5 |
507096dbfbc5e1e4f89c3dc376ed43e6
|
|
| BLAKE2b-256 |
19b0265e947f23d7eb44ecc4ee55f0951c8e512018e84c1af27d08a1e91fa59a
|
File details
Details for the file uoft_quercus_mcp-0.1.0-py3-none-any.whl.
File metadata
- Download URL: uoft_quercus_mcp-0.1.0-py3-none-any.whl
- Upload date:
- Size: 50.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6170c4a946be9609a4d7e4de2761c67e0c01bd9a5a39ea0283829eca74d70083
|
|
| MD5 |
2411e351d8226d1b5bc97f4234123b6a
|
|
| BLAKE2b-256 |
6faa6f6ba8ec177a7a7653c629d9a1a14ba1c9a9bf0490b23ced721db6c5334f
|