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