Skip to main content
Glama

unw-planner

CI

A command-line and MCP client for planning courses at the University of Northwestern – St. Paul. It signs in to UNW's Banner 9 Student Self-Service and Ellucian Degree Works audit with an Entra ID + TOTP login, reads the degree audit and course catalog, resolves remaining requirements against real sections, checks time conflicts, and builds term or multi-term plans using Rate My Professors ratings as a soft preference. The same domain layer is exposed as a unwplanner CLI and a stdio MCP server for agent use.

This is an independent project. It is not affiliated with, endorsed by, or supported by the University of Northwestern, Ellucian, or Rate My Professors.

Requirements

  • Python 3.11+ (CI covers 3.11 and 3.12).

  • A UNW account with access to Banner 9 Student Self-Service and the Degree Works dashboard.

  • TOTP two-factor authentication. The login driver generates the current code from a shared secret. Push-notification-only accounts, or tenants that require number matching without a "use a verification code" fallback, cannot be automated.

  • Network access to registration-ss.oci.unw.edu, degreeaudit.oci.unw.edu, login.microsoftonline.com and www.ratemyprofessors.com.

Related MCP server: ewm-ops-mcp

One-command install

macOS / Linux:

curl -fsSL https://raw.githubusercontent.com/HenryGetz/unw-planner/main/bootstrap.sh | sh

Windows PowerShell:

irm https://raw.githubusercontent.com/HenryGetz/unw-planner/main/bootstrap.ps1 | iex

The bootstrap clones to ${UNW_PLANNER_HOME:-$HOME/.unw-planner} (or the Windows equivalent), then runs the interactive installer. It attaches /dev/tty to the setup prompts when available. In CI or another context with no terminal, it refuses to start the interactive prompts and prints the checkout alternative; use the non-interactive command below instead. It installs Python dependencies and Playwright Chromium, writes .env, and optionally logs in.

If you prefer a checkout:

git clone https://github.com/HenryGetz/unw-planner.git
cd unw-planner
./install.sh          # or: sh install.sh

After setup (from the project folder; Windows uses .venv\\Scripts\\unwplanner.exe):

.venv/bin/unwplanner advise --term 202620

advise prints a shareable advising brief: remaining requirements, candidate sections, a conflict-free draft and (with --roadmap) the multi-term plan. It is name-free by default; --no-pii is accepted for scripts that want to be explicit about it.

Non-interactive install (never logs in): pipe the password and provide the other credentials; --no-login still writes .env:

printf '%s\n' "$UNW_PASS" | .venv/bin/unwplanner setup --yes --no-login \
  --password-stdin --user "$UNW_USER" --totp-secret "$UNW_TOTP_SECRET"

CLI

The current commands are listed below. Read-only planning commands beyond this set (profile, remaining requirements, candidates, ratings, conflicts, draft, roadmap, offering matrix) are available through the MCP server; see docs/mcp.md. Every command reads the session written by login; set UNW_AUTO_LOGIN=0 to guarantee no command starts a browser login.

command

what it does

login [--force]

Run the Entra ID + TOTP browser login and save the session state.

terms

List Banner term codes and descriptions.

search [--subject S] [--course N] [--term CODE] [--limit N]

Search one term's sections.

plans [--term CODE]

List Banner Plan Ahead worksheets for one term (term-scoped).

audit [--unmet] [--json]

Print the degree-audit groups, or all outstanding groups.

advise [--term CODE] [--out FILE] [--roadmap] [--max-credits N] [--json] [--no-pii] [--no-auto-login]

Build the one-shot advising brief from live data.

setup [--yes] [--user EMAIL] [--totp-secret SECRET] [--password-stdin] [--no-login] [--register-mcp] [--force] [--root PATH]

First-run wizard: install dependencies, write .env (mode 600), run doctor checks, optionally log in and register MCP.

windows [--term CODE] [--json]

Show registration time-ticket windows parsed from Banner's eligibility text (read-only).

watch --term CODE [--interval N] [--json] [--until-open]

Poll one term's eligibility and print changes (read-only).

snipe --term CODE [--plan NAME] [--crn CRN ...] [--mode window|seats] [--offset N] [--interval N] [--live] [--dry-run/--no-dry-run] [--i-understand] [--allow-waitlist] [--probe] [--max-attempts N] [--timeout N]

Targeted registration run. Dry-run by default; --live additionally requires --i-understand and typing the exact term; it never drops/withdraws. Banner is two-phase: it stages each CRN with Add, then clicks Save once; it never reports registered without the Save. Unverified against a live window.

plan-create NAME [--term CODE] [--yes]

Create a plan worksheet. Writes to Banner.

plan-add PLAN_ID --crn CRN [--crn CRN ...] [--term CODE] [--yes] [--force]

Add one or more sections to a plan. Writes to Banner.

plan-delete PLAN_ID [--term CODE] [--yes] [--force]

Delete a plan; without --force a TTY asks for the exact name. Writes to Banner.

plans-json [--term CODE]

Legacy raw plan dump; prefer plans or the MCP plan tools.

MCP server

unw-mcp runs a stdio MCP server with 20 tools: session health, profile, audit, remaining requirements, terms, search, course detail, candidates, RMP ratings, conflicts, draft (with pins, minimum load and variable-credit hours), roadmap, offering matrix, term-scoped plan operations, a read-only registration preview and a read-only registration time-ticket window.

Plan writes are protected by enforced confirmation, not prose. The four write tools require confirm: true; delete additionally requires confirm_name equal to the plan's exact name. A call without them returns refused: true, the intended effect and the current plan state, and performs no write. Successful writes return a before/after diff read back from Banner.

Register it as a stdio server with an absolute path to <repo>/.venv/bin/unw-mcp (<repo>\\.venv\\Scripts\\unw-mcp.exe on Windows). The shape is the same for omp, Claude Desktop and Cursor:

{
  "mcpServers": {
    "unw-planner": {
      "command": "/ABSOLUTE/PATH/TO/unw-planner/.venv/bin/unw-mcp",
      "args": [],
      "env": {"PYTHONUNBUFFERED": "1"}
    }
  }
}

Config locations: ~/.omp/agent/mcp.json (omp), the Claude Desktop claude_desktop_config.json, and ~/.cursor/mcp.json (Cursor). unwplanner setup --register-mcp merges this entry into the clients it finds; it preserves other servers and backs up before writing. Restart the client after registering.

See docs/mcp.md for the tool table, real request/response examples, error contract and session-recovery procedure.

What is verified vs unverified

area

status

Entra ID + TOTP login, cookie replay, re-login

Verified live

Term list, class search, section detail, term snapshots

Verified live (seven terms)

Degree Works audit retrieval/parsing, profile, unmet requirements

Verified live

Rate My Professors search and name matching (school 706)

Verified live; coverage varies by professor

Conflict checking, draft, roadmap, offering matrix

Offline tests plus live snapshots

Banner plan list/read and plan lifecycle

Verified live earlier on a throwaway plan

MCP plan-write confirmation/refusal and add diff

Verified with a mocked Banner transport; not re-run against live plans

Registration add/drop/register/withdraw

Unverified; deliberately not implemented

Safety model

  • Credentials stay local. UNW_USER, UNW_PASS and UNW_TOTP_SECRET live only in .env (mode 600, gitignored). No code hardcodes them and no command prints them.

  • The session state is a secret. .state/unw_state.json is a replayable cookie jar for Banner, Entra ID and Degree Works. It is gitignored and must not be copied into tickets, chat or commits. Overwrite it with unwplanner login --force if exposed.

  • No registration writes by default. The MCP server never registers; only the explicit unwplanner snipe command can submit, and only with --live plus --i-understand plus typing the exact term code on stdin. It is browser-driven, dry-run by default, never drops/withdraws, and has not been validated against a live registration window. windows/watch and the MCP registration preview are read-only.

  • Plan writes are explicit. CLI plan writes require --yes (and an exact typed plan name for deletes); MCP writes require confirm=true and, for delete, the exact confirm_name. They never run during read commands and never touch anything except Plan Ahead worksheets.

  • Captures are never committed. .state/, captures/, .venv/ and caches are gitignored. Capture files may contain course data, screenshots or redacted request logs; keep them local.

  • Be polite. The client reuses one session and caches term snapshots for 15 minutes. Do not run a second browser against the same session while an HTTP call is in flight; Banner invalidates the old session.

Known limitations

  • Registration is browser-driven and unverified. The direct add/drop endpoints were never observed, so snipe drives the UI. It defaults to a dry run, requires explicit live authorization, and has not been executed against an eligible term. The MCP server has no registration write tool.

  • Filtering is client-side. Banner ignores txt_attribute, txt_instructor, chk_open_only and similar parameters on this deployment, so the server fetches a whole-term snapshot (cached for 15 minutes) and filters locally. Seat counts are a snapshot, not live guarantees.

  • Plans are term-scoped and bounded. Plan Ahead only works for selectable terms; terms beyond Summer 2027 are visible in the catalog but cannot be planned. An empty plan list means "no plans for this term".

  • RMP coverage is thin. Many faculty have no Rate My Professors record or very few ratings. Unrated means unknown, not bad; n<3 is weak evidence.

  • Offering patterns are inferred. "Fall-only" or "every-term" comes from seven observed catalog terms, not a registrar guarantee.

  • Prerequisite parsing is heuristic. Banner's nested and/or prerequisite text and grade/test clauses are parsed approximately; confirm with an advisor.

  • One student per checkout. Settings and state are process-wide, not multi-user.

Porting

The tenant, Banner host, Degree Works host/screen code, RMP school id, term-code convention, attribute vocabulary and login selectors are UNW-specific. docs/porting.md lists every constant/function to change and the tests to add. Contribution rules, including the fixture-scrubbing procedure, are in CONTRIBUTING.md.

Repository layout

path

contents

src/unw_planner/

Library: auth, audit/, banner/, catalog, planner, schedule, rmp/, report, cli, mcp_server.

docs/

Login, Banner API, audit, planning, RMP, MCP, porting and an index.

scripts/

Login, probe, draft, advising artifacts, RMP probe and MCP smoke test.

tests/

Offline pytest suite, hermetic MCP stdio test and scrubbed fixtures.

.github/workflows/ci.yml

Python 3.11/3.12 lint, tests and the hermetic MCP smoke.

pyproject.toml

Package metadata, dependencies, Ruff, coverage, pytest and entry points.

Disclaimer

This is unofficial software. Use it only with your own account, at your own risk. Course data, audit results and instructor ratings can be wrong or stale; verify everything with your advisor and the official UNW systems before making enrollment decisions. Respect the terms of service and rate limits of UNW, Ellucian and Rate My Professors. The project is licensed under the MIT License (see LICENSE).

Available Tools

20 tools
unw_auditB

Structured degree-audit requirement tree (groups -> blocks -> course options, with applied/in-progress/transfer flags). group filters by a case-insensitive substring of a group title/kind or block label.

Inputs: group.

unw_banner_plan_addA

Add a section (crn) or a course (subject+number) to a Banner plan (writes to Banner).

Confirmation is ENFORCED: confirm is required and must be true, otherwise the tool names the exact section/course and current plan state and performs no write. plan is an id or exact name; ambiguous names are rejected with candidates. Successful writes return a before/after diff read back from Banner.

Inputs: plan (required), term (required), confirm (required), crn, +2 more.

unw_banner_plan_createA

Create a named Banner Plan Ahead worksheet for term (writes to Banner).

Confirmation is ENFORCED, not advisory: confirm is required and must be true, otherwise the tool returns the intended effect and the current plan count without writing. Verify the effect with the user first. The student may hold at most five plans per term; names cannot be renamed later.

Inputs: name (required), term (required), confirm (required).

unw_banner_plan_deleteA

Delete an entire Banner plan (writes to Banner, irreversible).

Confirmation is ENFORCED twice: confirm must be true AND confirm_name must equal the plan's exact name. Otherwise the tool reports the intended deletion and current plan state and performs no write. Successful deletion returns the removed plan, remaining plan count and a before/after diff read back from Banner.

Inputs: plan (required), term (required), confirm (required), confirm_name (required).

unw_banner_plan_removeA

Remove one course row from a Banner plan (writes to Banner).

Confirmation is ENFORCED: confirm is required and must be true, otherwise the tool names the exact row and projected credits and performs no write. Identify the row by crn or subject+number; plan is an id or exact name. Successful writes return a before/after diff read back from Banner.

Inputs: plan (required), term (required), confirm (required), crn, +2 more.

unw_banner_plansA

List the student's Banner Plan Ahead worksheets for one term. Plans are TERM-SCOPED: this list only contains plans whose planHeader.term equals term; an empty list means "no plans for THIS term", not "no plans exist" (check other terms, and remember the session term must be bound).

Inputs: term (required).

unw_candidatesA

Resolve unmet requirements to sections offered in term (wraps the catalog resolver). requirement matches an id, title, parent group or target; omit it for every unmet requirement. Every empty result carries the reason (never offered / all closed / already completed). limit caps candidates per requirement (0=all); compact omits repeated section detail. One requirement can be large, so prefer compact/limit for broad queries.

Inputs: term (required), limit, compact, refresh, +2 more.

unw_conflictsB

Pairwise time conflicts for a CRN set, plus the ASCII week grid and the list of unscheduled (TBA/asynchronous) sections. Half-open intervals; linked lecture/lab pairs are exempt.

Inputs: crns (required), term (required), refresh.

unw_courseB

Full detail for one CRN: meetings, faculty with RMP evidence, attributes, catalog description, prerequisite/corequisite/restriction text plus the parsed prerequisite codes, linked lecture/lab CRNs, enrollment text and availability.

Inputs: crn (required), term (required), refresh.

unw_offering_matrixA

Offering pattern per course/attribute across every observed term: every-term, fall-only, spring-only, summer-only or never observed, with section counts, open counts and instructors per term. Accepts "CYS 4245", "ATTRIBUTE NATS" or "DAL *". Omit courses for every unmet requirement target. Use this before proposing a course for a future term.

Inputs: courses, refresh.

unw_plan_draftA

Draft a conflict-free term plan from the audit, prioritizing major-required courses, then major pools, then gen-ed by RMP rating.

required_crns/required_courses pin sections the student insists on; each pin reports placed/unavailable/conflict/prereq_blocked/excluded/budget and a failed pin makes ok=false. exclude_crns/exclude_courses remove candidates. min_credits keeps filling elective pools until the load is met (and reports min_credits_met). hours maps a CRN to the credit hours to reserve for a variable-credit section (default: the low end). Pins apply to this single-term draft only, not to unw_roadmap. include_summer=True also drafts the following summer for requirements that did not fit.

Inputs: term (required), hours, latest, profile, +12 more.

unw_profileB

Student profile from Degree Works: name, id, classification, degree/school, catalog year(s), majors, minors, programs, college, advisors, credits earned and in progress, GPA, percent complete, anticipated graduation, academic standing and holds. Holds are not exposed by the mapped APIs: null = unknown.

Inputs: no named parameters.

unw_ratingsA

Batch Rate My Professors lookup. Accepts "Last, First" or "First Last". Unrated professors return rating=null (never 0.0); weak_evidence flags n<3. Cache entries expire after 7 days; refresh=True refetches now. Transient RMP failures are never cached.

Inputs: names (required), refresh.

unw_register_previewA

DRY RUN ONLY: never posts add/drop/register. Returns Banner's real term eligibility flags and failure text for term, then local checks for the requested CRNs: seats/availability, time conflicts and prerequisites (prereq_ok is null when unverified). If Banner reports the term as ineligible, treat its failure text as the answer; never attempt real registration.

Inputs: crns (required), term (required), refresh.

unw_registration_windowA

READ-ONLY registration time-ticket window(s). With term, returns that term's window; without it, returns the configured current term and the next selectable term. Each row carries Banner's verbatim eligibility failure text, parse_status (parsed/no_ticket/not_eligible/unknown), America/Chicago start/end, seconds until opening and a countdown. It NEVER registers, adds, drops or withdraws; parse_status=unknown must be reported from raw_text.

Inputs: term.

unw_remainingA

Unmet requirements only: id, title, parent group, kind, credits remaining, pick-N progress, course/attribute/wildcard options, status, priority class and shared_title_with (other ids with the same title/options: double-counting risk). This is the authoritative "what is left" list.

Inputs: no named parameters.

unw_roadmapA

Multi-term plan starting at start_term (default: the start term plus three non-summer terms; summer only when include_summer). Carries requirements and prerequisites forward, reports terms with no published catalog, and returns graduation-risk notes against the audit's anticipated graduation term. Course pins, minimum-load and instructor exclusions are draft-only: use unw_plan_draft for those.

Inputs: start_term (required), terms, refresh, max_credits, +1 more.

unw_sessionA

Check the UNW session: authenticated (live check), degraded (state exists but the live check failed), state-file age, cookie count and earliest cookie expiry (values are never exposed), plus read_hint/write_hint and the exact packaged CLI command that repairs an expired session. Call this before any other tool. If unauthenticated but degraded, try one read tool before asking for a login.

Inputs: no named parameters.

unw_termsA

Banner term codes with descriptions plus is_next, is_current, view_only and registration_eligible flags. Eligibility is probed through Banner's own mode=registration response (cached per process); refresh=True re-probes it. Failures are included verbatim.

Inputs: refresh.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.6/5.0

    Scored across 20 tools

    Disambiguation4/5

    Most tools target a clearly distinct resource or action, and the descriptions explicitly draw boundaries where overlap exists (unw_audit vs unw_remaining is 'authoritative what-is-left', unw_search vs unw_candidates is 'wraps the resolver', unw_plan_draft vs unw_roadmap is 'single-term with pins vs multi-term'). The remaining soft overlaps — unw_register_preview re-checking seats/conflicts/prereqs that unw_search and unw_conflicts already surface, and unw_banner_plans vs the local draft tools — are workable but could still cause misselection.

    Naming Consistency4/5

    Nearly all names share the unw_ prefix and snake_case noun-oriented pattern, with the five banner_plan tools forming an internally consistent verb-suffixed cluster. Minor deviations: unw_search is the lone bare verb, unw_plan_draft uses a noun+noun order unlike its sibling unw_roadmap, and unw_register_preview vs unw_registration_window use different stems for the same domain.

    Tool Count4/5

    Twenty tools is slightly above the comfortable range, but the surface spans several genuinely separate concerns (auth, audit, catalog search, RMP, drafting, Banner worksheet CRUD, registration checks), and each tool maps to a real operation. The only marginal additions are the near-paired audit/remaining and search/candidates tools.

    Completeness4/5

    The lifecycle is well covered: state check, profile, audit and unmet requirements, term catalog and course detail, candidate resolution, ratings, conflict checking, single- and multi-term planning, Banner worksheet create/add/remove/delete, and registration eligibility previews. The notable gap is that nothing ever performs a real add/drop/register or waitlist action — a deliberate safety boundary, but it leaves an agent at a dead end for the final registration step, and there is no schedule export.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      B
      maintenance
      Enables read-only inspection of Google Ads accounts through MCP, including account inventory, reporting, metadata, change history, and safe GAQL queries.
      Apache 2.0
    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables warehouse operations agents to inspect tasks, deliveries, stock, and KPIs, and to attempt write operations in shadow mode with human approval and segregation of duties enforced as code.
      MIT
    • F
      license
      A
      quality
      C
      maintenance
      Enables read-only interaction with CMU Student Information Online data, including student context, schedules, grades, enrollment, account activity, and campus life through MCP tools.
      8
      -