Canvas MCP Server
by DMontgomery40
Verified
- build
// src/client.ts
import axios from 'axios';
import { CanvasAPIError } from './types.js';
export class CanvasClient {
constructor(token, domain) {
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;
});
}
getNextPageUrl(linkHeader) {
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() {
const response = await this.client.get('/courses', {
params: {
include: ['total_students', 'teachers']
}
});
return response.data;
}
async getCourse(courseId) {
const response = await this.client.get(`/courses/${courseId}`, {
params: {
include: ['total_students', 'teachers']
}
});
return response.data;
}
async createCourse(args) {
const response = await this.client.post('/courses', {
course: args
});
return response.data;
}
async updateCourse(args) {
const { course_id, ...courseData } = args;
const response = await this.client.put(`/courses/${course_id}`, {
course: courseData
});
return response.data;
}
async deleteCourse(courseId) {
await this.client.delete(`/courses/${courseId}`);
}
// ---------------------
// ASSIGNMENTS
// ---------------------
async listAssignments(courseId) {
const response = await this.client.get(`/courses/${courseId}/assignments`);
return response.data;
}
async getAssignment(courseId, assignmentId) {
const response = await this.client.get(`/courses/${courseId}/assignments/${assignmentId}`);
return response.data;
}
async createAssignment(args) {
const { course_id, ...assignmentData } = args;
const response = await this.client.post(`/courses/${course_id}/assignments`, {
assignment: assignmentData
});
return response.data;
}
async updateAssignment(args) {
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, assignmentId) {
await this.client.delete(`/courses/${courseId}/assignments/${assignmentId}`);
}
// ---------------------
// SUBMISSIONS
// ---------------------
async getSubmissions(courseId, assignmentId) {
const response = await this.client.get(`/courses/${courseId}/assignments/${assignmentId}/submissions`);
return response.data;
}
async getSubmission(courseId, assignmentId, userId) {
const response = await this.client.get(`/courses/${courseId}/assignments/${assignmentId}/submissions/${userId}`);
return response.data;
}
async submitGrade(args) {
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) {
const response = await this.client.get(`/courses/${courseId}/users`, {
params: {
include: ['email', 'enrollments']
}
});
return response.data;
}
async getEnrollments(courseId) {
const response = await this.client.get(`/courses/${courseId}/enrollments`);
return response.data;
}
async enrollUser(args) {
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, enrollmentId) {
await this.client.delete(`/courses/${courseId}/enrollments/${enrollmentId}`);
}
// ---------------------
// GRADES
// ---------------------
async getCourseGrades(courseId) {
const response = await this.client.get(`/courses/${courseId}/enrollments`, {
params: {
include: ['grades']
}
});
return response.data;
}
// ---------------------
// USER PROFILE (STUDENT ACCESSIBLE ENDPOINT)
// ---------------------
async getUserProfile() {
// Fetch the profile of the currently authenticated user
const response = await this.client.get('/users/self/profile');
return response.data;
}
// ---------------------
// STUDENT COURSES
// ---------------------
async listStudentCourses() {
// 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) {
const response = await this.client.get(`/courses/${courseId}/modules`);
return response.data;
}
async getModule(courseId, moduleId) {
const response = await this.client.get(`/courses/${courseId}/modules/${moduleId}`);
return response.data;
}
async listModuleItems(courseId, moduleId) {
const response = await this.client.get(`/courses/${courseId}/modules/${moduleId}/items`);
return response.data;
}
async getModuleItem(courseId, moduleId, itemId) {
const response = await this.client.get(`/courses/${courseId}/modules/${moduleId}/items/${itemId}`);
return response.data;
}
// ---------------------
// DISCUSSION TOPICS (FORUMS)
// ---------------------
async listDiscussionTopics(courseId) {
const response = await this.client.get(`/courses/${courseId}/discussion_topics`);
return response.data;
}
async getDiscussionTopic(courseId, topicId) {
const response = await this.client.get(`/courses/${courseId}/discussion_topics/${topicId}`);
return response.data;
}
// ---------------------
// ANNOUNCEMENTS
// ---------------------
async listAnnouncements(courseId) {
const response = await this.client.get(`/courses/${courseId}/discussion_topics`, {
params: {
type: 'announcement'
}
});
return response.data;
}
// ---------------------
// QUIZZES
// ---------------------
async listQuizzes(courseId) {
const response = await this.client.get(`/courses/${courseId}/quizzes`);
return response.data;
}
async getQuiz(courseId, quizId) {
const response = await this.client.get(`/courses/${courseId}/quizzes/${quizId}`);
return response.data;
}
async createQuiz(courseId, quizData) {
const response = await this.client.post(`/courses/${courseId}/quizzes`, {
quiz: quizData
});
return response.data;
}
async updateQuiz(courseId, quizId, quizData) {
const response = await this.client.put(`/courses/${courseId}/quizzes/${quizId}`, {
quiz: quizData
});
return response.data;
}
async deleteQuiz(courseId, quizId) {
await this.client.delete(`/courses/${courseId}/quizzes/${quizId}`);
}
// ---------------------
// FILES
// ---------------------
async listFiles(courseId) {
// Replace 'any' with appropriate CanvasFile interface if defined
const response = await this.client.get(`/courses/${courseId}/files`);
return response.data;
}
async getFile(fileId) {
// 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, groupBy) {
const params = {};
if (groupBy) {
params.group_by = groupBy;
}
const response = await this.client.get(`/accounts/${accountId}/scopes`, { params });
return response.data;
}
// ---------------------
// ASSIGNMENT SUBMISSIONS
// ---------------------
async submitAssignment(args) {
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;
}
}