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