Skip to main content
Glama

create_rubric

Create grading rubrics in Canvas courses to define assessment criteria and scoring for assignments, enabling consistent evaluation and feedback.

Instructions

Create a new rubric in the specified course.

Args: course_identifier: The Canvas course code (e.g., badm_554_120251_246794) or ID title: The title of the rubric criteria: JSON string or dictionary containing rubric criteria structure free_form_criterion_comments: Allow free-form comments on rubric criteria (default: True) association_id: Optional ID to associate rubric with (assignment, course, etc.) association_type: Type of association (Assignment, Course, Account) (default: Assignment) use_for_grading: Whether to use rubric for grade calculation (default: False) purpose: Purpose of the rubric association (grading, bookmark) (default: grading) Example criteria format (as JSON string or dict): { "1": { "description": "Quality of Work", "points": 25, "long_description": "Detailed description of quality expectations", "ratings": { "1": {"description": "Excellent", "points": 25, "long_description": "Exceeds expectations"}, "2": {"description": "Good", "points": 20, "long_description": "Meets expectations"}, "3": {"description": "Satisfactory", "points": 15, "long_description": "Approaches expectations"}, "4": {"description": "Needs Improvement", "points": 10, "long_description": "Below expectations"} } } } Note: Ratings can be provided as objects (as above) or arrays - both formats are supported.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
course_identifierYes
titleYes
criteriaYes
free_form_criterion_commentsNo
association_idNo
association_typeNoAssignment
use_for_gradingNo
purposeNograding

Implementation Reference

  • The core handler function for the 'create_rubric' tool. Validates input criteria, builds Canvas-compatible rubric structure, creates the rubric via Canvas API, and formats the response.
    @mcp.tool() @validate_params async def create_rubric(course_identifier: str | int, title: str, criteria: str | dict[str, Any], free_form_criterion_comments: bool = True, association_id: str | int | None = None, association_type: str = "Assignment", use_for_grading: bool = False, purpose: str = "grading") -> str: """Create a new rubric in the specified course. Args: course_identifier: The Canvas course code (e.g., badm_554_120251_246794) or ID title: The title of the rubric criteria: JSON string or dictionary containing rubric criteria structure free_form_criterion_comments: Allow free-form comments on rubric criteria (default: True) association_id: Optional ID to associate rubric with (assignment, course, etc.) association_type: Type of association (Assignment, Course, Account) (default: Assignment) use_for_grading: Whether to use rubric for grade calculation (default: False) purpose: Purpose of the rubric association (grading, bookmark) (default: grading) Example criteria format (as JSON string or dict): { "1": { "description": "Quality of Work", "points": 25, "long_description": "Detailed description of quality expectations", "ratings": { "1": {"description": "Excellent", "points": 25, "long_description": "Exceeds expectations"}, "2": {"description": "Good", "points": 20, "long_description": "Meets expectations"}, "3": {"description": "Satisfactory", "points": 15, "long_description": "Approaches expectations"}, "4": {"description": "Needs Improvement", "points": 10, "long_description": "Below expectations"} } } } Note: Ratings can be provided as objects (as above) or arrays - both formats are supported. """ course_id = await get_course_id(course_identifier) # Validate and parse criteria try: # Handle both string and dict input if isinstance(criteria, str): parsed_criteria = validate_rubric_criteria(criteria) elif isinstance(criteria, dict): # If it's already a dict, validate it directly parsed_criteria = criteria # Still run validation to ensure structure is correct (but don't fail if it errors) try: validate_rubric_criteria(json.dumps(criteria)) except ValueError: # If validation fails, continue anyway since we have a dict pass else: return "Error: criteria must be a JSON string or dictionary object" formatted_criteria = build_criteria_structure(parsed_criteria) except ValueError as e: # If validation fails, provide detailed error and suggest a simpler format error_msg = f"Error validating criteria: {str(e)}\n\n" error_msg += "=== DEBUGGING INFORMATION ===\n" error_msg += f"Criteria type: {type(criteria)}\n" if isinstance(criteria, str): error_msg += f"Criteria length: {len(criteria)}\n" error_msg += f"First 200 chars: {repr(criteria[:200])}\n" error_msg += "\n=== SUGGESTED SIMPLE FORMAT ===\n" error_msg += "Try using this simple format:\n" error_msg += '{"1": {"description": "Test Criterion", "points": 5.0, "ratings": [{"description": "Good", "points": 5.0}, {"description": "Poor", "points": 0.0}]}}' return error_msg # Build rubric data rubric_data = { "title": title, "free_form_criterion_comments": free_form_criterion_comments, "criteria": formatted_criteria } # Build request data request_data = { "rubric": rubric_data } # Add association if provided if association_id: request_data["rubric_association"] = { "association_id": str(association_id), "association_type": association_type, "use_for_grading": use_for_grading, "purpose": purpose } # Make the API request response = await make_canvas_request( "post", f"/courses/{course_id}/rubrics", data=request_data ) if "error" in response: return f"Error creating rubric: {response['error']}" # Format and return response course_display = await get_course_code(course_id) or course_identifier formatted_response = format_rubric_response(response) return f"Rubric created in course {course_display}!\n\n{formatted_response}"
  • Key helper function used by create_rubric to validate and parse the criteria input (JSON string or dict), handling various formats, preprocessing, and providing comprehensive validation with user-friendly errors.
    def validate_rubric_criteria(criteria_json: str) -> dict[str, Any]: """Validate and parse rubric criteria JSON structure. Args: criteria_json: JSON string containing rubric criteria Returns: Parsed criteria dictionary Raises: ValueError: If JSON is invalid or structure is incorrect """ # Preprocess the string to handle common issues cleaned_json = preprocess_criteria_string(criteria_json) try: criteria = json.loads(cleaned_json) except json.JSONDecodeError as e: # Try alternative parsing methods if JSON fails try: # Maybe it's a Python literal string representation import ast criteria = ast.literal_eval(cleaned_json) if isinstance(criteria, dict): # Successfully parsed as Python literal, continue with validation pass else: raise ValueError("Parsed result is not a dictionary") except (ValueError, SyntaxError): # Both JSON and literal_eval failed, provide detailed error error_msg = f"Invalid JSON format: {str(e)}\n" error_msg += f"Original string length: {len(criteria_json)}\n" error_msg += f"Cleaned string length: {len(cleaned_json)}\n" error_msg += f"First 200 characters of original: {repr(criteria_json[:200])}\n" error_msg += f"First 200 characters of cleaned: {repr(cleaned_json[:200])}\n" if len(cleaned_json) > 200: error_msg += f"Last 100 characters of cleaned: {repr(cleaned_json[-100:])}" error_msg += "\nAlso failed to parse as Python literal. Please ensure the criteria is valid JSON." raise ValueError(error_msg) from e if not isinstance(criteria, dict): raise ValueError("Criteria must be a JSON object (dictionary)") # Validate each criterion for criterion_key, criterion_data in criteria.items(): if not isinstance(criterion_data, dict): raise ValueError(f"Criterion {criterion_key} must be an object") if "description" not in criterion_data: raise ValueError(f"Criterion {criterion_key} must have a 'description' field") if "points" not in criterion_data: raise ValueError(f"Criterion {criterion_key} must have a 'points' field") try: points = float(criterion_data["points"]) if points < 0: raise ValueError(f"Criterion {criterion_key} points must be non-negative") except (ValueError, TypeError) as err: raise ValueError(f"Criterion {criterion_key} points must be a valid number") from err # Validate ratings if present - handle both object and array formats if "ratings" in criterion_data: ratings = criterion_data["ratings"] # Handle both object and array formats if isinstance(ratings, dict): # Object format: {"1": {...}, "2": {...}} for rating_key, rating_data in ratings.items(): if not isinstance(rating_data, dict): raise ValueError(f"Rating {rating_key} in criterion {criterion_key} must be an object") if "description" not in rating_data: raise ValueError(f"Rating {rating_key} in criterion {criterion_key} must have a 'description' field") if "points" not in rating_data: raise ValueError(f"Rating {rating_key} in criterion {criterion_key} must have a 'points' field") try: rating_points = float(rating_data["points"]) if rating_points < 0: raise ValueError(f"Rating {rating_key} points must be non-negative") except (ValueError, TypeError) as err: raise ValueError(f"Rating {rating_key} points must be a valid number") from err elif isinstance(ratings, list): # Array format: [{"description": ..., "points": ...}, ...] for i, rating_data in enumerate(ratings): if not isinstance(rating_data, dict): raise ValueError(f"Rating {i} in criterion {criterion_key} must be an object") if "description" not in rating_data: raise ValueError(f"Rating {i} in criterion {criterion_key} must have a 'description' field") if "points" not in rating_data: raise ValueError(f"Rating {i} in criterion {criterion_key} must have a 'points' field") try: rating_points = float(rating_data["points"]) if rating_points < 0: raise ValueError(f"Rating {i} points must be non-negative") except (ValueError, TypeError) as err: raise ValueError(f"Rating {i} points must be a valid number") from err else: raise ValueError(f"Criterion {criterion_key} ratings must be an object or array") return criteria
  • Helper that transforms validated criteria into the exact format required by Canvas API for rubric creation, handling ratings conversion and sorting.
    def build_criteria_structure(criteria: dict[str, Any]) -> dict[str, Any]: """Build Canvas API-compatible criteria structure. Args: criteria: Validated criteria dictionary Returns: Canvas API-compatible criteria structure """ # Canvas expects criteria as a flat dictionary with string keys formatted_criteria = {} for criterion_key, criterion_data in criteria.items(): formatted_criteria[str(criterion_key)] = { "description": criterion_data["description"], "points": float(criterion_data["points"]), "long_description": criterion_data.get("long_description", "") } # Handle ratings if present if "ratings" in criterion_data: ratings = criterion_data["ratings"] # Canvas API expects ratings as an array, not object # Convert from object format to array format formatted_ratings = [] # Sort ratings by points (highest to lowest) for consistent ordering if isinstance(ratings, dict): # Convert object-style ratings to array rating_items = [] for _rating_key, rating_data in ratings.items(): rating_items.append({ "description": rating_data["description"], "points": float(rating_data["points"]), "long_description": rating_data.get("long_description", "") }) # Sort by points descending rating_items.sort(key=lambda x: x["points"], reverse=True) formatted_ratings = rating_items elif isinstance(ratings, list): # Already in array format, just ensure proper typing for rating_data in ratings: formatted_ratings.append({ "description": rating_data["description"], "points": float(rating_data["points"]), "long_description": rating_data.get("long_description", "") }) formatted_criteria[str(criterion_key)]["ratings"] = formatted_ratings return formatted_criteria
  • Formats the raw Canvas API response from create_rubric into a human-readable summary.
    def format_rubric_response(response: dict[str, Any]) -> str: """Format Canvas API rubric response into readable text. Args: response: Canvas API response (may be non-standard format) Returns: Formatted string representation of the rubric """ # Handle Canvas API's non-standard response format if "rubric" in response and "rubric_association" in response: rubric = response["rubric"] association = response["rubric_association"] result = "Rubric Created/Updated Successfully!\n\n" result += "Rubric Details:\n" result += f" ID: {rubric.get('id', 'N/A')}\n" result += f" Title: {rubric.get('title', 'Untitled')}\n" result += f" Context: {rubric.get('context_type', 'N/A')} (ID: {rubric.get('context_id', 'N/A')})\n" result += f" Points Possible: {rubric.get('points_possible', 0)}\n" result += f" Reusable: {'Yes' if rubric.get('reusable', False) else 'No'}\n" result += f" Free Form Comments: {'Yes' if rubric.get('free_form_criterion_comments', False) else 'No'}\n" if association: result += "\nAssociation Details:\n" result += f" Associated with: {association.get('association_type', 'N/A')} (ID: {association.get('association_id', 'N/A')})\n" result += f" Used for Grading: {'Yes' if association.get('use_for_grading', False) else 'No'}\n" result += f" Purpose: {association.get('purpose', 'N/A')}\n" # Show criteria count data = rubric.get('data', []) if data: result += f"\nCriteria: {len(data)} criterion defined\n" return result # Handle standard rubric response else: result = "Rubric Operation Completed!\n\n" result += f"ID: {response.get('id', 'N/A')}\n" result += f"Title: {response.get('title', 'Untitled')}\n" result += f"Points Possible: {response.get('points_possible', 0)}\n" return result
  • Call to register_rubric_tools(mcp) within register_all_tools, which defines and registers the create_rubric tool among other rubric tools.
    register_rubric_tools(mcp)
Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/vishalsachdev/canvas-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server