Skip to main content

UofT MCP

A small Python MCP server for the public UofT Timetable Builder API, with read-only Degree Explorer and ACORN access and reusable UofT login. It exposes twenty-five tools over local stdio using the official MCP Python SDK: ten public timetable tools, nine Degree Explorer reads, three ACORN reads, and three local authentication controls.

Work in progress: Degree Explorer and three basic ACORN reads are available in this checkout. Degree Explorer writes and ACORN enrolment changes are not implemented.

Public timetable tools require no login, API key, database, web server, or environment variables. This is an unofficial wrapper; it does not enroll students or write to ACORN. Saving a timetable creates an anonymous public share link on the Timetable Builder, not a personal account record.

Read the release notes for the current version's scope and known limitations.

Connect an MCP Client

Install uv, then add this configuration to any client that supports mcpServers:

{
  "mcpServers": {
    "uoft-timetable": {
      "command": "uvx",
      "args": ["uoft-mcp@latest"],
      "env": {
        "UV_HTTP_TIMEOUT": "300"
      }
    }
  }
}

Restart the client after saving its configuration. On Windows, if the client cannot find uvx, restart it after installing uv or replace "uvx" with the absolute path reported by where.exe uvx.

uvx downloads the published package into an isolated environment and starts the uoft-mcp command. No repository clone, virtual environment setup, API key, server URL, or listening port is needed. The first start can take longer while uv downloads Python and the dependencies; later starts use its cache.

To pin a release instead of following the newest release, use "args": ["uoft-mcp==0.4.1"].

Local Development

From a clone of this repository:

uv python install 3.13
uv sync --locked --managed-python

uv sync creates .venv, installs the package and development tools, and uses the committed uv.lock. .python-version selects Python 3.13; the package supports Python 3.13 and newer.

Run the local checkout with:

uv run --locked python -m uoft_mcp

The installed uoft-mcp command is another entry point. The process waits for an MCP client on stdin; a blank terminal is expected. Use Ctrl+C to stop a manual run. Stdout carries protocol messages only, and logging goes to stderr.

Connect Degree Explorer and ACORN

Degree Explorer tools reuse your saved UofT session to read academic records and existing plans. To try them from this checkout, install Chromium once:

uv run --locked python -m uoft_mcp auth setup

Connect your MCP client to this local checkout (the published package does not gain branch changes until a release):

{
  "mcpServers": {
    "uoft": {
      "command": "uv",
      "args": ["--directory", "C:/path/to/UofT-MCP", "run", "--locked", "python", "-m", "uoft_mcp"]
    }
  }
}

Ask your assistant to "Connect my UofT account to Degree Explorer and ACORN." Complete the official UofT login and Duo prompts in the dedicated Chromium window. After each service first verifies your login, the browser stays on that service for five more seconds, rechecks access, and captures the latest session before moving to the next service or closing. Login state is encrypted locally and reused after browser closure and MCP restarts, while UofT still accepts it. Passwords and Duo codes belong only on the official pages, never in chat or config.

Use "Check my UofT connection" to verify access, or "Forget my saved UofT session" to delete local access. See authentication setup and behavior for terminal commands, memory-only sessions, expiry, and troubleshooting.

Tools

Tool Arguments and purpose
get_current_sessions No arguments. Get current session IDs; skip entries with header: true.
get_reference_data No arguments. Get campus, division, delivery-mode, and sorting values.
get_divisions No arguments. List recognized faculty/division codes.
search_departments Required term keyword and divisions code.
search_course_titles Required term, divisions, and sessions strings. Optional lower_threshold=50, upper_threshold=200.
get_course_details Required course_code; optional section_code of F, S, or Y. The returned course id is required by generate_timetable.
search_courses Optional code/title, section, description, division, session, campus, delivery, and pagination filters.
generate_timetable Required plans array. Each plan has courses (course_id plus activity_types), optional preference of early, balanced, or late, and optional blocked_times.
save_timetable Required timetable object with sessions, timetables, and plans. Returns the share id plus a share_url.
retrieve_timetable Required share_id from save_timetable.
uoft_login Optional service of degree_explorer, acorn, or both (default), plus remember=true. Starts or reuses official browser login and returns while you complete Duo.
uoft_auth_status Optional refresh=false. Reports connection and login progress; refresh=true checks both services without opening a browser.
uoft_forget_session No arguments. Cancels login and removes locally saved UofT session state and its encryption key.
acorn_get_eligible_registrations Optional fields list. Read eligible registration periods; selection applies to each registration.
acorn_get_dashboard_courses Optional fields list. Read dashboard enrolled courses for the current session.
acorn_get_student_registration_info Optional fields list. Read registration/financial-hold status, person ID, and upcoming-exams flag.
degree_explorer_get_academic_history No arguments. Read course history, sessions, marks, and requirements after Degree Explorer login.
degree_explorer_get_student_data No arguments. Read the payload used by Degree Explorer's Current Status page.
degree_explorer_get_student_record No arguments. Read the record payload used by Current Status; it is not a certified transcript.
degree_explorer_get_student_user_data No arguments. Read menu/session user metadata.
degree_explorer_get_student_menu No arguments. Read available Degree Explorer navigation entries.
degree_explorer_get_messages No arguments. Read the UI string catalog, not a student inbox.
degree_explorer_get_session_timeouts No arguments. Read client timeout settings; they do not extend a session.
degree_explorer_get_planner No arguments. Read existing planner timelines and primary-plan flags.
degree_explorer_get_cell_details No arguments. Read the unparameterized planner popup endpoint; it cannot target a cell.

See Degree Explorer tools and workflow for all nine authenticated reads, their fixed API routes, and limitations. Each accepts {} and reads only the connected student's account.

See ACORN tools and workflow for the three basic reads and field-selection examples. They reuse the connected account without opening a login browser.

Each successful lookup, generation, and retrieve call returns one text block containing the complete upstream JSON. The wrapper preserves fields and arrays, including upstream payload and status envelopes. It does not summarize or truncate course data. save_timetable keeps the upstream share object and adds share_url.

Token-cost note: ACORN tools return compact JSON and accept optional fields to reduce response data before it reaches the model. Other tools still return full upstream JSON. Future client-side code execution can process intermediate results outside model context, as described in Anthropic's Code execution with MCP. This server does not provide a code-execution sandbox.

Example Workflow

  1. Call get_current_sessions with {} and select a non-header entry's value.
  2. Call get_divisions or get_reference_data for valid filter codes.
  3. Call search_course_titles with these arguments, substituting the session value:
{"term": "CSC108", "divisions": "ARTSC", "sessions": "SESSION_ID_FROM_STEP_1"}
  1. Use the returned exact course code in get_course_details:
{"course_code": "CSC108H1", "section_code": "F"}
  1. For filtered, paginated results, call search_courses:
{
  "course_code": "CSC108H1",
  "divisions": ["ARTSC"],
  "sessions": ["SESSION_ID_FROM_STEP_1"],
  "page": 1,
  "page_size": 2
}

search_courses also accepts course_title, course_section_code, search_course_description, campuses, delivery_modes, and direction (asc or desc). Pages start at 1, page size defaults to 20, and sorting defaults to asc. Omitted collection filters become empty arrays. Course codes should be exact; use autocomplete for prefixes or course_title for keyword searches.

  1. Copy each selected offering's id from get_course_details into generate_timetable. Activity types are the section types to fill, such as Lecture, Tutorial, or Practical. Preference defaults to balanced. Optional blocked intervals use weekday names and 24-hour HH:MM times:
{
  "plans": [
    {
      "courses": [
        {
          "course_id": "COURSE_ID_FROM_STEP_4",
          "activity_types": ["Lecture", "Tutorial"]
        }
      ],
      "preference": "early",
      "blocked_times": [{"day": "Monday", "start": "8:00", "end": "10:00"}]
    }
  ]
}

Use one plan per term. A Fall and Winter year is two plans. The solver returns chosen sections; it does not enroll students.

  1. Store a Timetable Builder state with save_timetable. The timetable object must include sessions, timetables, and plans in the frontend's serialized shape. The tool returns id and share_url (https://ttb.utoronto.ca/#!/?t=...).

  2. Reload that share later with retrieve_timetable:

{"share_id": "SHARE_ID_FROM_STEP_7"}

Checks

uv run --locked pytest -q
uv run --locked ruff check .
uv run --locked ruff format --check .

The PR workflow runs these checks on pull requests targeting main or master, using Python 3.13 on Ubuntu. New commits cancel an older run for the same PR. Tests need no Chromium installation or UofT credentials.

The tests run offline. They cover all twenty-five tools, request mapping, raw JSON preservation, validation, HTTP errors, timeouts, connection errors, invalid JSON, shared-client cleanup, MCP discovery, and actual stdio subprocesses. Authentication tests cover encrypted persistence, restoration, browser lifecycle, expiry, locking, CLI controls, and sanitized status results using synthetic state and fake backends. Degree Explorer tests also cover all nine GET mappings, MCP schemas and annotations, JSON preservation, sanitized errors, response disposal, saved-session reuse, and serialization with login and forget. These reads have not been verified against a live authenticated account in this change. ACORN tests additionally cover compact JSON, field selection, and expected root types. All 240 tests pass. A live ACORN read on 2026-09-19 returned a login redirect; authenticated live verification remains pending.

Previous timetable verification: 50 tests passed. Live checks of lookup, generateYear, tiny/shorten, and tiny/retrieve succeeded, including generating CSC258H1 and CSC311H1, saving an anonymous share, and retrieving that share. Live requests are deliberately not part of the test suite, so tests remain reproducible.

Current authentication verification is recorded in AUTHENTICATION.md.

API Notes

Project layout

The MCP entry points remain uoft_mcp.server and python -m uoft_mcp. Service-specific code is grouped below the package so integrations can grow independently:

uoft_mcp/
├── timetable_builder/  # public Timetable Builder client and API reference
├── degree_explorer/    # allowlisted, read-only Degree Explorer client
├── acorn/              # allowlisted ACORN reads and field selection
├── utilities/          # shared authentication, browser, and secure session storage
├── server.py           # MCP tool registration and application wiring
└── cli.py              # stdio server and terminal authentication commands
  • The supplied Timetable Builder reference remains the original reference. Live checks found two missing details: pagination starts at 1, and paginated search requires an empty departmentProps array when not filtering by department. The wrapper supplies it.
  • generateYear accepts an array of plans. Each plan needs course id values from get_course_details, sections with name: "*" and a type, fitnessFunctionOption (MORNING_WEIGHTED, BALANCED, or AFTERNOON_WEIGHTED), and blockedOff intervals using weekday numbers 1-5 (Monday-Friday) and milliseconds since midnight.
  • tiny/shorten stores the official https://ttb.utoronto.ca/#!/? URL for a serialized timetable and returns { "id": "..." }. tiny/retrieve?id= loads it. These are anonymous share records, not ACORN enrolment.
  • Get division codes from the API. For example, the live API uses ERIN and SCAR for Mississauga and Scarborough, rather than the reference's UTM and UTSC examples.
  • Even one course can have a large response because all its sections are included. Choose narrow filters and small page sizes. The wrapper never fetches extra pages.
  • HTTP failures become MCP tool errors containing the endpoint and status code. UofT may return HTTP 404 for no matching courses. Timeouts, connection failures, and malformed JSON get their own readable errors. No automatic retries occur.
  • This API is not covered by an official support guarantee. Changes upstream may require updating the mappings. Successful HTTP responses are preserved as supplied, including any application-level status messages inside their JSON.

Metadata

Release files for uoft-mcp 0.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for uoft-mcp 0.5.0
File Size Uploaded Provenance Actions
uoft_mcp-0.5.0.tar.gz 94.9 kB Without provenance Details for uoft_mcp-0.5.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for uoft-mcp 0.5.0
File Interpreter ABI Platform Actions
uoft_mcp-0.5.0-py3-none-any.whl Python 3 none any Details for uoft_mcp-0.5.0-py3-none-any.whl

Total release size: 128.9 kB

Release files / uoft_mcp-0.5.0.tar.gz

Download URL uoft_mcp-0.5.0.tar.gz
Size 94.9 kB
Tags Source
SHA-256 checksum
How to use checksums
38b0062163e4e695298666a2eaa0823e2722880dac28bb7db88cffeb39ba51e3
BLAKE2b-256 checksum
How to use checksums
cc43f2c89896e0179c94ef33bf073fb08ced1a3d7aea61aad4c9b037e4f82bd3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","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":true}

Release files / uoft_mcp-0.5.0-py3-none-any.whl

Download URL uoft_mcp-0.5.0-py3-none-any.whl
Size 34.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f0b4197acdb2c555bd60881f6d8aa2464cebe992dc3605a5a76364be8ed72994
BLAKE2b-256 checksum
How to use checksums
d116ffbc5165068a6d23aa01bf710b71a980603a9e94e4a998952a0e1aab3b24
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","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":true}

Security for uoft-mcp 0.5.0

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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page