download_file
Download files from web URLs with redirect handling and browser fallback for JavaScript-heavy sites. Supports custom file naming and cross-platform compatibility.
Instructions
Download a file from URL (supports redirects and browser download)
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| filename | No | Save as filename (optional). Can be absolute path. | |
| url | Yes | URL to download | |
| use_browser | No | Force browser download method (for JavaScript-heavy sites). Default: false |
Implementation Reference
- download-server.js:187-215 (handler)Core handler function that executes the download_file tool logic. Supports HTTP downloads with redirect handling and fallback to browser-based download using Playwright for complex sites.async function downloadFile(url, filePath, useBrowser = false) { if (useBrowser) { console.error('Using browser download method...'); return await browserDownload(url, filePath); } // Try HTTP download first try { console.error('Trying HTTP download with redirect support...'); return await httpDownload(url, filePath); } catch (httpError) { console.error(`HTTP download failed: ${httpError.message}`); // If HTTP fails with too many redirects or other issues, try browser if (httpError.message.includes('Too many redirects') || httpError.message.includes('HTTP 4') || httpError.message.includes('HTTP 5')) { console.error('Falling back to browser download...'); try { return await browserDownload(url, filePath); } catch (browserError) { // If both fail, return the original HTTP error throw new Error(`HTTP failed: ${httpError.message}, Browser failed: ${browserError.message}`); } } throw httpError; } }
- download-server.js:236-254 (schema)Input schema definition for the download_file tool, specifying parameters: url (required), filename (optional), use_browser (optional boolean).inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'URL to download' }, filename: { type: 'string', description: 'Save as filename (optional). Can be absolute path.' }, use_browser: { type: 'boolean', description: 'Force browser download method (for JavaScript-heavy sites). Default: false', default: false } }, required: ['url'] }
- download-server.js:233-256 (registration)Tool registration/declaration in the ListTools response, including name, description, and input schema.{ name: 'download_file', description: 'Download a file from URL (supports redirects and browser download)', inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'URL to download' }, filename: { type: 'string', description: 'Save as filename (optional). Can be absolute path.' }, use_browser: { type: 'boolean', description: 'Force browser download method (for JavaScript-heavy sites). Default: false', default: false } }, required: ['url'] } } ]
- download-server.js:262-318 (handler)MCP CallToolRequest handler specifically for download_file tool: parses arguments, determines file path, calls downloadFile, handles response and errors.if (request.params.name === 'download_file') { const { url, filename, use_browser } = request.params.arguments; try { let filePath; if (filename && path.isAbsolute(filename)) { // If filename is an absolute path, use it directly filePath = filename; // Create the directory if it doesn't exist const dir = path.dirname(filePath); await fs.mkdir(dir, { recursive: true }); } else { // Use the default download directory const downloadDir = path.join(os.homedir(), 'Downloads', 'mcp-downloads'); await fs.mkdir(downloadDir, { recursive: true }); let fname = filename; if (!fname) { try { const urlPath = new URL(url).pathname; fname = path.basename(urlPath) || 'download'; } catch { fname = 'download'; } } filePath = path.join(downloadDir, fname); } const result = await downloadFile(url, filePath, use_browser); return { content: [ { type: 'text', text: `✅ Downloaded successfully! URL: ${url} Method: ${result.method} Saved to: ${filePath} Size: ${result.size} bytes` } ] }; } catch (error) { return { content: [ { type: 'text', text: `❌ Download failed! URL: ${url} Error: ${error.message}` } ] }; } }
- download-server.js:17-88 (helper)Helper function for HTTP-based file download with automatic redirect following.async function httpDownload(url, filePath, redirectCount = 0) { const maxRedirects = 10; return new Promise((resolve, reject) => { if (redirectCount > maxRedirects) { reject(new Error(`Too many redirects (>${maxRedirects})`)); return; } const client = url.startsWith('https') ? https : http; client.get(url, { headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' } }, (response) => { // Handle redirects (301, 302, 303, 307, 308) if ([301, 302, 303, 307, 308].includes(response.statusCode)) { const redirectUrl = response.headers.location; if (!redirectUrl) { reject(new Error(`Redirect ${response.statusCode} but no location header`)); return; } // Handle relative redirects let newUrl = redirectUrl; if (!redirectUrl.startsWith('http')) { const baseUrl = new URL(url); newUrl = new URL(redirectUrl, baseUrl).toString(); } console.error(`Following redirect: ${url} -> ${newUrl}`); // Recursively follow the redirect httpDownload(newUrl, filePath, redirectCount + 1) .then(resolve) .catch(reject); return; } // Check for success if (response.statusCode !== 200) { reject(new Error(`HTTP ${response.statusCode}`)); return; } // Download the file const file = createWriteStream(filePath); let size = 0; response.on('data', (chunk) => { size += chunk.length; }); response.pipe(file); file.on('finish', () => { file.close(); resolve({ size, method: 'http' }); }); file.on('error', (err) => { file.close(); fs.unlink(filePath).catch(() => {}); reject(err); }); }).on('error', (err) => { reject(err); }); }); }