Canvas MCP Server

by DMontgomery40
Verified
  • src
// src/client.ts import axios, { AxiosInstance } from 'axios'; import { CanvasCourse, CanvasAssignment, CanvasSubmission, CanvasUser, CanvasEnrollment, CreateCourseArgs, UpdateCourseArgs, CreateAssignmentArgs, UpdateAssignmentArgs, SubmitGradeArgs, EnrollUserArgs, CanvasAPIError, CanvasDiscussionTopic, CanvasModule, CanvasModuleItem, CanvasQuiz, CanvasAnnouncement, CanvasUserProfile, CanvasScope, CanvasAssignmentSubmission } from './types.js'; export class CanvasClient { private client: AxiosInstance; private baseURL: string; constructor(token: string, domain: string) { this.baseURL = `https://${domain}/api/v1`; this.client = axios.create({ baseURL: this.baseURL, headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); // Add response interceptor for pagination this.client.interceptors.response.use(async (response) => { const { headers, data } = response; const linkHeader = headers.link; // If this is a paginated response, fetch all pages if (Array.isArray(data) && linkHeader) { let allData = [...data]; let nextUrl = this.getNextPageUrl(linkHeader); while (nextUrl) { const nextResponse = await this.client.get(nextUrl); allData = [...allData, ...nextResponse.data]; nextUrl = this.getNextPageUrl(nextResponse.headers.link); } response.data = allData; } return response; }); // Add error interceptor this.client.interceptors.response.use(undefined, (error) => { if (error.response) { const { status, data } = error.response; throw new CanvasAPIError(`Canvas API Error (${status}): ${data.message || JSON.stringify(data)}`, status, data); } throw error; }); } private getNextPageUrl(linkHeader: string): string | null { const links = linkHeader.split(','); const nextLink = links.find(link => link.includes('rel="next"')); if (!nextLink) return null; const match = nextLink.match(/<(.+?)>/); return match ? match[1] : null; } // --------------------- // COURSES // --------------------- async listCourses(): Promise<CanvasCourse[]> { const response = await this.client.get('/courses', { params: { include: ['total_students', 'teachers'] } }); return response.data; } async getCourse(courseId: number): Promise<CanvasCourse> { const response = await this.client.get(`/courses/${courseId}`, { params: { include: ['total_students', 'teachers'] } }); return response.data; } async createCourse(args: CreateCourseArgs): Promise<CanvasCourse> { const response = await this.client.post('/courses', { course: args }); return response.data; } async updateCourse(args: UpdateCourseArgs): Promise<CanvasCourse> { const { course_id, ...courseData } = args; const response = await this.client.put(`/courses/${course_id}`, { course: courseData }); return response.data; } async deleteCourse(courseId: number): Promise<void> { await this.client.delete(`/courses/${courseId}`); } // --------------------- // ASSIGNMENTS // --------------------- async listAssignments(courseId: number): Promise<CanvasAssignment[]> { const response = await this.client.get(`/courses/${courseId}/assignments`); return response.data; } async getAssignment(courseId: number, assignmentId: number): Promise<CanvasAssignment> { const response = await this.client.get(`/courses/${courseId}/assignments/${assignmentId}`); return response.data; } async createAssignment(args: CreateAssignmentArgs): Promise<CanvasAssignment> { const { course_id, ...assignmentData } = args; const response = await this.client.post(`/courses/${course_id}/assignments`, { assignment: assignmentData }); return response.data; } async updateAssignment(args: UpdateAssignmentArgs): Promise<CanvasAssignment> { const { course_id, assignment_id, ...assignmentData } = args; const response = await this.client.put( `/courses/${course_id}/assignments/${assignment_id}`, { assignment: assignmentData } ); return response.data; } async deleteAssignment(courseId: number, assignmentId: number): Promise<void> { await this.client.delete(`/courses/${courseId}/assignments/${assignmentId}`); } // --------------------- // SUBMISSIONS // --------------------- async getSubmissions(courseId: number, assignmentId: number): Promise<CanvasSubmission[]> { const response = await this.client.get( `/courses/${courseId}/assignments/${assignmentId}/submissions` ); return response.data; } async getSubmission(courseId: number, assignmentId: number, userId: number): Promise<CanvasSubmission> { const response = await this.client.get( `/courses/${courseId}/assignments/${assignmentId}/submissions/${userId}` ); return response.data; } async submitGrade(args: SubmitGradeArgs): Promise<CanvasSubmission> { const { course_id, assignment_id, user_id, grade, comment } = args; const response = await this.client.put( `/courses/${course_id}/assignments/${assignment_id}/submissions/${user_id}`, { submission: { posted_grade: grade, comment: { text_comment: comment } } }); return response.data; } // --------------------- // USERS AND ENROLLMENTS // --------------------- async listUsers(courseId: number): Promise<CanvasUser[]> { const response = await this.client.get(`/courses/${courseId}/users`, { params: { include: ['email', 'enrollments'] } }); return response.data; } async getEnrollments(courseId: number): Promise<CanvasEnrollment[]> { const response = await this.client.get(`/courses/${courseId}/enrollments`); return response.data; } async enrollUser(args: EnrollUserArgs): Promise<CanvasEnrollment> { const { course_id, user_id, role = 'StudentEnrollment', enrollment_state = 'active' } = args; const response = await this.client.post(`/courses/${course_id}/enrollments`, { enrollment: { user_id, type: role, enrollment_state } }); return response.data; } async unenrollUser(courseId: number, enrollmentId: number): Promise<void> { await this.client.delete(`/courses/${courseId}/enrollments/${enrollmentId}`); } // --------------------- // GRADES // --------------------- async getCourseGrades(courseId: number): Promise<CanvasEnrollment[]> { const response = await this.client.get(`/courses/${courseId}/enrollments`, { params: { include: ['grades'] } }); return response.data; } // --------------------- // USER PROFILE (STUDENT ACCESSIBLE ENDPOINT) // --------------------- async getUserProfile(): Promise<CanvasUserProfile> { // Fetch the profile of the currently authenticated user const response = await this.client.get('/users/self/profile'); return response.data; } // --------------------- // STUDENT COURSES // --------------------- async listStudentCourses(): Promise<CanvasCourse[]> { // Returns courses visible to the current user (student) const response = await this.client.get('/courses', { params: { include: ['enrollments', 'total_students'] } }); return response.data; } // --------------------- // MODULES // --------------------- async listModules(courseId: number): Promise<CanvasModule[]> { const response = await this.client.get(`/courses/${courseId}/modules`); return response.data; } async getModule(courseId: number, moduleId: number): Promise<CanvasModule> { const response = await this.client.get(`/courses/${courseId}/modules/${moduleId}`); return response.data; } async listModuleItems(courseId: number, moduleId: number): Promise<CanvasModuleItem[]> { const response = await this.client.get(`/courses/${courseId}/modules/${moduleId}/items`); return response.data; } async getModuleItem(courseId: number, moduleId: number, itemId: number): Promise<CanvasModuleItem> { const response = await this.client.get(`/courses/${courseId}/modules/${moduleId}/items/${itemId}`); return response.data; } // --------------------- // DISCUSSION TOPICS (FORUMS) // --------------------- async listDiscussionTopics(courseId: number): Promise<CanvasDiscussionTopic[]> { const response = await this.client.get(`/courses/${courseId}/discussion_topics`); return response.data; } async getDiscussionTopic(courseId: number, topicId: number): Promise<CanvasDiscussionTopic> { const response = await this.client.get(`/courses/${courseId}/discussion_topics/${topicId}`); return response.data; } // --------------------- // ANNOUNCEMENTS // --------------------- async listAnnouncements(courseId: string): Promise<CanvasAnnouncement[]> { const response = await this.client.get(`/courses/${courseId}/discussion_topics`, { params: { type: 'announcement' } }); return response.data; } // --------------------- // QUIZZES // --------------------- async listQuizzes(courseId: string): Promise<CanvasQuiz[]> { const response = await this.client.get(`/courses/${courseId}/quizzes`); return response.data; } async getQuiz(courseId: string, quizId: number): Promise<CanvasQuiz> { const response = await this.client.get(`/courses/${courseId}/quizzes/${quizId}`); return response.data; } async createQuiz(courseId: number, quizData: Partial<CanvasQuiz>): Promise<CanvasQuiz> { const response = await this.client.post(`/courses/${courseId}/quizzes`, { quiz: quizData }); return response.data; } async updateQuiz(courseId: number, quizId: number, quizData: Partial<CanvasQuiz>): Promise<CanvasQuiz> { const response = await this.client.put(`/courses/${courseId}/quizzes/${quizId}`, { quiz: quizData }); return response.data; } async deleteQuiz(courseId: number, quizId: number): Promise<void> { await this.client.delete(`/courses/${courseId}/quizzes/${quizId}`); } // --------------------- // FILES // --------------------- async listFiles(courseId: number): Promise<any[]> { // Replace 'any' with appropriate CanvasFile interface if defined const response = await this.client.get(`/courses/${courseId}/files`); return response.data; } async getFile(fileId: number): Promise<any> { // Replace 'any' with appropriate CanvasFile interface if defined const response = await this.client.get(`/files/${fileId}`); return response.data; } // --------------------- // SCOPES // --------------------- /** * List scopes for a given account. * This endpoint is BETA and may change. * * GET /api/v1/accounts/:account_id/scopes * Optional parameter: group_by (e.g., 'resource_name') */ async listTokenScopes(accountId: number, groupBy?: string): Promise<CanvasScope[]> { const params: Record<string, string> = {}; if (groupBy) { params.group_by = groupBy; } const response = await this.client.get(`/accounts/${accountId}/scopes`, { params }); return response.data; } // --------------------- // ASSIGNMENT SUBMISSIONS // --------------------- async submitAssignment(args: { course_id: string; assignment_id: number; user_id: number; submission_type: string; body?: string; }): Promise<CanvasAssignmentSubmission> { const { course_id, assignment_id, user_id, submission_type, body } = args; const response = await this.client.post( `/courses/${course_id}/assignments/${assignment_id}/submissions/${user_id}`, { submission: { submission_type, body } } ); return response.data; } }