import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { canvasApiRequest } from "../api/canvas-client.js";
import { CanvasAssignment } from "../types/canvas.js";
import { htmlToPlainText, convertHtmlToMarkdown, extractLinks } from "../utils/html.js";
import { formatDate } from "../utils/date.js";
export function registerGetAssignmentTool(server: McpServer) {
server.tool(
"get_assignment",
"Retrieves detailed information about a specific assignment, including its description, submission requirements, and embedded links.",
{
courseId: z.string().or(z.number()).describe("Course ID"),
assignmentId: z.string().or(z.number()).describe("Assignment ID"),
formatType: z.enum(['full', 'plain', 'markdown']).default('markdown')
.describe("Format type: full (HTML), plain (text only), or markdown (formatted)"),
},
async ({ courseId, assignmentId, formatType }) => {
try {
const assignment = await canvasApiRequest<CanvasAssignment>(`/courses/${courseId}/assignments/${assignmentId}`);
let description: string;
let links: { text: string; href: string }[] = [];
if (assignment.description) {
links = extractLinks(assignment.description);
}
switch (formatType) {
case 'full':
description = assignment.description || 'No description available';
break;
case 'plain':
description = htmlToPlainText(assignment.description) || 'No description available';
break;
case 'markdown':
default:
description = assignment.description ?
convertHtmlToMarkdown(assignment.description) :
'No description available';
break;
}
const details = [
`# ${assignment.name}`,
``,
`**Course ID:** ${courseId}`,
`**Assignment ID:** ${assignment.id}`,
`**Due Date:** ${formatDate(assignment.due_at)}`,
`**Points Possible:** ${assignment.points_possible}`,
`**Status:** ${assignment.published ? 'Published' : 'Unpublished'}${assignment.only_visible_to_overrides ? ' (Only visible to specific students)' : ''}`,
``,
`## Submission Requirements`,
`- **Submission Type:** ${assignment.submission_types?.join(', ') || 'Not specified'}`,
];
// Add allowed file extensions if relevant
if (assignment.submission_types?.includes('online_upload') && assignment.allowed_extensions?.length) {
details.push(`- **Allowed File Types:** ${assignment.allowed_extensions.map(ext => `\`.${ext}\``).join(', ')}`);
}
// Add attempt limits if specified
if (assignment.allowed_attempts !== null && assignment.allowed_attempts !== -1) {
details.push(`- **Allowed Attempts:** ${assignment.allowed_attempts}`);
}
// Add grading type info
details.push(`- **Grading Type:** ${assignment.grading_type.replace(/_/g, ' ').toLowerCase()}`);
// Add time restrictions if any
if (assignment.unlock_at || assignment.lock_at) {
details.push(`- **Time Restrictions:**`);
if (assignment.unlock_at) {
details.push(` - Available from: ${formatDate(assignment.unlock_at)}`);
}
if (assignment.lock_at) {
details.push(` - Locks at: ${formatDate(assignment.lock_at)}`);
}
}
// Add group assignment info if relevant
if (assignment.has_group_assignment) {
details.push(`- **Group Assignment:** Yes`);
}
// Add peer review info if enabled
if (assignment.peer_reviews) {
details.push(`- **Peer Reviews Required:** Yes`);
}
// Add word count requirement if specified
if (assignment.word_count) {
details.push(`- **Required Word Count:** ${assignment.word_count}`);
}
// Add external tool info if present
if (assignment.external_tool_tag_attributes?.url) {
details.push(`- **External Tool Required:** Yes`);
details.push(` - Tool URL: ${assignment.external_tool_tag_attributes.url}`);
if (assignment.external_tool_tag_attributes.new_tab) {
details.push(` - Opens in new tab: Yes`);
}
}
// Add plagiarism detection info
if (assignment.turnitin_enabled || assignment.vericite_enabled) {
details.push(`- **Plagiarism Detection:**`);
if (assignment.turnitin_enabled) details.push(` - Turnitin enabled`);
if (assignment.vericite_enabled) details.push(` - VeriCite enabled`);
}
// Add rubric information if available
if (assignment.rubric && assignment.rubric.length > 0) {
details.push('', '## Rubric');
if (assignment.use_rubric_for_grading) {
details.push('*This rubric is used for grading*', '');
}
assignment.rubric.forEach(criterion => {
details.push(`### ${criterion.description} (${criterion.points} points)`);
if (criterion.long_description) {
details.push(criterion.long_description);
}
details.push('');
});
}
// Add special requirements
const specialReqs = [];
if (assignment.anonymize_students) specialReqs.push('Anonymous Grading Enabled');
if (assignment.require_lockdown_browser) specialReqs.push('Lockdown Browser Required');
if (assignment.annotatable_attachment_id) specialReqs.push('Annotation Required');
if (specialReqs.length > 0) {
details.push('', '## Special Requirements', '');
specialReqs.forEach(req => details.push(`- ${req}`));
}
// Add lock status if relevant
if (assignment.locked_for_user) {
details.push('', '## Access Restrictions', '');
details.push(assignment.lock_explanation || 'This assignment is currently locked.');
}
details.push(
``,
`## Description`,
``,
description
);
// Add links section if any links were found
if (links.length > 0) {
details.push('', '## Required Materials and Links', '');
links.forEach(link => {
details.push(`- [${link.text}](${link.href})`);
});
}
return {
content: [{
type: "text",
text: details.join('\n')
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: `Failed to fetch assignment details: ${(error as Error).message}`
}],
isError: true
};
}
}
);
}