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
JavaScript
/**
* 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