search_assignments
Search for Canvas assignments across courses using keywords, due dates, and course filters to find relevant tasks and deadlines.
Instructions
Searches for assignments across all courses based on title, description, due dates, and course filters.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | No | Search term to find in assignment titles or descriptions | |
| dueBefore | No | Only include assignments due before this date (YYYY-MM-DD) | |
| dueAfter | No | Only include assignments due after this date (YYYY-MM-DD) | |
| includeCompleted | No | Include assignments from completed courses | |
| courseId | No | Optional: Limit search to specific course ID |
Implementation Reference
- src/tools/search-assignments.ts:19-183 (handler)The core handler logic for the search_assignments tool. Fetches Canvas courses (filtered by courseId or state), retrieves assignments with date filtering via Canvas API, filters by search query in title/description, sorts by due date, and formats a markdown list of matching assignments.async ({ query = "", dueBefore, dueAfter, includeCompleted, courseId }) => { try { let courses: CanvasCourse[]; // If courseId is provided, only search that course if (courseId) { courses = [await canvasApiRequest<CanvasCourse>(`/courses/${courseId}`)]; } else { // Otherwise, get all courses based on state const courseState = includeCompleted ? 'all' : 'active'; courses = await canvasApiRequest<CanvasCourse[]>(`/courses?enrollment_state=${courseState}`); } if (courses.length === 0) { return { content: [{ type: "text", text: "No courses found." }] }; } // Search assignments in each course let allResults: AssignmentWithCourse[] = []; for (const course of courses) { try { // Build the assignments query let assignmentsUrl = `/courses/${course.id}/assignments?per_page=100&order_by=due_at&include[]=submission`; // Add date filtering parameters if provided const params = new URLSearchParams(); // Canvas API uses bucket parameter for broad date filtering if (dueAfter && !dueBefore) { params.append('bucket', 'future'); } else if (dueBefore && !dueAfter) { params.append('bucket', 'past'); } // Add specific date range parameters if (dueAfter) { const afterDate = parseDate(dueAfter); if (afterDate) { afterDate.setHours(0, 0, 0, 0); params.append('due_after', afterDate.toISOString()); } } if (dueBefore) { const beforeDate = parseDate(dueBefore); if (beforeDate) { beforeDate.setHours(23, 59, 59, 999); params.append('due_before', beforeDate.toISOString()); } } if (params.toString()) { assignmentsUrl += `&${params.toString()}`; } console.error(`Fetching assignments from URL: ${assignmentsUrl}`); // Debug logging const assignments = await canvasApiRequest<CanvasAssignment[]>(assignmentsUrl); console.error(`Found ${assignments.length} assignments in course ${course.id}`); // Debug logging // Filter by search terms if query is provided const searchTerms = query.toLowerCase().split(/\s+/).filter(term => term.length > 0); const matchingAssignments = searchTerms.length > 0 ? assignments.filter((assignment) => { // Search in title and description const titleMatch = searchTerms.some(term => assignment.name.toLowerCase().includes(term) ); const descriptionMatch = assignment.description ? searchTerms.some(term => htmlToPlainText(assignment.description).toLowerCase().includes(term) ) : false; return titleMatch || descriptionMatch; }) : assignments; // Double-check date range (in case API filter wasn't exact) const dateFilteredAssignments = matchingAssignments.filter(assignment => { // Skip local date filtering if the API is already handling it if ((dueAfter && !dueBefore && params.has('bucket')) || (dueBefore && !dueAfter && params.has('bucket'))) { return true; } return isDateInRange(assignment.due_at, dueBefore, dueAfter); }); // Add course information to each matching assignment dateFilteredAssignments.forEach((assignment) => { allResults.push({ ...assignment, courseName: course.name, courseId: course.id }); }); } catch (error) { console.error(`Error searching in course ${course.id}: ${(error as Error).message}`); // Continue with other courses even if one fails } } // Sort results by due date allResults.sort((a, b) => { // Put assignments with no due date at the end if (!a.due_at && !b.due_at) return 0; if (!a.due_at) return 1; if (!b.due_at) return -1; const dateA = parseDate(a.due_at); const dateB = parseDate(b.due_at); if (!dateA || !dateB) return 0; return dateA.getTime() - dateB.getTime(); }); if (allResults.length === 0) { const dateRange = []; if (dueAfter) dateRange.push(`after ${dueAfter}`); if (dueBefore) dateRange.push(`before ${dueBefore}`); const dateStr = dateRange.length > 0 ? ` due ${dateRange.join(' and ')}` : ''; const queryStr = query ? ` matching "${query}"` : ''; return { content: [{ type: "text", text: `No assignments found${queryStr}${dateStr}.` }] }; } const resultsList = allResults.map((assignment) => { const dueDate = formatDate(assignment.due_at); const status = assignment.published ? '' : ' (Unpublished)'; return [ `- Course: ${assignment.courseName} (ID: ${assignment.courseId})`, ` Assignment: ${assignment.name}${status} (ID: ${assignment.id})`, ` Due: ${dueDate}` ].join('\n'); }).join('\n\n'); const dateRange = []; if (dueAfter) dateRange.push(`after ${dueAfter}`); if (dueBefore) dateRange.push(`before ${dueBefore}`); const dateStr = dateRange.length > 0 ? ` due ${dateRange.join(' and ')}` : ''; const queryStr = query ? ` matching "${query}"` : ''; return { content: [{ type: "text", text: `Found ${allResults.length} assignments${queryStr}${dateStr}:\n\n${resultsList}` }] }; } catch (error) { return { content: [{ type: "text", text: `Search failed: ${(error as Error).message}` }], isError: true }; } }
- Zod input schema defining parameters for the tool: query (search term), date range filters (dueBefore, dueAfter), includeCompleted (boolean), and optional courseId.query: z.string().optional().default("").describe("Search term to find in assignment titles or descriptions"), dueBefore: z.string().optional().describe("Only include assignments due before this date (YYYY-MM-DD)"), dueAfter: z.string().optional().describe("Only include assignments due after this date (YYYY-MM-DD)"), includeCompleted: z.boolean().default(false).describe("Include assignments from completed courses"), courseId: z.string().or(z.number()).optional().describe("Optional: Limit search to specific course ID"), },
- src/tools/search-assignments.ts:9-184 (registration)Registers the 'search_assignments' tool on the MCP server with name, description, input schema, and handler function.server.tool( "search_assignments", "Searches for assignments across all courses based on title, description, due dates, and course filters.", { query: z.string().optional().default("").describe("Search term to find in assignment titles or descriptions"), dueBefore: z.string().optional().describe("Only include assignments due before this date (YYYY-MM-DD)"), dueAfter: z.string().optional().describe("Only include assignments due after this date (YYYY-MM-DD)"), includeCompleted: z.boolean().default(false).describe("Include assignments from completed courses"), courseId: z.string().or(z.number()).optional().describe("Optional: Limit search to specific course ID"), }, async ({ query = "", dueBefore, dueAfter, includeCompleted, courseId }) => { try { let courses: CanvasCourse[]; // If courseId is provided, only search that course if (courseId) { courses = [await canvasApiRequest<CanvasCourse>(`/courses/${courseId}`)]; } else { // Otherwise, get all courses based on state const courseState = includeCompleted ? 'all' : 'active'; courses = await canvasApiRequest<CanvasCourse[]>(`/courses?enrollment_state=${courseState}`); } if (courses.length === 0) { return { content: [{ type: "text", text: "No courses found." }] }; } // Search assignments in each course let allResults: AssignmentWithCourse[] = []; for (const course of courses) { try { // Build the assignments query let assignmentsUrl = `/courses/${course.id}/assignments?per_page=100&order_by=due_at&include[]=submission`; // Add date filtering parameters if provided const params = new URLSearchParams(); // Canvas API uses bucket parameter for broad date filtering if (dueAfter && !dueBefore) { params.append('bucket', 'future'); } else if (dueBefore && !dueAfter) { params.append('bucket', 'past'); } // Add specific date range parameters if (dueAfter) { const afterDate = parseDate(dueAfter); if (afterDate) { afterDate.setHours(0, 0, 0, 0); params.append('due_after', afterDate.toISOString()); } } if (dueBefore) { const beforeDate = parseDate(dueBefore); if (beforeDate) { beforeDate.setHours(23, 59, 59, 999); params.append('due_before', beforeDate.toISOString()); } } if (params.toString()) { assignmentsUrl += `&${params.toString()}`; } console.error(`Fetching assignments from URL: ${assignmentsUrl}`); // Debug logging const assignments = await canvasApiRequest<CanvasAssignment[]>(assignmentsUrl); console.error(`Found ${assignments.length} assignments in course ${course.id}`); // Debug logging // Filter by search terms if query is provided const searchTerms = query.toLowerCase().split(/\s+/).filter(term => term.length > 0); const matchingAssignments = searchTerms.length > 0 ? assignments.filter((assignment) => { // Search in title and description const titleMatch = searchTerms.some(term => assignment.name.toLowerCase().includes(term) ); const descriptionMatch = assignment.description ? searchTerms.some(term => htmlToPlainText(assignment.description).toLowerCase().includes(term) ) : false; return titleMatch || descriptionMatch; }) : assignments; // Double-check date range (in case API filter wasn't exact) const dateFilteredAssignments = matchingAssignments.filter(assignment => { // Skip local date filtering if the API is already handling it if ((dueAfter && !dueBefore && params.has('bucket')) || (dueBefore && !dueAfter && params.has('bucket'))) { return true; } return isDateInRange(assignment.due_at, dueBefore, dueAfter); }); // Add course information to each matching assignment dateFilteredAssignments.forEach((assignment) => { allResults.push({ ...assignment, courseName: course.name, courseId: course.id }); }); } catch (error) { console.error(`Error searching in course ${course.id}: ${(error as Error).message}`); // Continue with other courses even if one fails } } // Sort results by due date allResults.sort((a, b) => { // Put assignments with no due date at the end if (!a.due_at && !b.due_at) return 0; if (!a.due_at) return 1; if (!b.due_at) return -1; const dateA = parseDate(a.due_at); const dateB = parseDate(b.due_at); if (!dateA || !dateB) return 0; return dateA.getTime() - dateB.getTime(); }); if (allResults.length === 0) { const dateRange = []; if (dueAfter) dateRange.push(`after ${dueAfter}`); if (dueBefore) dateRange.push(`before ${dueBefore}`); const dateStr = dateRange.length > 0 ? ` due ${dateRange.join(' and ')}` : ''; const queryStr = query ? ` matching "${query}"` : ''; return { content: [{ type: "text", text: `No assignments found${queryStr}${dateStr}.` }] }; } const resultsList = allResults.map((assignment) => { const dueDate = formatDate(assignment.due_at); const status = assignment.published ? '' : ' (Unpublished)'; return [ `- Course: ${assignment.courseName} (ID: ${assignment.courseId})`, ` Assignment: ${assignment.name}${status} (ID: ${assignment.id})`, ` Due: ${dueDate}` ].join('\n'); }).join('\n\n'); const dateRange = []; if (dueAfter) dateRange.push(`after ${dueAfter}`); if (dueBefore) dateRange.push(`before ${dueBefore}`); const dateStr = dateRange.length > 0 ? ` due ${dateRange.join(' and ')}` : ''; const queryStr = query ? ` matching "${query}"` : ''; return { content: [{ type: "text", text: `Found ${allResults.length} assignments${queryStr}${dateStr}:\n\n${resultsList}` }] }; } catch (error) { return { content: [{ type: "text", text: `Search failed: ${(error as Error).message}` }], isError: true }; } } );
- src/index.ts:26-26 (registration)Invokes the tool registration function to add search_assignments to the main MCP server.registerSearchAssignmentsTool(server);
- src/index.ts:6-6 (registration)Imports the registration function for the search_assignments tool.import { registerSearchAssignmentsTool } from "./tools/search-assignments.js";