UNPKG

bottlenecks-mcp-server

Version:

Model Context Protocol server for Bottlenecks database - enables AI agents like Claude to interact with bottleneck data

486 lines 19.4 kB
/** * MCP File Operations Tools * Provides AI agents with file upload, download, and management capabilities */ /** * Upload a file and attach it to a bottleneck */ export function createUploadFileTool(client) { return { type: 'call_tool', name: 'upload_file', description: 'Upload a file and attach it to a bottleneck for supporting documentation', inputSchema: { type: 'object', properties: { card_id: { type: 'string', description: 'ID of the bottleneck to attach the file to', format: 'uuid', }, file_path: { type: 'string', description: 'Local path to the file to upload', }, filename: { type: 'string', description: 'Name for the uploaded file (optional, will use original filename if not provided)', }, upload_type: { type: 'string', description: 'Type of file being uploaded', enum: ['attachment', 'research_data', 'image', 'reference_doc'], default: 'attachment', }, description: { type: 'string', description: 'Description of the file and its relevance to the bottleneck', maxLength: 500, }, is_public: { type: 'boolean', description: 'Whether the file should be publicly accessible', default: false, }, }, required: ['card_id', 'file_path'], }, handler: async (args) => { try { // Validate required permissions const hasWritePermission = await client.checkPermissions(['write']); if (!hasWritePermission) { return { content: [ { type: 'text', text: 'Error: Write permission required to upload files. Please check your API key scopes.', }, ], isError: true, }; } // Note: In a real implementation, this would read the file from the local filesystem // For now, we'll simulate the upload process return { content: [ { type: 'text', text: `# File Upload Not Yet Implemented\n\nThe upload_file tool is designed but not yet implemented in this MCP server version.\n\n**Planned functionality:**\n- Read file from: \`${args.file_path}\`\n- Attach to bottleneck: \`${args.card_id}\`\n- Upload type: \`${args.upload_type}\`\n- Public: ${args.is_public ? 'Yes' : 'No'}\n\n**Alternative:** Use the web interface at \`/admin/cards/edit/${args.card_id}\` to upload files manually.`, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error in upload_file: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } }, }; } /** * Get file attachments for a bottleneck */ export function createGetFileAttachmentsTool(client) { return { type: 'call_tool', name: 'get_file_attachments', description: 'Get list of file attachments for a specific bottleneck', inputSchema: { type: 'object', properties: { card_id: { type: 'string', description: 'ID of the bottleneck to get attachments for', format: 'uuid', }, file_types: { type: 'array', items: { type: 'string' }, description: 'Filter by file types (e.g., pdf, csv, png)', maxItems: 10, }, upload_type: { type: 'string', description: 'Filter by upload type', enum: ['attachment', 'research_data', 'image', 'reference_doc'], }, include_metadata: { type: 'boolean', description: 'Include extracted metadata and processing results', default: false, }, }, required: ['card_id'], }, handler: async (args) => { try { const params = new URLSearchParams(); if (args.file_types && args.file_types.length > 0) { args.file_types.forEach((type) => params.append('file_type', type)); } if (args.upload_type) params.append('upload_type', args.upload_type); if (args.include_metadata) params.append('include_metadata', 'true'); const response = await client.request(`/api/files/card/${args.card_id}?${params.toString()}`, { method: 'GET', requireAuth: true, }); if (!response.success) { return { content: [ { type: 'text', text: `Error getting file attachments: ${response.error}`, }, ], isError: true, }; } const data = response.data; let content = `# File Attachments\n\n`; if (!data.files || data.files.length === 0) { content += `No file attachments found for this bottleneck.\n\n`; content += `**Use \`upload_file\` to add supporting documents.**\n`; return { content: [ { type: 'text', text: content, }, ], }; } content += `Found **${data.files.length}** file(s) attached to this bottleneck:\n\n`; data.files.forEach((file, index) => { content += `## ${index + 1}. ${file.original_filename}\n`; content += `- **ID:** \`${file.id}\`\n`; content += `- **Type:** ${file.upload_type}\n`; content += `- **Format:** ${file.mime_type}\n`; content += `- **Size:** ${(file.file_size / 1024 / 1024).toFixed(2)} MB\n`; content += `- **Status:** ${file.processing_status}\n`; content += `- **Public:** ${file.is_public ? 'Yes' : 'No'}\n`; content += `- **Uploaded:** ${new Date(file.created_at).toLocaleDateString()}\n`; if (file.description) { content += `- **Description:** ${file.description}\n`; } if (args.include_metadata && file.metadata) { content += `- **Metadata:**\n`; Object.entries(file.metadata).forEach(([key, value]) => { content += ` - ${key}: ${value}\n`; }); } content += '\n'; }); content += `**Actions:**\n`; content += `- Use \`get_file_content\` to access processed content (OCR, extracted text)\n`; content += `- Use \`download_file\` to get the original file\n`; return { content: [ { type: 'text', text: content, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error in get_file_attachments: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } }, }; } /** * Get processed content from a file (OCR, extracted text, etc.) */ export function createGetFileContentTool(client) { return { type: 'call_tool', name: 'get_file_content', description: 'Get processed content from an uploaded file (OCR text, extracted data, etc.)', inputSchema: { type: 'object', properties: { file_id: { type: 'string', description: 'ID of the file to get content from', format: 'uuid', }, content_type: { type: 'string', description: 'Type of processed content to retrieve', enum: [ 'extracted_text', 'ocr_text', 'metadata', 'data_preview', 'all', ], default: 'extracted_text', }, format: { type: 'string', description: 'Response format', enum: ['text', 'json', 'markdown'], default: 'text', }, }, required: ['file_id'], }, handler: async (args) => { try { const params = new URLSearchParams(); if (args.content_type) params.append('content_type', args.content_type); if (args.format) params.append('format', args.format); const response = await client.request(`/api/files/${args.file_id}/content?${params.toString()}`, { method: 'GET', requireAuth: true, }); if (!response.success) { return { content: [ { type: 'text', text: `Error getting file content: ${response.error}`, }, ], isError: true, }; } const data = response.data; let content = ''; if (args.format === 'json') { content = JSON.stringify(data, null, 2); } else if (args.format === 'markdown') { content += `# File Content: ${data.filename}\n\n`; content += `**File ID:** \`${args.file_id}\`\n`; content += `**Content Type:** ${args.content_type}\n`; content += `**Processing Status:** ${data.processing_status}\n\n`; if (data.content) { content += `## Extracted Content\n\n`; content += data.content; } else { content += `*No processed content available yet. File may still be processing.*\n`; } } else { // Text format (default) if (data.content) { content = data.content; } else { content = `No processed content available for file ${args.file_id}. The file may still be processing or may not support content extraction.`; } } return { content: [ { type: 'text', text: content, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error in get_file_content: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } }, }; } /** * Download a file attachment */ export function createDownloadFileTool(client) { return { type: 'call_tool', name: 'download_file', description: 'Download an original file attachment', inputSchema: { type: 'object', properties: { file_id: { type: 'string', description: 'ID of the file to download', format: 'uuid', }, save_path: { type: 'string', description: 'Local path where to save the downloaded file (optional)', }, }, required: ['file_id'], }, handler: async (args) => { try { const response = await client.request(`/api/files/${args.file_id}/download`, { method: 'GET', requireAuth: true, }); if (!response.success) { return { content: [ { type: 'text', text: `Error downloading file: ${response.error}`, }, ], isError: true, }; } // Note: In a real implementation, this would handle the binary file download // For now, we'll return information about the download const data = response.data; let content = `# File Download\n\n`; content += `**File ID:** \`${args.file_id}\`\n`; content += `**Filename:** ${data.filename}\n`; content += `**Size:** ${(data.file_size / 1024 / 1024).toFixed(2)} MB\n`; content += `**Type:** ${data.mime_type}\n`; if (args.save_path) { content += `**Save Path:** \`${args.save_path}\`\n`; } content += `\n**Download URL:** \`${data.download_url}\`\n`; content += `\n*Note: In a full implementation, the file would be downloaded directly to your specified path.*\n`; return { content: [ { type: 'text', text: content, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error in download_file: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } }, }; } /** * Delete a file attachment */ export function createDeleteFileTool(client) { return { type: 'call_tool', name: 'delete_file', description: 'Delete a file attachment from a bottleneck', inputSchema: { type: 'object', properties: { file_id: { type: 'string', description: 'ID of the file to delete', format: 'uuid', }, confirm: { type: 'boolean', description: 'Confirmation that you want to delete the file', default: false, }, }, required: ['file_id', 'confirm'], }, handler: async (args) => { try { // Validate required permissions const hasWritePermission = await client.checkPermissions(['write']); if (!hasWritePermission) { return { content: [ { type: 'text', text: 'Error: Write permission required to delete files. Please check your API key scopes.', }, ], isError: true, }; } if (!args.confirm) { return { content: [ { type: 'text', text: 'Error: File deletion requires confirmation. Set `confirm: true` to proceed with deletion.', }, ], isError: true, }; } const response = await client.request(`/api/files/${args.file_id}`, { method: 'DELETE', requireAuth: true, }); if (!response.success) { return { content: [ { type: 'text', text: `Error deleting file: ${response.error}`, }, ], isError: true, }; } let content = `# File Deleted Successfully ✅\n\n`; content += `**File ID:** \`${args.file_id}\`\n`; content += `**Status:** Permanently deleted from storage\n\n`; content += `*The file has been removed from the bottleneck and deleted from storage.*\n`; return { content: [ { type: 'text', text: content, }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error in delete_file: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], isError: true, }; } }, }; } //# sourceMappingURL=file-operations.js.map