UNPKG

dbx-mcp-server

Version:

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

178 lines 7.75 kB
import { getValidAccessToken } from '../auth.js'; import { config, log } from '../config.js'; import { Dropbox } from 'dropbox'; // 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; // Helper function to detect team configuration and team space support export async function getTeamConfiguration() { try { const token = config.dropbox.accessToken || await getValidAccessToken(); // Try to identify a representative business user first (needed for team access tokens) const businessUserId = await getCachedBusinessUserId(); // First, check if this is a business account using the official SDK. If we have a business user // ID available, include it via the "selectUser" option so that the call operates in the context // of an actual team member rather than the whole team (required for certain team access tokens). const sdkClient = new Dropbox({ accessToken: token, selectUser: businessUserId || undefined }); let accountData; try { accountData = await sdkClient.usersGetCurrentAccount(); log.debug('usersGetCurrentAccount keys', { keys: Object.keys(accountData) }); log.debug('full accountData', { accountData }); } catch (err) { log.warn('Could not get account info for team detection', { error: err?.error || err?.message || err }); return null; } if (!accountData.team) { // Some responses (especially when using team access tokens with select_user) may omit the // "team" field even though the user belongs to a business team. If we have discovered a // businessUserId earlier, treat this as a business account and continue, otherwise bail out. if (!businessUserId) { log.info('Account appears to be a personal account'); return { hasTeamSpace: false }; } log.info('Account did not include team info but business user detected – assuming business account', { businessUserId }); } else { log.info('Business account detected', { teamId: accountData.team.id, teamName: accountData.team.name }); } // Extract the team shared root namespace ID if available (team space root) const rootInfo = (accountData.result?.root_info) || accountData.root_info; const rootNamespaceId = rootInfo?.root_namespace_id; if (rootNamespaceId) { log.debug('Detected team space namespace ID', { rootNamespaceId }); } // Check team features to see if team shared dropbox is enabled const featuresResponse = 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" } ] }) }); if (!featuresResponse.ok) { log.warn('Could not fetch team features', { status: featuresResponse.status }); // Assume team space is available for business accounts return { hasTeamSpace: true, teamId: accountData.team?.id, teamName: accountData.team?.name, teamSpaceId: rootNamespaceId }; } const featuresData = await featuresResponse.json(); const hasTeamSpace = featuresData.values?.some((feature) => feature['.tag'] === 'has_team_shared_dropbox' && feature.has_team_shared_dropbox?.enabled === true); let finalHasTeamSpace; if (hasTeamSpace !== undefined) { finalHasTeamSpace = hasTeamSpace; } else { // fall back to rootInfo tag detection finalHasTeamSpace = rootInfo?.['.tag'] === 'team'; } log.info('Team configuration detected', { hasTeamSpace: finalHasTeamSpace, teamId: accountData.team?.id, teamName: accountData.team?.name, teamSpaceId: rootNamespaceId }); return { hasTeamSpace: finalHasTeamSpace, teamId: accountData.team?.id, teamName: accountData.team?.name, teamSpaceId: rootNamespaceId }; } catch (error) { log.warn('Could not determine team configuration', { error }); return null; } } // Helper function to get business team member ID for the current user export 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; } } // Get cached or fetch team configuration export async function getCachedTeamConfig() { if (!teamConfigChecked) { cachedTeamConfig = await getTeamConfiguration(); teamConfigChecked = true; } return cachedTeamConfig; } // Get cached or fetch business user ID export async function getCachedBusinessUserId() { 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 }); } } return cachedBusinessUserId; } // Helper function to determine if a path should use team space export function shouldUseTeamSpace(path, teamConfig) { if (!teamConfig?.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())); } //# sourceMappingURL=team-config.js.map