• v0.41.0 13e7ec769d

    joeyr released this 2026-08-24 17:44:56 -05:00 | 82 commits to main since this release

    Canvas C2: the five reads that make the tenant useful. list_assignments,
    get_assignment, get_grades, list_announcements and get_syllabus — one tool to six.

    Every one was designed against a measurement rather than a guess.
    probes/canvas_probe_c2.py ran before any of it was written and moved four decisions.

    The sharpest trap in the phase

    current and final are not two spellings of one number. current scores only what has
    been graded; final treats every ungraded assignment as a zero, so mid-term a student
    doing perfectly well reads as failing. Both are returned because Canvas returns both — but
    the projection is named projected_score_if_nothing_else_is_submitted, and there is
    deliberately no neutral final_score key
    for a model to reach for by mistake.

    Added

    • get_grades is ONE request for every course, not one per course.
    • turned_in reads workflow_state, never the row's existence. Canvas creates a
      submission row for every assignment whether or not anything was submitted, so the obvious
      implementation reports the entire course as turned in. Canvas's own late, missing and
      excused verdicts are carried rather than recomputed — an extension is invisible from
      here and Canvas has already applied it.
    • get_assignment earns its place on RESPONSE SIZE, not on data. Both shapes are 71
      fields; the single fetch adds nothing. The list omits description, the getter carries it.
    • list_announcements spans every course in one request, at half the price of the
      route the documentation leads with.
    • get_syllabus — the only tool that reaches late policies and grade weighting.
      Measured at 13,767 characters of HTML, so it is rendered to text and capped.
    • Pagination follows rel="next" with a cap that reports itself. A cap is a stop, not
      a page size: reaching it means the query is wrong.

    Changed

    • _strip_html moved to mcp_gateway/htmltext.py, the rule timeparse.py was extracted
      under — Canvas is its second caller. Mail behaviour is byte-identical.

    Testing

    All twelve new guards proved by breaking them, each with a plausible implementation
    rather than a nonsense one — "take the first enrolment", "work out late ourselves from
    the dates", "keep sending the params while walking pages".

    1367 tests green.

    Downloads