Canvas MCP - College and High School Courses
by aryankeluskar
Verified
from gradescopeapi.classes.connection import GSConnection
from datetime import datetime
import json
import os
import traceback
import sys
from typing import List, Dict, Optional, Any, Union
from dotenv import load_dotenv
load_dotenv()
"""
Gradescope API integration functions
"""
# Credentials
DEFAULT_EMAIL = os.getenv("GRADESCOPE_EMAIL")
DEFAULT_PASSWORD = os.getenv("GRADESCOPE_PASSWORD")
# Cache storage
cache = {
"connection": None,
"courses": None,
"assignments": {},
"submissions": {},
"users": {},
"extensions": {}
}
def get_connection():
"""Get a GSConnection instance (with caching)"""
if cache["connection"] is None:
try:
connection = GSConnection()
connection.login(DEFAULT_EMAIL, DEFAULT_PASSWORD)
cache["connection"] = connection
except Exception as e:
print(f"Error creating Gradescope connection: {e}")
print(traceback.format_exc())
return None
return cache["connection"]
def _get_gradescope_courses():
"""Get all courses from Gradescope"""
# Check cache first
if cache["courses"] is not None:
return cache["courses"]
try:
connection = get_connection()
if not connection:
return None
courses = connection.account.get_courses()
# Convert courses to serializable format
serializable_courses = {
"student": {
f"Course ID: {course_id}": {
"id": course_id,
"name": course.name,
"full_name": course.full_name,
"semester": course.semester,
"year": course.year,
"num_grades_published": course.num_grades_published,
"num_assignments": course.num_assignments
} for course_id, course in courses["student"].items()
}
}
# Save to cache
cache["courses"] = serializable_courses
# Try to save to file if filesystem is writable
try:
is_writable = os.access(os.getcwd(), os.W_OK)
if is_writable:
with open("gs_courses.json", "w") as f:
json.dump(serializable_courses, f, indent=2)
except Exception as e:
print(f"Warning: Could not save courses to file: {e}")
return serializable_courses
except Exception as e:
print(f"Error in _get_gradescope_courses: {e}")
print(f"Line number: {sys.exc_info()[-1].tb_lineno}")
print(traceback.format_exc())
return None
def get_course_by_name(course_name: str):
"""Get a course from Gradescope by name"""
courses = _get_gradescope_courses()
if not courses:
return None
for course in courses["student"].values():
if course_name.lower() in course["name"].lower() or course_name.lower() in course.get("full_name", "").lower():
return course
return None
def get_course_by_id(course_id: str):
"""Get a course from Gradescope by ID"""
courses = _get_gradescope_courses()
if not courses:
return None
for key, course in courses["student"].items():
if f"Course ID: {course_id}" == key or course.get("id") == course_id:
return course
return None
def get_assignments_for_course(course_id: str):
"""Get all assignments for a course"""
# Check cache first
if course_id in cache["assignments"]:
return cache["assignments"][course_id]
try:
connection = get_connection()
if not connection:
return None
assignments = connection.account.get_assignments(course_id)
# Save to cache
cache["assignments"][course_id] = assignments
return assignments
except Exception as e:
print(f"Error in get_assignments_for_course: {e}")
print(f"Line number: {sys.exc_info()[-1].tb_lineno}")
print(traceback.format_exc())
return None
def get_assignment_by_name(course_id: str, assignment_name: str):
"""Get an assignment by name within a course"""
assignments = get_assignments_for_course(course_id)
if not assignments:
return None
for assignment in assignments:
if assignment_name.lower() in assignment.name.lower():
return assignment
return None
def get_assignment_submissions(course_id: str, assignment_id: str):
"""Get all submissions for an assignment"""
# Check cache first
cache_key = f"{course_id}_{assignment_id}"
if cache_key in cache["submissions"]:
return cache["submissions"][cache_key]
try:
connection = get_connection()
if not connection:
return None
submissions = connection.account.get_assignment_submissions(
course_id=course_id, assignment_id=assignment_id
)
# Save to cache
cache["submissions"][cache_key] = submissions
return submissions
except Exception as e:
print(f"Error in get_assignment_submissions: {e}")
print(f"Line number: {sys.exc_info()[-1].tb_lineno}")
print(traceback.format_exc())
return None
def get_student_submission(course_id: str, assignment_id: str, student_email: str):
"""Get a specific student's submission for an assignment"""
try:
connection = get_connection()
if not connection:
return None
submission = connection.account.get_assignment_submission(
student_email=student_email,
course_id=course_id,
assignment_id=assignment_id
)
return submission
except Exception as e:
print(f"Error in get_student_submission: {e}")
print(f"Line number: {sys.exc_info()[-1].tb_lineno}")
print(traceback.format_exc())
return None
def get_course_users(course_id: str):
"""Get all users for a course"""
# Check cache first
if course_id in cache["users"]:
return cache["users"][course_id]
try:
connection = get_connection()
if not connection:
return None
users = connection.account.get_course_users(course_id)
# Save to cache
cache["users"][course_id] = users
return users
except Exception as e:
print(f"Error in get_course_users: {e}")
print(f"Line number: {sys.exc_info()[-1].tb_lineno}")
print(traceback.format_exc())
return None
def get_assignment_extensions(course_id: str, assignment_id: str):
"""Get all extensions for an assignment"""
# Check cache first
cache_key = f"{course_id}_{assignment_id}"
if cache_key in cache["extensions"]:
return cache["extensions"][cache_key]
try:
connection = get_connection()
if not connection:
return None
from gradescopeapi.classes.extensions import get_extensions
extensions = get_extensions(
session=connection.session,
course_id=course_id,
assignment_id=assignment_id
)
# Save to cache
cache["extensions"][cache_key] = extensions
return extensions
except Exception as e:
print(f"Error in get_assignment_extensions: {e}")
print(f"Line number: {sys.exc_info()[-1].tb_lineno}")
print(traceback.format_exc())
return None
def update_student_extension(
course_id: str,
assignment_id: str,
user_id: str,
release_date: datetime,
due_date: datetime,
late_due_date: datetime
):
"""Update an extension for a student on an assignment"""
try:
connection = get_connection()
if not connection:
return False
from gradescopeapi.classes.extensions import update_student_extension as gs_update_extension
success = gs_update_extension(
session=connection.session,
course_id=course_id,
assignment_id=assignment_id,
user_id=user_id,
release_date=release_date,
due_date=due_date,
late_due_date=late_due_date
)
# Clear the cache for this assignment's extensions
cache_key = f"{course_id}_{assignment_id}"
if cache_key in cache["extensions"]:
del cache["extensions"][cache_key]
return success
except Exception as e:
print(f"Error in update_student_extension: {e}")
print(f"Line number: {sys.exc_info()[-1].tb_lineno}")
print(traceback.format_exc())
return False
def update_assignment_dates(
course_id: str,
assignment_id: str,
release_date: datetime,
due_date: datetime,
late_due_date: datetime
):
"""Update the dates for an assignment"""
try:
connection = get_connection()
if not connection:
return False
from gradescopeapi.classes.assignments import update_assignment_date
success = update_assignment_date(
session=connection.session,
course_id=course_id,
assignment_id=assignment_id,
release_date=release_date,
due_date=due_date,
late_due_date=late_due_date
)
# Clear the cache for this course's assignments
if course_id in cache["assignments"]:
del cache["assignments"][course_id]
return success
except Exception as e:
print(f"Error in update_assignment_dates: {e}")
print(f"Line number: {sys.exc_info()[-1].tb_lineno}")
print(traceback.format_exc())
return False
def analyze_gradescope_query(query: str) -> Dict[str, Any]:
"""
Analyze a natural language query to determine what Gradescope information is being requested
Returns a dictionary with extracted parameters and context
"""
# This is a simple keyword-based analysis
# In a real implementation, this would use a more sophisticated NLP approach
result = {
"type": None,
"course_id": None,
"course_name": None,
"assignment_id": None,
"assignment_name": None,
"student_email": None,
"confidence": 0.0
}
# Check for courses request
if any(keyword in query.lower() for keyword in ["my courses", "list courses", "show courses", "what courses"]):
result["type"] = "get_courses"
result["confidence"] = 0.9
return result
# Check for assignments request
if any(keyword in query.lower() for keyword in ["assignments", "homework", "due dates"]):
result["type"] = "get_assignments"
result["confidence"] = 0.8
# Try to extract course name
courses = _get_gradescope_courses()
if courses:
for course_data in courses["student"].values():
course_name = course_data["name"]
if course_name.lower() in query.lower():
result["course_name"] = course_name
result["course_id"] = course_data["id"]
result["confidence"] = 0.9
break
return result
# Check for submission request
if any(keyword in query.lower() for keyword in ["submission", "submitted", "grade", "feedback", "score"]):
result["type"] = "get_submission"
result["confidence"] = 0.7
# Try to extract course and assignment
courses = _get_gradescope_courses()
if courses:
for course_data in courses["student"].values():
if course_data["name"].lower() in query.lower():
result["course_name"] = course_data["name"]
result["course_id"] = course_data["id"]
# If we found a course, try to find an assignment
assignments = get_assignments_for_course(course_data["id"])
if assignments:
for assignment in assignments:
if assignment.name.lower() in query.lower():
result["assignment_name"] = assignment.name
result["assignment_id"] = assignment.id
result["confidence"] = 0.9
break
break
return result
# Default to unknown query type
return result
# For testing
if __name__ == "__main__":
print("Testing Gradescope API functions")
# Get courses
print("Getting courses...")
courses = _get_gradescope_courses()
print(f"Found {len(courses['student']) if courses else 0} courses")
if courses:
# Get a course by name
course_name = next(iter(courses["student"].values()))["name"]
print(f"Getting course by name: {course_name}")
course = get_course_by_name(course_name)
print(f"Found course: {course}")
if course:
# Get assignments for the course
course_id = course["id"]
print(f"Getting assignments for course ID: {course_id}")
assignments = get_assignments_for_course(course_id)
print(f"Found {len(assignments) if assignments else 0} assignments")
if assignments and len(assignments) > 0:
# Get submissions for an assignment
assignment = assignments[0]
print(f"Getting submissions for assignment: {assignment.name}")
submissions = get_assignment_submissions(course_id, assignment.id)
print(f"Found submissions: {submissions}")