Skip to main content
Glama

Canvas MCP Server

student_tools.py14.6 kB
"""Student-specific MCP tools for Canvas API. These tools provide student-focused functionality using Canvas API "/self" endpoints to access only the student's own data across their enrolled courses. """ from datetime import datetime, timedelta from typing import Any from mcp.server.fastmcp import FastMCP from ..core.cache import get_course_code, get_course_id from ..core.client import fetch_all_paginated_results, make_canvas_request from ..core.dates import format_date, parse_date from ..core.validation import validate_params def register_student_tools(mcp: FastMCP): """Register student-specific MCP tools.""" @mcp.tool() async def get_my_upcoming_assignments(days: int = 7) -> str: """Get your upcoming assignments across all courses. Args: days: Number of days to look ahead (default: 7) Returns upcoming assignments due within the specified timeframe, sorted by due date, with submission status. """ # Calculate the date range end_date = datetime.now() + timedelta(days=days) end_date_str = end_date.strftime("%Y-%m-%d") # Get upcoming events for the current user events = await fetch_all_paginated_results( "/users/self/upcoming_events", params={"per_page": 100} ) if isinstance(events, dict) and "error" in events: return f"Error fetching upcoming assignments: {events['error']}" if not events: return f"No assignments due in the next {days} days." # Filter to assignments only (not calendar events) assignments = [] for event in events: if event.get("type") == "assignment" or event.get("assignment"): assignment_data = event.get("assignment", event) due_at = assignment_data.get("due_at") if due_at: # Check if within our date range due_date = parse_date(due_at) if due_date and due_date <= end_date: assignments.append(assignment_data) if not assignments: return f"No assignments due in the next {days} days." # Sort by due date assignments.sort(key=lambda x: parse_date(x.get("due_at", "")) or datetime.max) # Format output output_lines = [f"Upcoming Assignments (Next {days} Days):\n"] for assignment in assignments: name = assignment.get("name", "Unnamed Assignment") due_at = format_date(assignment.get("due_at")) course_id = assignment.get("course_id") # Get course name course_display = await get_course_code(course_id) if course_id else "Unknown Course" # Get submission status submission = assignment.get("submission") if submission: submitted = submission.get("submitted_at") is not None status = "✅ Submitted" if submitted else "❌ Not Submitted" else: status = "❌ Not Submitted" output_lines.append( f"• {name}\n" f" Course: {course_display}\n" f" Due: {due_at}\n" f" Status: {status}\n" ) return "\n".join(output_lines) @mcp.tool() @validate_params async def get_my_submission_status(course_identifier: str | int | None = None) -> str: """Get your submission status for assignments. Args: course_identifier: Optional course code or ID to filter by specific course. If not provided, shows all courses. Returns your submission status across assignments, highlighting missing submissions. """ if course_identifier: # Get submissions for specific course course_id = await get_course_id(course_identifier) assignments = await fetch_all_paginated_results( f"/courses/{course_id}/assignments", params={"include[]": ["submission"], "per_page": 100} ) if isinstance(assignments, dict) and "error" in assignments: return f"Error fetching assignments: {assignments['error']}" course_display = await get_course_code(course_id) or course_identifier output_lines = [f"Submission Status for {course_display}:\n"] else: # Get all courses and their assignments courses = await fetch_all_paginated_results( "/courses", params={"enrollment_state": "active", "per_page": 100} ) if isinstance(courses, dict) and "error" in courses: return f"Error fetching courses: {courses['error']}" output_lines = ["Submission Status (All Courses):\n"] all_assignments = [] for course in courses: course_id = course.get("id") course_name = course.get("course_code", course.get("name", "Unknown")) assignments = await fetch_all_paginated_results( f"/courses/{course_id}/assignments", params={"include[]": ["submission"], "per_page": 100} ) if not isinstance(assignments, dict) or "error" not in assignments: for assignment in assignments if isinstance(assignments, list) else []: assignment["_course_name"] = course_name all_assignments.append(assignment) assignments = all_assignments if not assignments: return "No assignments found." # Separate submitted and missing submitted = [] missing = [] for assignment in assignments: submission = assignment.get("submission") is_submitted = submission and submission.get("submitted_at") is not None if is_submitted: submitted.append(assignment) else: # Check if past due due_at = assignment.get("due_at") if due_at: due_date = parse_date(due_at) if due_date and due_date < datetime.now(): missing.append((assignment, "OVERDUE")) else: missing.append((assignment, "NOT SUBMITTED")) else: missing.append((assignment, "NOT SUBMITTED")) # Format output if missing: output_lines.append(f"⚠️ Missing Submissions ({len(missing)}):\n") for assignment, status in missing: name = assignment.get("name", "Unnamed") due_at = format_date(assignment.get("due_at")) if assignment.get("due_at") else "No due date" course_name = assignment.get("_course_name", "") output_lines.append( f"• {name}\n" f" {f'Course: {course_name}' if course_name else ''}\n" f" Due: {due_at}\n" f" Status: {status}\n" ) if submitted: output_lines.append(f"\n✅ Submitted ({len(submitted)}):\n") for assignment in submitted[:10]: # Show first 10 name = assignment.get("name", "Unnamed") submission = assignment.get("submission", {}) submitted_at = format_date(submission.get("submitted_at")) course_name = assignment.get("_course_name", "") output_lines.append( f"• {name}\n" f" {f'Course: {course_name}' if course_name else ''}\n" f" Submitted: {submitted_at}\n" ) return "\n".join(output_lines) @mcp.tool() async def get_my_course_grades() -> str: """Get your current grades across all enrolled courses. Returns your current grade, enrollment status, and recent performance for each active course. """ courses = await fetch_all_paginated_results( "/courses", params={ "enrollment_state": "active", "include[]": ["total_scores", "current_grading_period_scores"], "per_page": 100 } ) if isinstance(courses, dict) and "error" in courses: return f"Error fetching courses: {courses['error']}" if not courses: return "No active course enrollments found." output_lines = ["Your Course Grades:\n"] for course in courses: name = course.get("name", "Unnamed Course") course_code = course.get("course_code", "") # Get enrollment data (grades) enrollments = course.get("enrollments", []) if enrollments: enrollment = enrollments[0] # Student typically has one enrollment per course # Current score current_score = enrollment.get("computed_current_score") final_score = enrollment.get("computed_final_score") current_grade = enrollment.get("computed_current_grade", "N/A") # Format grade info if current_score is not None: grade_info = f"{current_grade} ({current_score:.1f}%)" elif final_score is not None: grade_info = f"{final_score:.1f}%" else: grade_info = "No grade yet" output_lines.append( f"• {course_code}: {name}\n" f" Current Grade: {grade_info}\n" ) else: output_lines.append( f"• {course_code}: {name}\n" f" Current Grade: No enrollment data\n" ) return "\n".join(output_lines) @mcp.tool() async def get_my_todo_items() -> str: """Get your Canvas TODO list. Returns all items in your Canvas TODO list including assignments, quizzes, and discussions that need your attention. """ todos = await fetch_all_paginated_results( "/users/self/todo", params={"per_page": 100} ) if isinstance(todos, dict) and "error" in todos: return f"Error fetching TODO items: {todos['error']}" if not todos: return "Your TODO list is empty! 🎉" output_lines = ["Your TODO List:\n"] for item in todos: item_type = item.get("type", "item") assignment = item.get("assignment", {}) name = assignment.get("name") or item.get("title", "Unnamed item") due_at = format_date(assignment.get("due_at")) if assignment.get("due_at") else "No due date" course_id = item.get("course_id") course_display = await get_course_code(course_id) if course_id else "Unknown Course" output_lines.append( f"• {name}\n" f" Type: {item_type.title()}\n" f" Course: {course_display}\n" f" Due: {due_at}\n" ) return "\n".join(output_lines) @mcp.tool() @validate_params async def get_my_peer_reviews_todo(course_identifier: str | int | None = None) -> str: """Get peer reviews you need to complete. Args: course_identifier: Optional course code or ID to filter by specific course Returns list of peer reviews assigned to you that need completion. """ if course_identifier: course_ids = [await get_course_id(course_identifier)] else: # Get all active courses courses = await fetch_all_paginated_results( "/courses", params={"enrollment_state": "active", "per_page": 100} ) if isinstance(courses, dict) and "error" in courses: return f"Error fetching courses: {courses['error']}" course_ids = [course.get("id") for course in courses if course.get("id")] all_peer_reviews = [] for course_id in course_ids: # Get assignments for this course assignments = await fetch_all_paginated_results( f"/courses/{course_id}/assignments", params={"per_page": 100} ) if isinstance(assignments, dict) and "error" in assignments: continue # Check each assignment for peer reviews for assignment in assignments if isinstance(assignments, list) else []: if assignment.get("peer_reviews"): assignment_id = assignment.get("id") # Get peer reviews for this assignment peer_reviews = await fetch_all_paginated_results( f"/courses/{course_id}/assignments/{assignment_id}/peer_reviews", params={"include[]": ["user"], "per_page": 100} ) if isinstance(peer_reviews, list): # Filter to reviews assigned to current user that are incomplete for review in peer_reviews: # Note: We'd need to filter by current user ID # For now, show all incomplete reviews if review.get("workflow_state") != "completed": review["_course_id"] = course_id review["_assignment_name"] = assignment.get("name") all_peer_reviews.append(review) if not all_peer_reviews: return "You have no pending peer reviews! ✅" output_lines = ["Peer Reviews You Need to Complete:\n"] for review in all_peer_reviews: assignment_name = review.get("_assignment_name", "Unknown Assignment") course_id = review.get("_course_id") course_display = await get_course_code(course_id) if course_id else "Unknown Course" user_id = review.get("user_id") assessor_id = review.get("assessor_id") output_lines.append( f"• {assignment_name}\n" f" Course: {course_display}\n" f" Reviewing: Student {user_id}\n" f" Status: Incomplete\n" ) return "\n".join(output_lines)

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