download_file
Download files from D2L Brightspace courses to your local device. Specify a content URL to save lecture slides, assignments, or course materials to your Downloads folder or custom path.
Instructions
Download a file from D2L Brightspace. Provide a D2L content URL (e.g., https://learn.ul.ie/content/enforced/68929-CS4444.../file.docx or /content/enforced/...). The file will be saved to your Downloads folder by default, or to a custom path if specified. Returns the local file path, filename, size, and content type. Use this to download lecture slides, assignment files, course materials, or any file linked in course content.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | The D2L URL or path to the file to download (e.g., https://learn.ul.ie/content/enforced/68929-CS4444_SEM1_2025_6/file.docx) | |
| savePath | No | Optional: Custom path to save the file (directory or full file path). Defaults to ~/Downloads |
Implementation Reference
- src/tools/files.ts:32-126 (handler)Core handler function that performs the file download from D2L Brightspace using authenticated browser context. Handles URL resolution, downloading with cookies/SSO, local saving with collision detection, metadata extraction (path, size, contentType), and optional text content extraction for supported formats.export async function downloadFile(url: string, savePath?: string) { // Ensure full URL const fullUrl = url.startsWith('http') ? url : `https://${D2L_HOST}${url}`; // Extract filename from URL const urlPath = new URL(fullUrl).pathname; const pathParts = urlPath.split('/'); const urlFilename = decodeURIComponent(pathParts[pathParts.length - 1] || 'download'); // Get authenticated browser context (handles SSO login if needed) const browser = await getAuthenticatedContext(); try { const page = await browser.newPage(); // Use the page's request API to fetch with cookies const response = await page.request.get(fullUrl); if (!response.ok()) { throw new Error(`Failed to download file: ${response.status()} ${response.statusText()}`); } // Get response body as buffer const data = await response.body(); // Get content type and disposition from headers const contentType = response.headers()['content-type'] || 'application/octet-stream'; const contentDisposition = response.headers()['content-disposition'] || ''; // Extract filename from content-disposition or use URL filename let filename = urlFilename; const filenameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/); if (filenameMatch) { filename = filenameMatch[1].replace(/['"]/g, ''); } // Determine where to save const downloadsDir = savePath && fs.existsSync(savePath) && fs.statSync(savePath).isDirectory() ? savePath : path.join(os.homedir(), 'Downloads'); let finalPath = savePath && fs.existsSync(savePath) && !fs.statSync(savePath).isDirectory() ? savePath : path.join(downloadsDir, filename); // Handle filename collisions let counter = 1; const ext = path.extname(finalPath); const base = path.basename(finalPath, ext); const dirPath = path.dirname(finalPath); while (fs.existsSync(finalPath)) { finalPath = path.join(dirPath, `${base} (${counter})${ext}`); counter++; } // Write file fs.writeFileSync(finalPath, data); // Determine mime type from extension if content-type is generic const extToMime: Record<string, string> = { '.pdf': 'application/pdf', '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', '.doc': 'application/msword', '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', '.xls': 'application/vnd.ms-excel', '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation', '.ppt': 'application/vnd.ms-powerpoint', '.zip': 'application/zip', '.txt': 'text/plain', '.html': 'text/html', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png', '.gif': 'image/gif', }; const finalContentType = contentType.includes('octet-stream') ? (extToMime[ext.toLowerCase()] || contentType) : contentType; // Extract text content for supported file types const textContent = await extractContent(data, ext); return { path: finalPath, filename: path.basename(finalPath), size: data.length, contentType: finalContentType, content: textContent, }; } finally { await browser.close(); } }
- src/index.ts:151-169 (registration)MCP server registration of the 'download_file' tool, including tool name, description, Zod input schema (url required, savePath optional), and thin wrapper handler that delegates to core downloadFile function and formats the textual response.server.tool( 'download_file', 'Download a file from D2L Brightspace. Provide a D2L content URL (e.g., https://learn.ul.ie/content/enforced/68929-CS4444.../file.docx or /content/enforced/...). The file will be saved to your Downloads folder by default, or to a custom path if specified. Returns the local file path, filename, size, and content type. Use this to download lecture slides, assignment files, course materials, or any file linked in course content.', { url: z.string().describe('The D2L URL or path to the file to download (e.g., https://learn.ul.ie/content/enforced/68929-CS4444_SEM1_2025_6/file.docx)'), savePath: z.string().optional().describe('Optional: Custom path to save the file (directory or full file path). Defaults to ~/Downloads'), }, async (args) => { const result = await downloadFile(args.url, args.savePath); const sizeKB = (result.size / 1024).toFixed(1); let text = `Downloaded: ${result.filename}\nPath: ${result.path}\nSize: ${sizeKB} KB\nType: ${result.contentType}`; if (result.content) { text += `\n\n--- File Content ---\n${result.content}`; } return { content: [{ type: 'text', text }] }; } );
- src/index.ts:154-157 (schema)Zod schema defining the input parameters for the download_file tool: required 'url' string and optional 'savePath' string.{ url: z.string().describe('The D2L URL or path to the file to download (e.g., https://learn.ul.ie/content/enforced/68929-CS4444_SEM1_2025_6/file.docx)'), savePath: z.string().optional().describe('Optional: Custom path to save the file (directory or full file path). Defaults to ~/Downloads'), },
- src/tools/files.ts:10-30 (helper)Helper function to extract readable text content from downloaded files for certain formats (text files, DOCX via mammoth). Used in downloadFile to provide 'content' in response.async function extractContent(data: Buffer, ext: string): Promise<string | null> { const lowerExt = ext.toLowerCase(); // Text-based files - return as string if (['.txt', '.md', '.csv', '.json', '.xml', '.html', '.htm', '.css', '.js', '.ts', '.py', '.java', '.c', '.cpp', '.h'].includes(lowerExt)) { return data.toString('utf-8'); } // Word documents - extract text with mammoth if (lowerExt === '.docx') { try { const result = await mammoth.extractRawText({ buffer: data }); return result.value; } catch { return null; } } // For binary files, return null (could add base64 option later) return null; }