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