UNPKG

dbx-mcp-server

Version:

A Model Context Protocol server for Dropbox integration with AI-powered PDF analysis, multi-directory indexing, and parallel processing

175 lines 7.08 kB
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; import { config, log } from '../config.js'; import { getDropboxClientForPath, formatDropboxPath } from './client-factory.js'; import { handleDropboxError } from './error-handler.js'; // Track deletion operations for rate limiting const deletionTracker = new Map(); export async function deleteItem(path) { try { const client = await getDropboxClientForPath(path); await client.filesDeleteV2({ path: formatDropboxPath(path) }); return { content: [{ type: 'text', text: `Deleted ${path}`, }], }; } catch (error) { handleDropboxError(error); } } export async function safeDeleteItem(path, userId, skipConfirmation = false, retentionDays, reason, permanent = false) { try { // Rate limiting check const now = Date.now(); const today = Math.floor(now / (24 * 60 * 60 * 1000)); if (!deletionTracker.has(userId)) { deletionTracker.set(userId, []); } const userDeletions = deletionTracker.get(userId); const todayDeletions = userDeletions.filter(timestamp => Math.floor(timestamp / (24 * 60 * 60 * 1000)) === today).length; if (todayDeletions >= config.safety.maxDeletesPerDay) { throw new McpError(ErrorCode.InvalidRequest, `Delete rate limit exceeded for user ${userId}`); } const client = await getDropboxClientForPath(path); const normalizedPath = formatDropboxPath(path); // Get file metadata before deletion const metadata = await client.filesGetMetadata({ path: normalizedPath }); if (!skipConfirmation) { // Log confirmation requirement config.auditLogger.info('Delete confirmation required', { path: normalizedPath, userId, metadata: metadata.result }); return { content: [{ type: 'text', text: JSON.stringify({ status: 'confirmation_required', message: 'Please confirm deletion', path: normalizedPath, metadata: metadata.result }, null, 2) }], }; } if (permanent) { // Permanent deletion await client.filesDeleteV2({ path: normalizedPath }); // Log permanent deletion config.auditLogger.warn('Permanent deletion executed', { path: normalizedPath, userId, reason: reason || 'No reason provided', metadata: metadata.result }); // Track deletion userDeletions.push(now); deletionTracker.set(userId, userDeletions); return { content: [{ type: 'text', text: `File permanently deleted: ${normalizedPath}`, }], }; } else { // Move to recycle bin const recycleBinPath = config.safety.recycleBinPath; const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const recycledPath = `${recycleBinPath}/${timestamp}_${metadata.result.name}`; try { await client.filesMoveV2({ from_path: normalizedPath, to_path: recycledPath, allow_shared_folder: false, autorename: true, allow_ownership_transfer: false, }); // Log safe deletion config.auditLogger.info('Safe deletion executed', { originalPath: normalizedPath, recycleBinPath: recycledPath, userId, reason: reason || 'No reason provided', retentionDays: retentionDays || config.safety.retentionDays, metadata: metadata.result }); // Track deletion userDeletions.push(now); deletionTracker.set(userId, userDeletions); return { content: [{ type: 'text', text: JSON.stringify({ status: 'deleted', message: 'File moved to recycle bin', originalPath: normalizedPath, recycleBinPath: recycledPath, retentionDays: retentionDays || config.safety.retentionDays }, null, 2) }], }; } catch (moveError) { // If move to recycle bin fails, fallback to permanent deletion with warning log.warn('Failed to move to recycle bin, performing permanent deletion', { path: normalizedPath, error: moveError.message }); await client.filesDeleteV2({ path: normalizedPath }); config.auditLogger.warn('Fallback permanent deletion executed', { path: normalizedPath, userId, reason: `Recycle bin unavailable: ${moveError.message}`, metadata: metadata.result }); // Track deletion userDeletions.push(now); deletionTracker.set(userId, userDeletions); return { content: [{ type: 'text', text: `File permanently deleted (recycle bin unavailable): ${normalizedPath}`, }], }; } } } catch (error) { config.auditLogger.error('Delete operation failed', { path, userId, error: error.message }); handleDropboxError(error); } } // Legacy delete function for backward compatibility export { deleteItem as legacyDelete }; // Cleanup function to maintain deletion tracker export function cleanupDeletionTracker() { const sevenDaysAgo = Date.now() - (7 * 24 * 60 * 60 * 1000); for (const [userId, deletions] of deletionTracker.entries()) { const recentDeletions = deletions.filter(timestamp => timestamp > sevenDaysAgo); if (recentDeletions.length === 0) { deletionTracker.delete(userId); } else { deletionTracker.set(userId, recentDeletions); } } } // Run cleanup periodically setInterval(cleanupDeletionTracker, 60 * 60 * 1000); // Every hour //# sourceMappingURL=delete-operations.js.map