Skip to main content
Glama

Canvas MCP Server

"""Other MCP tools for Canvas API (discussions, announcements, pages, users, analytics).""" from typing import Union, Optional from mcp.server.fastmcp import FastMCP import sys import os sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from core.client import fetch_all_paginated_results, make_canvas_request from core.cache import get_course_id, get_course_code from core.validation import validate_params from core.dates import format_date, truncate_text def register_other_tools(mcp: FastMCP): """Register other MCP tools (pages, users, analytics).""" # ===== PAGE TOOLS ===== @mcp.tool() @validate_params async def list_pages(course_identifier: Union[str, int], sort: Optional[str] = "title", order: Optional[str] = "asc", search_term: Optional[str] = None, published: Optional[bool] = None) -> str: """List pages for a specific course. Args: course_identifier: The Canvas course code (e.g., badm_554_120251_246794) or ID sort: Sort criteria ('title', 'created_at', 'updated_at') order: Sort order ('asc' or 'desc') search_term: Search for pages containing this term in title or body published: Filter by published status (True, False, or None for all) """ course_id = await get_course_id(course_identifier) params = {"per_page": 100} if sort: params["sort"] = sort if order: params["order"] = order if search_term: params["search_term"] = search_term if published is not None: params["published"] = published pages = await fetch_all_paginated_results(f"/courses/{course_id}/pages", params) if isinstance(pages, dict) and "error" in pages: return f"Error fetching pages: {pages['error']}" if not pages: return f"No pages found for course {course_identifier}." pages_info = [] for page in pages: url = page.get("url", "No URL") title = page.get("title", "Untitled page") published_status = "Published" if page.get("published", False) else "Unpublished" is_front_page = page.get("front_page", False) updated_at = format_date(page.get("updated_at")) front_page_indicator = " (Front Page)" if is_front_page else "" pages_info.append( f"URL: {url}\nTitle: {title}{front_page_indicator}\nStatus: {published_status}\nUpdated: {updated_at}\n" ) course_display = await get_course_code(course_id) or course_identifier return f"Pages for Course {course_display}:\n\n" + "\n".join(pages_info) @mcp.tool() @validate_params async def get_page_content(course_identifier: Union[str, int], page_url_or_id: str) -> str: """Get the full content body of a specific page. Args: course_identifier: The Canvas course code (e.g., badm_554_120251_246794) or ID page_url_or_id: The page URL or page ID """ course_id = await get_course_id(course_identifier) response = await make_canvas_request("get", f"/courses/{course_id}/pages/{page_url_or_id}") if "error" in response: return f"Error fetching page content: {response['error']}" title = response.get("title", "Untitled") body = response.get("body", "") published = response.get("published", False) if not body: return f"Page '{title}' has no content." course_display = await get_course_code(course_id) or course_identifier status = "Published" if published else "Unpublished" return f"Page Content for '{title}' in Course {course_display} ({status}):\n\n{body}" @mcp.tool() @validate_params async def get_page_details(course_identifier: Union[str, int], page_url_or_id: str) -> str: """Get detailed information about a specific page. Args: course_identifier: The Canvas course code (e.g., badm_554_120251_246794) or ID page_url_or_id: The page URL or page ID """ course_id = await get_course_id(course_identifier) response = await make_canvas_request("get", f"/courses/{course_id}/pages/{page_url_or_id}") if "error" in response: return f"Error fetching page details: {response['error']}" title = response.get("title", "Untitled") url = response.get("url", "N/A") body = response.get("body", "") created_at = format_date(response.get("created_at")) updated_at = format_date(response.get("updated_at")) published = response.get("published", False) front_page = response.get("front_page", False) locked_for_user = response.get("locked_for_user", False) editing_roles = response.get("editing_roles", "") # Handle last edited by user info last_edited_by = response.get("last_edited_by", {}) editor_name = last_edited_by.get("display_name", "Unknown") if last_edited_by else "Unknown" # Clean up body text for display if body: # Remove HTML tags for cleaner display import re body_clean = re.sub(r'<[^>]+>', '', body) body_clean = body_clean.strip() if len(body_clean) > 500: body_clean = body_clean[:500] + "..." else: body_clean = "No content" status_info = [] if published: status_info.append("Published") else: status_info.append("Unpublished") if front_page: status_info.append("Front Page") if locked_for_user: status_info.append("Locked") course_display = await get_course_code(course_id) or course_identifier result = f"Page Details for Course {course_display}:\n\n" result += f"Title: {title}\n" result += f"URL: {url}\n" result += f"Status: {', '.join(status_info)}\n" result += f"Created: {created_at}\n" result += f"Updated: {updated_at}\n" result += f"Last Edited By: {editor_name}\n" result += f"Editing Roles: {editing_roles or 'Not specified'}\n" result += f"\nContent Preview:\n{body_clean}" return result @mcp.tool() @validate_params async def get_front_page(course_identifier: Union[str, int]) -> str: """Get the front page content for a course. Args: course_identifier: The Canvas course code (e.g., badm_554_120251_246794) or ID """ course_id = await get_course_id(course_identifier) response = await make_canvas_request("get", f"/courses/{course_id}/front_page") if "error" in response: return f"Error fetching front page: {response['error']}" title = response.get("title", "Untitled") body = response.get("body", "") updated_at = format_date(response.get("updated_at")) if not body: return f"Course front page '{title}' has no content." # Try to get the course code for display course_display = await get_course_code(course_id) or course_identifier return f"Front Page '{title}' for Course {course_display} (Updated: {updated_at}):\n\n{body}" @mcp.tool() @validate_params async def create_page(course_identifier: Union[str, int], title: str, body: str, published: bool = True, front_page: bool = False, editing_roles: str = "teachers") -> str: """Create a new page in a Canvas course. Args: course_identifier: The Canvas course code (e.g., badm_554_120251_246794) or ID title: The title of the new page body: The HTML content for the page published: Whether the page should be published (default: True) front_page: Whether this should be the course front page (default: False) editing_roles: Who can edit the page (default: "teachers") """ course_id = await get_course_id(course_identifier) data = { "wiki_page[title]": title, "wiki_page[body]": body, "wiki_page[published]": published, "wiki_page[front_page]": front_page, "wiki_page[editing_roles]": editing_roles } response = await make_canvas_request("post", f"/courses/{course_id}/pages", data=data) if "error" in response: return f"Error creating page: {response['error']}" page_url = response.get("url", "") page_title = response.get("title", title) created_at = format_date(response.get("created_at")) published_status = "Published" if response.get("published", False) else "Unpublished" course_display = await get_course_code(course_id) or course_identifier result = f"Successfully created page in Course {course_display}:\n\n" result += f"Title: {page_title}\n" result += f"URL: {page_url}\n" result += f"Status: {published_status}\n" result += f"Created: {created_at}\n" if front_page: result += f"Set as front page: Yes\n" return result @mcp.tool() @validate_params async def list_module_items(course_identifier: Union[str, int], module_id: Union[str, int], include_content_details: bool = True) -> str: """List items within a specific module, including pages. Args: course_identifier: The Canvas course code (e.g., badm_554_120251_246794) or ID module_id: The module ID include_content_details: Whether to include additional details about content items """ course_id = await get_course_id(course_identifier) params = {"per_page": 100} if include_content_details: params["include[]"] = ["content_details"] items = await fetch_all_paginated_results( f"/courses/{course_id}/modules/{module_id}/items", params ) if isinstance(items, dict) and "error" in items: return f"Error fetching module items: {items['error']}" if not items: return f"No items found in module {module_id}." # Get module details for context module_response = await make_canvas_request( "get", f"/courses/{course_id}/modules/{module_id}" ) module_name = "Unknown Module" if "error" not in module_response: module_name = module_response.get("name", "Unknown Module") course_display = await get_course_code(course_id) or course_identifier result = f"Module Items for '{module_name}' in Course {course_display}:\n\n" for item in items: item_id = item.get("id") title = item.get("title", "Untitled") item_type = item.get("type", "Unknown") content_id = item.get("content_id") url = item.get("url", "") external_url = item.get("external_url", "") published = item.get("published", False) result += f"Item: {title}\n" result += f"Type: {item_type}\n" result += f"ID: {item_id}\n" if content_id: result += f"Content ID: {content_id}\n" if url: result += f"URL: {url}\n" if external_url: result += f"External URL: {external_url}\n" result += f"Published: {'Yes' if published else 'No'}\n\n" return result @mcp.tool() @validate_params async def list_groups(course_identifier: Union[str, int]) -> str: """List all groups and their members for a specific course. Args: course_identifier: The Canvas course code (e.g., badm_554_120251_246794) or ID """ course_id = await get_course_id(course_identifier) # Get all groups in the course groups = await fetch_all_paginated_results( f"/courses/{course_id}/groups", {"per_page": 100} ) if isinstance(groups, dict) and "error" in groups: return f"Error fetching groups: {groups['error']}" if not groups: return f"No groups found for course {course_identifier}." # Format the output course_display = await get_course_code(course_id) or course_identifier output = f"Groups for Course {course_display}:\n\n" for group in groups: group_id = group.get("id") group_name = group.get("name", "Unnamed group") group_category = group.get("group_category_id", "Uncategorized") member_count = group.get("members_count", 0) output += f"Group: {group_name}\n" output += f"ID: {group_id}\n" output += f"Category ID: {group_category}\n" output += f"Member Count: {member_count}\n" # Get members for this group members = await fetch_all_paginated_results( f"/groups/{group_id}/users", {"per_page": 100} ) if isinstance(members, dict) and "error" in members: output += f"Error fetching members: {members['error']}\n" elif not members: output += "No members in this group.\n" else: output += "Members:\n" for member in members: member_id = member.get("id") member_name = member.get("name", "Unnamed user") member_email = member.get("email", "No email") output += f" - {member_name} (ID: {member_id}, Email: {member_email})\n" output += "\n" return output # ===== USER TOOLS ===== @mcp.tool() async def list_users(course_identifier: str) -> str: """List users enrolled in a specific course. Args: course_identifier: The Canvas course code (e.g., badm_554_120251_246794) or ID """ course_id = await get_course_id(course_identifier) params = { "include[]": ["enrollments", "email"], "per_page": 100 } users = await fetch_all_paginated_results(f"/courses/{course_id}/users", params) if isinstance(users, dict) and "error" in users: return f"Error fetching users: {users['error']}" if not users: return f"No users found for course {course_identifier}." users_info = [] for user in users: user_id = user.get("id") name = user.get("name", "Unknown") email = user.get("email", "No email") # Get enrollment info enrollments = user.get("enrollments", []) roles = [enrollment.get("role", "Student") for enrollment in enrollments] role_list = ", ".join(set(roles)) if roles else "Student" users_info.append( f"ID: {user_id}\nName: {name}\nEmail: {email}\nRoles: {role_list}\n" ) course_display = await get_course_code(course_id) or course_identifier return f"Users in Course {course_display}:\n\n" + "\n".join(users_info) # ===== ANALYTICS TOOLS ===== @mcp.tool() async def get_student_analytics(course_identifier: str, current_only: bool = True, include_participation: bool = True, include_assignment_stats: bool = True, include_access_stats: bool = True) -> str: """Get detailed analytics about student activity, participation, and progress in a course. Args: course_identifier: The Canvas course code (e.g., badm_554_120251_246794) or ID current_only: Whether to include only assignments due on or before today include_participation: Whether to include participation data (discussions, submissions) include_assignment_stats: Whether to include assignment completion statistics include_access_stats: Whether to include course access statistics """ course_id = await get_course_id(course_identifier) # Get basic course info course_response = await make_canvas_request("get", f"/courses/{course_id}") if "error" in course_response: return f"Error fetching course: {course_response['error']}" course_name = course_response.get("name", "Unknown Course") # Get students students = await fetch_all_paginated_results( f"/courses/{course_id}/users", {"enrollment_type[]": "student", "per_page": 100} ) if isinstance(students, dict) and "error" in students: return f"Error fetching students: {students['error']}" # Get assignments assignments = await fetch_all_paginated_results( f"/courses/{course_id}/assignments", {"per_page": 100} ) if isinstance(assignments, dict) and "error" in assignments: assignments = [] course_display = await get_course_code(course_id) or course_identifier output = f"Student Analytics for Course {course_display} ({course_name})\n\n" output += f"Total Students: {len(students)}\n" output += f"Total Assignments: {len(assignments)}\n\n" if include_assignment_stats and assignments: # Calculate assignment completion stats published_assignments = [a for a in assignments if a.get("published", False)] total_points = sum(a.get("points_possible", 0) for a in published_assignments) output += f"Published Assignments: {len(published_assignments)}\n" output += f"Total Points Available: {total_points}\n\n" output += "This analytics feature provides basic course statistics.\n" output += "For detailed individual student analytics, use specific assignment analytics tools." return output

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