@taazkareem/clickup-mcp-server
Version:
ClickUp MCP Server - Integrate ClickUp tasks with AI through Model Context Protocol
98 lines (97 loc) • 4.43 kB
JavaScript
/**
* SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
* SPDX-License-Identifier: MIT
*
* ClickUp Task Service - Attachments Module
*
* Handles file attachment operations for ClickUp tasks, supporting three methods:
* - Uploading file attachments from base64/buffer data
* - Uploading file attachments from a URL (web URLs like http/https)
* - Uploading file attachments from local file paths (absolute paths)
*/
import { TaskServiceSearch } from './task-search.js';
/**
* Attachment functionality for the TaskService
*/
export class TaskServiceAttachments extends TaskServiceSearch {
/**
* Upload a file attachment to a ClickUp task
* @param taskId The ID of the task to attach the file to
* @param fileData The file data as a Buffer
* @param fileName The name of the file
* @returns Promise resolving to the attachment response from ClickUp
*/
async uploadTaskAttachment(taskId, fileData, fileName) {
this.logOperation('uploadTaskAttachment', { taskId, fileName, fileSize: fileData.length });
try {
return await this.makeRequest(async () => {
// Create FormData for multipart/form-data upload
const FormData = (await import('form-data')).default;
const formData = new FormData();
// Add the file to the form data
formData.append('attachment', fileData, {
filename: fileName,
contentType: 'application/octet-stream' // Let ClickUp determine the content type
});
// Use the raw axios client for this request since we need to handle FormData
const response = await this.client.post(`/task/${taskId}/attachment`, formData, {
headers: {
...formData.getHeaders(),
'Authorization': this.apiKey
}
});
return response.data;
});
}
catch (error) {
throw this.handleError(error, `Failed to upload attachment to task ${taskId}`);
}
}
/**
* Upload a file attachment to a ClickUp task from a URL
* @param taskId The ID of the task to attach the file to
* @param fileUrl The URL of the file to download and attach
* @param fileName Optional file name (if not provided, it will be extracted from the URL)
* @param authHeader Optional authorization header for the URL
* @returns Promise resolving to the attachment response from ClickUp
*/
async uploadTaskAttachmentFromUrl(taskId, fileUrl, fileName, authHeader) {
this.logOperation('uploadTaskAttachmentFromUrl', { taskId, fileUrl, fileName });
try {
return await this.makeRequest(async () => {
// Import required modules
const axios = (await import('axios')).default;
const FormData = (await import('form-data')).default;
// Download the file from the URL
const headers = {};
if (authHeader) {
headers['Authorization'] = authHeader;
}
const response = await axios.get(fileUrl, {
responseType: 'arraybuffer',
headers
});
// Extract filename from URL if not provided
const actualFileName = fileName || fileUrl.split('/').pop() || 'downloaded-file';
// Create FormData for multipart/form-data upload
const formData = new FormData();
// Add the file to the form data
formData.append('attachment', Buffer.from(response.data), {
filename: actualFileName,
contentType: 'application/octet-stream'
});
// Upload the file to ClickUp
const uploadResponse = await this.client.post(`/task/${taskId}/attachment`, formData, {
headers: {
...formData.getHeaders(),
'Authorization': this.apiKey
}
});
return uploadResponse.data;
});
}
catch (error) {
throw this.handleError(error, `Failed to upload attachment from URL to task ${taskId}`);
}
}
}