$npx -y skills add vishalsachdev/canvas-mcp --skill canvas-bulk-gradingBulk grading workflows for Canvas LMS assignments using rubrics. Covers single grading, batch grading, and code execution strategies with safety-first dry runs.
| 1 | # Canvas Bulk Grading |
| 2 | |
| 3 | Grade Canvas LMS assignments efficiently using rubric-based workflows. This skill requires the Canvas MCP server to be running and authenticated with an instructor or TA token. |
| 4 | |
| 5 | ## Prerequisites |
| 6 | |
| 7 | - Canvas MCP server running and connected |
| 8 | - Authenticated with an **educator** (instructor/TA) Canvas API token |
| 9 | - Assignment must exist and have submissions to grade |
| 10 | - Rubric must already be created in Canvas and associated with the assignment (Canvas API cannot reliably create rubrics -- use the Canvas web UI for that) |
| 11 | |
| 12 | ## Workflow |
| 13 | |
| 14 | ### Step 1: Gather Assignment and Rubric Information |
| 15 | |
| 16 | Before grading, retrieve the assignment details and its rubric criteria. |
| 17 | |
| 18 | ``` |
| 19 | get_assignment_details(course_identifier, assignment_id) |
| 20 | ``` |
| 21 | |
| 22 | Then get the rubric. Use `get_assignment_rubric_details` if the rubric is already linked to the assignment, or `list_all_rubrics` to browse all rubrics in the course: |
| 23 | |
| 24 | ``` |
| 25 | get_assignment_rubric_details(course_identifier, assignment_id) |
| 26 | list_all_rubrics(course_identifier) |
| 27 | get_rubric_details(course_identifier, rubric_id) |
| 28 | ``` |
| 29 | |
| 30 | Record the **criterion IDs** (often prefixed with underscore, e.g., `_8027`) and **rating IDs** from the rubric response. These are required for rubric-based grading. |
| 31 | |
| 32 | ### Step 2: List Submissions |
| 33 | |
| 34 | Retrieve all student submissions to determine how many need grading: |
| 35 | |
| 36 | ``` |
| 37 | list_submissions(course_identifier, assignment_id) |
| 38 | ``` |
| 39 | |
| 40 | Note the `user_id` for each submission and the `workflow_state` (submitted, graded, pending_review). Count the submissions that need grading to determine which strategy to use. |
| 41 | |
| 42 | ### Step 3: Choose a Grading Strategy |
| 43 | |
| 44 | Use this decision tree based on the number of submissions to grade: |
| 45 | |
| 46 | ``` |
| 47 | How many submissions need grading? |
| 48 | | |
| 49 | +-- 1-9 submissions |
| 50 | | Use grade_with_rubric (one call per submission) |
| 51 | | |
| 52 | +-- 10-29 submissions |
| 53 | | Use bulk_grade_submissions (concurrent batch processing) |
| 54 | | Set max_concurrent: 5, rate_limit_delay: 1.0 |
| 55 | | ALWAYS run with dry_run: true first |
| 56 | | |
| 57 | +-- 30+ submissions OR custom grading logic needed |
| 58 | Use execute_typescript with bulkGrade function |
| 59 | 99.7% token savings -- grading logic runs locally |
| 60 | ALWAYS run with dry_run: true first |
| 61 | ``` |
| 62 | |
| 63 | ### Strategy A: Single Grading (1-9 submissions) |
| 64 | |
| 65 | Call `grade_with_rubric` once per student: |
| 66 | |
| 67 | ``` |
| 68 | grade_with_rubric( |
| 69 | course_identifier, |
| 70 | assignment_id, |
| 71 | user_id, |
| 72 | rubric_assessment: { |
| 73 | "criterion_id": { |
| 74 | "points": <number>, |
| 75 | "rating_id": "<string>", // optional |
| 76 | "comments": "<string>" // optional per-criterion feedback |
| 77 | } |
| 78 | }, |
| 79 | comment: "Overall feedback" // optional |
| 80 | ) |
| 81 | ``` |
| 82 | |
| 83 | ### Strategy B: Bulk Grading (10-29 submissions) |
| 84 | |
| 85 | **Always dry run first.** Build the grades dictionary mapping each user ID to their grade data, then validate before submitting: |
| 86 | |
| 87 | ``` |
| 88 | bulk_grade_submissions( |
| 89 | course_identifier, |
| 90 | assignment_id, |
| 91 | grades: { |
| 92 | "user_id_1": { |
| 93 | "rubric_assessment": { |
| 94 | "criterion_id": {"points": 85, "comments": "Good analysis"} |
| 95 | }, |
| 96 | "comment": "Overall feedback" |
| 97 | }, |
| 98 | "user_id_2": { |
| 99 | "grade": 92, |
| 100 | "comment": "Excellent work" |
| 101 | } |
| 102 | }, |
| 103 | dry_run: true, // VALIDATE FIRST |
| 104 | max_concurrent: 5, |
| 105 | rate_limit_delay: 1.0 |
| 106 | ) |
| 107 | ``` |
| 108 | |
| 109 | Review the dry run output. If everything looks correct, re-run with `dry_run: false`. |
| 110 | |
| 111 | ### Strategy C: Code Execution (30+ submissions) |
| 112 | |
| 113 | For large classes or custom grading logic, use `execute_typescript` to run grading locally. This avoids loading all submission data into the conversation context. |
| 114 | |
| 115 | ``` |
| 116 | execute_typescript(code: ` |
| 117 | import { bulkGrade } from './canvas/grading/bulkGrade.js'; |
| 118 | |
| 119 | await bulkGrade({ |
| 120 | courseIdentifier: "COURSE_ID", |
| 121 | assignmentId: "ASSIGNMENT_ID", |
| 122 | gradingFunction: (submission) => { |
| 123 | // Custom grading logic runs locally -- no token cost |
| 124 | const notebook = submission.attachments?.find( |
| 125 | f => f.filename.endsWith('.ipynb') |
| 126 | ); |
| 127 | |
| 128 | if (!notebook) return null; // skip ungraded |
| 129 | |
| 130 | return { |
| 131 | points: 100, |
| 132 | rubricAssessment: { "_8027": { points: 100 } }, |
| 133 | comment: "Graded via automated review" |
| 134 | }; |
| 135 | } |
| 136 | }); |
| 137 | `) |
| 138 | ``` |
| 139 | |
| 140 | Use `search_canvas_tools("grading", "signatures")` to discover available TypeScript modules and their function signatures before writing code. |
| 141 | |
| 142 | ## Token Efficiency |
| 143 | |
| 144 | The three strategies have very different token costs: |
| 145 | |
| 146 | | Strategy | When | Token Cost | Why | |
| 147 | |----------|------|------------|-----| |
| 148 | | `grade_with_rubric` | 1-9 submissions | Low | Few round-trips, small payloads | |
| 149 | | `bulk_grade_submissions` | 10-29 submissions | Medium | One call with batch data | |
| 150 | | `execute_typescript` | 30+ submissions | Minimal | Grading logic runs locally; only the code string is sent. **99.7% savings** vs loading all submissions into context | |
| 151 | |
| 152 | The key insight: as submission count grows, s |