dbx-mcp-server
Version:
A Model Context Protocol server for Dropbox integration with AI-powered PDF analysis, multi-directory indexing, and parallel processing
951 lines • 37.5 kB
JavaScript
import { Dropbox } from 'dropbox';
import { getValidAccessToken } from './auth.js';
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
import { config, log } from './config.js';
// Error mapping for better error messages
const ERROR_MESSAGES = {
path_not_found: 'The specified path was not found in Dropbox',
path_malformed: 'The path format is invalid',
insufficient_permissions: 'The access token does not have the required permissions',
invalid_access_token: 'The access token is invalid or has expired',
rate_limit: 'Rate limit exceeded. Please try again later',
server_error: 'Dropbox server error occurred',
network_error: 'Network error occurred while connecting to Dropbox'
};
// Helper function to handle Dropbox API errors
function handleDropboxError(error) {
const errorMessage = error?.error?.error_summary || error?.message || 'Unknown error';
log.error('Dropbox API error:', { error: errorMessage, stack: error?.stack });
// Map common error patterns to user-friendly messages
for (const [key, message] of Object.entries(ERROR_MESSAGES)) {
if (errorMessage.includes(key)) {
throw new McpError(key === 'path_not_found' || key === 'path_malformed'
? ErrorCode.InvalidParams
: ErrorCode.InternalError, message);
}
}
// Generic error case
throw new McpError(ErrorCode.InternalError, `Dropbox API error: ${errorMessage}`);
}
// Cache for business user ID to avoid repeated API calls
let cachedBusinessUserId = null;
let businessUserIdChecked = false;
// Cache for team configuration to avoid repeated API calls
let cachedTeamConfig = null;
let teamConfigChecked = false;
// Get a Dropbox client with a valid token and business account support
async function getDropboxClient(options) {
const token = config.dropbox.accessToken || await getValidAccessToken();
// Auto-detect business user ID if not already checked
if (!businessUserIdChecked) {
cachedBusinessUserId = await getBusinessUserId();
businessUserIdChecked = true;
// Set environment variable for future use
if (cachedBusinessUserId) {
process.env.DROPBOX_BUSINESS_USER_ID = cachedBusinessUserId;
log.info('Auto-detected Dropbox Business user', { userId: cachedBusinessUserId });
}
}
// Auto-detect team configuration if not already checked
if (!teamConfigChecked) {
cachedTeamConfig = await getTeamConfiguration();
teamConfigChecked = true;
}
// Determine if we should use team space
const useTeamSpace = options?.useTeamSpace ?? (cachedTeamConfig?.hasTeamSpace || false);
// Create client configuration
const clientConfig = {
accessToken: token,
selectUser: process.env.DROPBOX_BUSINESS_USER_ID || cachedBusinessUserId || undefined
};
// Add team space path root if team space is enabled and requested
if (useTeamSpace && cachedTeamConfig?.hasTeamSpace) {
clientConfig.pathRoot = {
".tag": "root",
"root": {
".tag": "team_space_shared"
}
};
log.info('Using team space path root for API calls');
}
const client = new Dropbox(clientConfig);
return client;
}
// Helper function to detect team configuration and team space support
async function getTeamConfiguration() {
try {
const token = config.dropbox.accessToken || await getValidAccessToken();
// Check if team has team space enabled
const response = await fetch('https://api.dropboxapi.com/2/team/features/get_values', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
features: [
{ ".tag": "has_team_shared_dropbox" },
{ ".tag": "has_team_file_events" },
{ ".tag": "has_team_selective_sync" }
]
})
});
if (!response.ok) {
log.warn('Could not fetch team features', { status: response.status });
return null;
}
const featuresData = await response.json();
const hasTeamSpace = featuresData.values?.some((feature) => feature['.tag'] === 'has_team_shared_dropbox' && feature.has_team_shared_dropbox?.enabled === true);
log.info('Team configuration detected', { hasTeamSpace });
if (hasTeamSpace) {
// Get team space ID if available
try {
const namespacesResponse = await fetch('https://api.dropboxapi.com/2/users/get_space_usage', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
}
});
if (namespacesResponse.ok) {
const namespaceData = await namespacesResponse.json();
// Extract team space ID from the response if available
log.info('Space usage retrieved for team space detection');
}
}
catch (e) {
log.warn('Could not get team space details', { error: e });
}
}
return { hasTeamSpace };
}
catch (error) {
log.warn('Could not determine team configuration', { error });
return null;
}
}
// Helper function to get business team member ID for the current user
async function getBusinessUserId() {
try {
const token = config.dropbox.accessToken || await getValidAccessToken();
// Try to get team members to find the target user
const response = await fetch('https://api.dropboxapi.com/2/team/members/list_v2', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ limit: 100 })
});
if (!response.ok) {
// Not a business account or no permission
return null;
}
const teamData = await response.json();
// Look for olivier@surgemgmt.com specifically, or first admin user
const targetUser = teamData.members?.find((member) => member.profile.email === 'olivier@surgemgmt.com') || teamData.members?.find((member) => member.profile.membership_type['.tag'] === 'full') || teamData.members?.[0];
if (targetUser) {
log.info('Found business user', {
email: targetUser.profile.email,
userId: targetUser.profile.team_member_id
});
return targetUser.profile.team_member_id;
}
return null;
}
catch (error) {
log.warn('Could not determine business user ID', { error });
return null;
}
}
// Helper function to format paths for Dropbox API
function formatDropboxPath(path) {
if (!path || path === '/')
return '';
return '/' + path.replace(/^\/+|\/+$/g, '');
}
// Helper function to determine if a path should use team space
function shouldUseTeamSpace(path) {
if (!cachedTeamConfig?.hasTeamSpace)
return false;
// Common team space indicators
const teamSpaceIndicators = [
'/Surge', // Specific team folder mentioned by user
'/team', // Common team folder patterns
'/shared',
'/company',
'/organization',
'/projects'
];
const normalizedPath = path.toLowerCase();
return teamSpaceIndicators.some(indicator => normalizedPath.startsWith(indicator.toLowerCase()) ||
normalizedPath.includes(indicator.toLowerCase()));
}
// Get a Dropbox client with automatic team space detection based on path
async function getDropboxClientForPath(path) {
const useTeamSpace = shouldUseTeamSpace(path);
return getDropboxClient({ useTeamSpace });
}
async function listFiles(path) {
try {
const client = await getDropboxClientForPath(path);
const response = await client.filesListFolder({
path: formatDropboxPath(path),
recursive: false,
include_media_info: true,
include_deleted: false,
include_has_explicit_shared_members: false,
include_mounted_folders: true,
include_non_downloadable_files: true
});
// Convert entries to a simpler format for listing
const files = response.result.entries.map(entry => ({
'.tag': entry['.tag'],
name: entry.name,
path_display: entry.path_display,
size: entry['.tag'] === 'file' ? entry.size : 0,
server_modified: entry['.tag'] === 'file' ? entry.server_modified : null,
client_modified: entry['.tag'] === 'file' ? entry.client_modified : null
}));
return {
content: [{
type: 'text',
text: JSON.stringify(files, null, 2)
}],
};
}
catch (error) {
handleDropboxError(error);
}
}
async function uploadFile(path, content) {
try {
const buffer = Buffer.from(content, 'base64');
const client = await getDropboxClientForPath(path);
await client.filesUpload({
path: formatDropboxPath(path),
contents: buffer,
mode: { '.tag': 'overwrite' },
});
return {
content: [{
type: 'text',
text: `File uploaded successfully to ${path}`,
}],
};
}
catch (error) {
handleDropboxError(error);
}
}
async function downloadFile(path) {
try {
const client = await getDropboxClientForPath(path);
// Get metadata first to check if it's a file or folder
const metadata = await client.filesGetMetadata({
path: formatDropboxPath(path)
});
if (metadata.result['.tag'] === 'folder') {
// For folders, get contents and format as ResourceContent
const folderContents = await client.filesListFolder({
path: formatDropboxPath(path),
recursive: false,
include_media_info: true,
include_deleted: false,
include_has_explicit_shared_members: false,
include_mounted_folders: true,
include_non_downloadable_files: true
});
// For folders, return a list of ResourceReference objects
const references = folderContents.result.entries.map(entry => ({
type: entry['.tag'] === 'folder' ? 'collection' : 'inline',
uri: `dbx://${entry.path_display}`,
content: {
uri: `dbx://${entry.path_display}`,
mimeType: entry['.tag'] === 'folder' ?
'application/x-directory' :
getMimeType(entry.name),
content: '', // Content is loaded on demand when accessing individual items
encoding: 'utf8',
metadata: {
size: entry['.tag'] === 'file' && entry.size ? entry.size : 0,
path: entry.path_display || path,
modified: entry['.tag'] === 'file' && (entry.server_modified || entry.client_modified) ?
entry.server_modified || entry.client_modified :
new Date().toISOString()
}
}
}));
return {
content: [{
type: 'text',
text: JSON.stringify(references, null, 2)
}],
};
}
// For files, proceed with download and format as ResourceContent
const response = await client.filesDownload({
path: formatDropboxPath(path)
});
const fileData = response.result;
if (!fileData?.fileBinary) {
throw new Error('No file data received from Dropbox');
}
// For files, return a single ResourceReference with base64 encoded content
const reference = {
type: 'inline',
uri: `dbx://${path}`,
content: {
uri: `dbx://${path}`,
mimeType: fileData.name ? getMimeType(fileData.name) : 'application/octet-stream',
content: fileData.fileBinary.toString('base64'),
encoding: 'base64',
metadata: {
size: fileData.size || 0,
path: fileData.path_display || path,
modified: fileData.server_modified || fileData.client_modified || new Date().toISOString()
}
}
};
return {
content: [{
type: 'text',
text: JSON.stringify(reference, null, 2)
}],
};
}
catch (error) {
handleDropboxError(error);
}
}
// Helper function to determine MIME type
function getMimeType(filename) {
if (!filename)
return 'application/octet-stream';
const ext = filename.split('.').pop()?.toLowerCase();
if (!ext)
return 'application/octet-stream';
const mimeTypes = {
'txt': 'text/plain',
'pdf': 'application/pdf',
'jpg': 'image/jpeg',
'jpeg': 'image/jpeg',
'png': 'image/png',
'gif': 'image/gif',
'doc': 'application/msword',
'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'xls': 'application/vnd.ms-excel',
'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'json': 'application/json',
'md': 'text/markdown',
'html': 'text/html',
'css': 'text/css',
'js': 'application/javascript',
'ts': 'application/typescript',
'zip': 'application/zip',
'mp3': 'audio/mpeg',
'mp4': 'video/mp4',
};
return mimeTypes[ext] || 'application/octet-stream';
}
// Keep track of delete operations for rate limiting
const deleteOperations = [];
// Helper function to check if path is allowed
function isPathAllowed(path) {
const normalizedPath = formatDropboxPath(path);
// First check if path is in blocked paths - this takes precedence
const isBlocked = config.safety.blockedPaths.some((blockedPath) => {
const normalizedBlockedPath = formatDropboxPath(blockedPath);
// Exact match or subdirectory of blocked path
return normalizedPath === normalizedBlockedPath ||
normalizedPath.startsWith(normalizedBlockedPath + '/');
});
if (isBlocked) {
log.warn('Attempted access to blocked path', { path: normalizedPath });
return false;
}
// Then check if path is in allowed paths
const isAllowed = config.safety.allowedPaths.some((allowedPath) => {
const normalizedAllowedPath = formatDropboxPath(allowedPath);
// Exact match or subdirectory of allowed path
return normalizedPath === normalizedAllowedPath ||
normalizedPath.startsWith(normalizedAllowedPath + '/');
});
if (!isAllowed) {
log.warn('Path not in allowed paths', { path: normalizedPath });
}
return isAllowed;
}
// Helper function to check delete rate limit
function checkDeleteRateLimit(userId) {
const now = new Date();
const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
// Clean up old operations
const recentOperations = deleteOperations.filter(op => op.timestamp > oneDayAgo);
deleteOperations.length = 0;
deleteOperations.push(...recentOperations);
// Count operations for this user in last 24 hours
const userOperations = deleteOperations.filter(op => op.userId === userId);
return userOperations.length < config.safety.maxDeletesPerDay;
}
// Helper function to generate version ID
function generateVersionId() {
return `v${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
async function safeDeleteItem(options) {
try {
const { path, userId, skipConfirmation = false, retentionDays = config.safety.retentionDays, reason = '', permanent = false } = options;
// Validate path
if (!isPathAllowed(path)) {
const normalizedPath = formatDropboxPath(path);
const isBlocked = config.safety.blockedPaths.some((blockedPath) => {
const normalizedBlockedPath = formatDropboxPath(blockedPath);
return normalizedPath === normalizedBlockedPath ||
normalizedPath.startsWith(normalizedBlockedPath + '/');
});
throw new McpError(ErrorCode.InvalidParams, isBlocked ?
`Path ${path} is blocked and cannot be deleted` :
`Path ${path} is not in allowed paths for deletion`);
}
// Check rate limit
if (!checkDeleteRateLimit(userId)) {
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.info('Permanent deletion', {
path: normalizedPath,
userId,
reason,
metadata: metadata.result
});
return {
content: [{
type: 'text',
text: JSON.stringify({
status: 'success',
operation: 'permanent_delete',
path: normalizedPath
}, null, 2)
}]
};
}
// Soft deletion - move to recycle bin
const versionId = generateVersionId();
const recyclePath = `${config.safety.recycleBinPath}/${versionId}_${path.split('/').pop()}`;
// Create recycle bin if it doesn't exist
try {
await client.filesCreateFolderV2({
path: config.safety.recycleBinPath,
autorename: false
});
}
catch (error) {
// Ignore error if folder already exists
}
// Move file to recycle bin
await client.filesMoveV2({
from_path: normalizedPath,
to_path: recyclePath,
autorename: true
});
// Save metadata for version history
const versionMetadata = {
id: versionId,
originalPath: normalizedPath,
deletedAt: new Date().toISOString(),
expiresAt: new Date(Date.now() + retentionDays * 24 * 60 * 60 * 1000).toISOString(),
metadata: metadata.result,
userId,
reason
};
// Track delete operation for rate limiting
deleteOperations.push({
timestamp: new Date(),
path: normalizedPath,
userId
});
// Log soft deletion
config.auditLogger.info('Soft deletion', {
...versionMetadata,
recyclePath
});
return {
content: [{
type: 'text',
text: JSON.stringify({
status: 'success',
operation: 'soft_delete',
versionId,
originalPath: normalizedPath,
recyclePath,
expiresAt: versionMetadata.expiresAt
}, null, 2)
}]
};
}
catch (error) {
// Log deletion error
config.auditLogger.error('Deletion error', {
path: options.path,
userId: options.userId,
error: error.message,
stack: error.stack
});
handleDropboxError(error);
}
}
// Keep original deleteItem for backward compatibility
async function deleteItem(path) {
return safeDeleteItem({
path,
userId: 'legacy_user',
skipConfirmation: true,
permanent: true
});
}
async function createFolder(path) {
try {
const client = await getDropboxClientForPath(path);
await client.filesCreateFolderV2({
path: formatDropboxPath(path),
autorename: false,
});
return {
content: [{
type: 'text',
text: `Created folder at ${path}`,
}],
};
}
catch (error) {
handleDropboxError(error);
}
}
async function copyItem(fromPath, toPath) {
try {
// Use team space if either path suggests it
const useTeamSpace = shouldUseTeamSpace(fromPath) || shouldUseTeamSpace(toPath);
const client = await getDropboxClient({ useTeamSpace });
await client.filesCopyV2({
from_path: formatDropboxPath(fromPath),
to_path: formatDropboxPath(toPath),
allow_shared_folder: true,
autorename: false,
allow_ownership_transfer: false,
});
return {
content: [{
type: 'text',
text: `Copied from ${fromPath} to ${toPath}`,
}],
};
}
catch (error) {
handleDropboxError(error);
}
}
async function moveItem(fromPath, toPath) {
try {
// Use team space if either path suggests it
const useTeamSpace = shouldUseTeamSpace(fromPath) || shouldUseTeamSpace(toPath);
const client = await getDropboxClient({ useTeamSpace });
await client.filesMoveV2({
from_path: formatDropboxPath(fromPath),
to_path: formatDropboxPath(toPath),
allow_shared_folder: true,
autorename: false,
allow_ownership_transfer: false,
});
return {
content: [{
type: 'text',
text: `Moved from ${fromPath} to ${toPath}`,
}],
};
}
catch (error) {
handleDropboxError(error);
}
}
async function getFileMetadata(path) {
try {
const client = await getDropboxClientForPath(path);
const response = await client.filesGetMetadata({
path: formatDropboxPath(path),
include_media_info: true,
include_deleted: false,
include_has_explicit_shared_members: true,
});
return {
content: [{
type: 'text',
text: JSON.stringify(response.result, null, 2),
}],
};
}
catch (error) {
handleDropboxError(error);
}
}
function getFileCategory(metadata) {
if (!metadata?.name)
return 'other';
const extension = metadata.name.split('.').pop()?.toLowerCase();
const mimeType = metadata.media_info?.metadata?.mime_type;
if (mimeType?.startsWith('image/') || ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(extension)) {
return 'image';
}
if (['doc', 'docx', 'rtf', 'odt'].includes(extension)) {
return 'document';
}
if (extension === 'pdf' || mimeType === 'application/pdf') {
return 'pdf';
}
if (['xls', 'xlsx', 'csv', 'ods'].includes(extension)) {
return 'spreadsheet';
}
if (['ppt', 'pptx', 'odp'].includes(extension)) {
return 'presentation';
}
if (mimeType?.startsWith('audio/') || ['mp3', 'wav', 'ogg', 'm4a'].includes(extension)) {
return 'audio';
}
if (mimeType?.startsWith('video/') || ['mp4', 'avi', 'mov', 'wmv'].includes(extension)) {
return 'video';
}
if (metadata['.tag'] === 'folder') {
return 'folder';
}
return 'other';
}
async function searchFiles(options) {
try {
const client = await getDropboxClientForPath(options.path || '');
const response = await client.filesSearchV2({
query: options.query,
options: {
path: formatDropboxPath(options.path || ''),
max_results: Math.min(Math.max(1, options.maxResults || 20), 1000),
file_status: { '.tag': 'active' },
filename_only: !options.includeContentMatch,
},
match_field_options: {
include_highlights: true,
},
});
let matches = response.result.matches
.map(match => ({
metadata: match.metadata.metadata,
match_type: match.match_type,
highlights: match.highlight_spans,
}))
.filter(match => {
// Apply filters
if (options.fileExtensions?.length && match.metadata?.name) {
const ext = match.metadata.name.split('.').pop()?.toLowerCase();
if (!ext || !options.fileExtensions.includes(ext))
return false;
}
if (options.fileCategories?.length) {
const category = getFileCategory(match.metadata);
if (!options.fileCategories.includes(category))
return false;
}
if (options.dateRange && match.metadata?.['.tag'] === 'file' && match.metadata?.server_modified) {
const modTime = new Date(match.metadata.server_modified).getTime();
const startTime = options.dateRange.start ? new Date(options.dateRange.start).getTime() : 0;
const endTime = options.dateRange.end ? new Date(options.dateRange.end).getTime() : Infinity;
if (modTime < startTime || modTime > endTime)
return false;
}
return true;
});
// Apply sorting
if (options.sortBy && options.sortBy !== 'relevance') {
matches.sort((a, b) => {
if (options.sortBy === 'last_modified_time' && a.metadata && b.metadata) {
const timeA = new Date(a.metadata.server_modified || 0).getTime();
const timeB = new Date(b.metadata.server_modified || 0).getTime();
return options.order === 'asc' ? timeA - timeB : timeB - timeA;
}
if (options.sortBy === 'file_size' && a.metadata && b.metadata) {
const sizeA = a.metadata['.tag'] === 'file' && a.metadata.size ? a.metadata.size : 0;
const sizeB = b.metadata['.tag'] === 'file' && b.metadata.size ? b.metadata.size : 0;
return options.order === 'asc' ? sizeA - sizeB : sizeB - sizeA;
}
return 0;
});
}
else if (options.order === 'asc') {
matches.reverse();
}
return {
content: [{
type: 'text',
text: JSON.stringify({
total_results: matches.length,
search_criteria: {
query: options.query,
path: options.path,
file_extensions: options.fileExtensions,
file_categories: options.fileCategories,
date_range: options.dateRange,
include_content_match: options.includeContentMatch,
sort_by: options.sortBy,
order: options.order,
},
matches: matches.map(match => ({
...match,
category: getFileCategory(match.metadata),
})),
}, null, 2),
}],
};
}
catch (error) {
handleDropboxError(error);
}
}
async function getSharingLink(path) {
try {
const client = await getDropboxClientForPath(path);
try {
const response = await client.sharingCreateSharedLinkWithSettings({
path: formatDropboxPath(path),
settings: {
requested_visibility: { '.tag': 'public' },
audience: { '.tag': 'public' },
access: { '.tag': 'viewer' },
},
});
return {
content: [{
type: 'text',
text: JSON.stringify({
url: response.result.url,
path: response.result.path_lower,
visibility: response.result.link_permissions?.resolved_visibility?.['.tag'] || 'unknown',
}, null, 2),
}],
};
}
catch (error) {
// Handle case where link already exists
if (error?.error?.error_summary?.includes('shared_link_already_exists')) {
const listResponse = await client.sharingListSharedLinks({
path: formatDropboxPath(path),
direct_only: true,
});
if (listResponse.result.links?.[0]) {
const link = listResponse.result.links[0];
return {
content: [{
type: 'text',
text: JSON.stringify({
url: link.url,
path: link.path_lower,
visibility: link.link_permissions?.resolved_visibility?.['.tag'] || 'unknown',
note: 'Existing shared link retrieved',
}, null, 2),
}],
};
}
}
throw error;
}
}
catch (error) {
handleDropboxError(error);
}
}
async function getAccountInfo() {
try {
const client = await getDropboxClient(); // Account info doesn't need team space
const response = await client.usersGetCurrentAccount();
const accountInfo = {
account_id: response.result.account_id,
name: response.result.name,
email: response.result.email,
email_verified: response.result.email_verified,
profile_photo_url: response.result.profile_photo_url,
country: response.result.country,
locale: response.result.locale,
team: response.result.team ? {
name: response.result.team.name,
team_id: response.result.team.id,
} : null,
account_type: response.result.account_type['.tag'] || 'unknown',
};
return {
content: [{
type: 'text',
text: JSON.stringify(accountInfo, null, 2),
}],
};
}
catch (error) {
handleDropboxError(error);
}
}
async function getFileContent(path) {
try {
const client = await getDropboxClientForPath(path);
// Get metadata first to verify it's a file
const metadata = await client.filesGetMetadata({
path: formatDropboxPath(path)
});
if (metadata.result['.tag'] !== 'file') {
throw new McpError(ErrorCode.InvalidParams, 'Cannot get content of a folder');
}
const response = await client.filesDownload({
path: formatDropboxPath(path)
});
const fileData = response.result;
if (!fileData?.fileBinary) {
throw new Error('No file data received from Dropbox');
}
const reference = {
type: 'inline',
uri: `dbx://${path}`,
content: {
uri: `dbx://${path}`,
mimeType: metadata.result.name ? getMimeType(metadata.result.name) : 'application/octet-stream',
content: fileData.fileBinary.toString('base64'),
encoding: 'base64',
metadata: {
size: metadata.result.size || 0,
path: metadata.result.path_display || path,
modified: metadata.result.server_modified || metadata.result.client_modified || new Date().toISOString()
}
}
};
return {
content: [{
type: 'text',
text: JSON.stringify(reference, null, 2)
}],
};
}
catch (error) {
handleDropboxError(error);
}
}
// Get raw file metadata for processing (used by PDF analyzer and indexer)
async function getRawFileMetadata(path) {
try {
const client = await getDropboxClientForPath(path);
const response = await client.filesGetMetadata({
path: formatDropboxPath(path),
include_media_info: true,
include_deleted: false,
include_has_explicit_shared_members: true,
});
return response.result;
}
catch (error) {
handleDropboxError(error);
}
}
// Download file as Buffer for processing (used by PDF analyzer)
async function downloadFileAsBuffer(path) {
try {
const client = await getDropboxClientForPath(path);
// Download the file
const response = await client.filesDownload({
path: formatDropboxPath(path)
});
const fileData = response.result;
if (!fileData?.fileBinary) {
throw new Error('No file data received from Dropbox');
}
return fileData.fileBinary;
}
catch (error) {
handleDropboxError(error);
}
}
async function getTeamInfo() {
try {
const client = await getDropboxClient(); // Use basic client to get team info
// Get current account to check if it's a business account
const accountResponse = await client.usersGetCurrentAccount();
const account = accountResponse.result;
let teamInfo = {
account_type: account.account_type['.tag'],
is_business_account: !!account.team,
};
if (account.team) {
teamInfo.team = {
name: account.team.name,
team_id: account.team.id,
};
// Get team configuration if available
if (cachedTeamConfig) {
teamInfo.team_space_enabled = cachedTeamConfig.hasTeamSpace;
}
else {
// Force check team configuration
cachedTeamConfig = await getTeamConfiguration();
teamConfigChecked = true;
teamInfo.team_space_enabled = cachedTeamConfig?.hasTeamSpace || false;
}
// Get business user ID
if (cachedBusinessUserId) {
teamInfo.business_user_id = cachedBusinessUserId;
}
else {
cachedBusinessUserId = await getBusinessUserId();
businessUserIdChecked = true;
teamInfo.business_user_id = cachedBusinessUserId;
}
teamInfo.team_folder_access = {
can_access_surge: shouldUseTeamSpace('/Surge'),
team_space_indicators: ['/Surge', '/team', '/shared', '/company', '/organization', '/projects'],
recommended_approach: teamInfo.team_space_enabled
? 'Use team space paths like /Surge/internal for team folders'
: 'Team folders may be accessible via member-specific paths'
};
}
return {
content: [{
type: 'text',
text: JSON.stringify(teamInfo, null, 2)
}],
};
}
catch (error) {
handleDropboxError(error);
}
}
export { listFiles, uploadFile, downloadFile, downloadFileAsBuffer, getRawFileMetadata, deleteItem, safeDeleteItem, createFolder, copyItem, moveItem, getFileMetadata, searchFiles, getSharingLink, getAccountInfo, getFileContent, getTeamInfo, };
//# sourceMappingURL=dbx-api-old.js.map