Skip to main content
Glama

Dropbox MCP Server

dbx-server.ts13 kB
import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, McpError, ErrorCode, CallToolResult, CallToolRequest } from '@modelcontextprotocol/sdk/types.js'; import { getValidAccessToken, initializeTokenData, refreshAccessToken } from './auth.js'; import { handleListPrompts, handleGetPrompt } from './prompt-handler.js'; import { handleListResources, handleReadResource } from './resource-handler.js'; import { toolDefinitions } from './tool-definitions.js'; import { config, log } from './config.js'; import * as dbxApi from './dbx-api.js'; import axios, { AxiosError } from 'axios'; import * as fs from 'fs'; import { z } from 'zod'; import { SearchOptions } from './types.js'; // Define resource templates const resourceTemplates = [ { uriTemplate: 'dbx://{path}', name: 'Dbx Item', description: 'Access any file or folder in Dropbox by path', parameters: { path: { description: 'Path to the file or folder', required: true, type: 'string' } } }, { uriTemplate: 'dbx:///shared/{share_id}', name: 'Shared Dbx Item', description: 'Access a shared Dropbox item by its share ID', parameters: { share_id: { description: 'Shared item identifier', required: true, type: 'string' } } } ]; export default class DbxServer { private server: Server; private transport: StdioServerTransport; constructor() { this.transport = new StdioServerTransport(); this.server = new Server( { name: 'dbx-mcp-server', version: '0.1.0' }, { capabilities: { tools: { tools: toolDefinitions } } } ); this.setupHandlers(); } private setupHandlers() { // Tool handlers this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: toolDefinitions })); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { try { log.info('Received tool request:', { method: request.method, params: request.params }); // Force token refresh before handling each request await this.ensureValidToken(); // Verify authentication if (!process.env.DROPBOX_ACCESS_TOKEN && !await getValidAccessToken()) { throw new McpError( ErrorCode.InvalidRequest, 'No valid access token available. Please authenticate first.' ); } // Log request (without sensitive data) log.info('Processing tool request:', { tool: request.params.name, args: this.sanitizeArgs(request.params.arguments) }); // Handle tool requests const result = await (async () => { switch (request.params.name) { case 'list_files': log.info('Handling list_files request'); return await this.listFiles(request.params.arguments); case 'upload_file': log.info('Handling upload_file request'); return await this.uploadFile(request.params.arguments); case 'download_file': log.info('Handling download_file request'); return await this.downloadFile(request.params.arguments); case 'safe_delete_item': log.info('Handling safe_delete_item request'); return await this.safeDeleteItem(request.params.arguments); case 'delete_item': // Legacy delete operation - logs a warning and uses safe delete with default settings log.warn('Legacy delete operation used', { path: request.params.arguments?.path }); return await this.safeDeleteItem({ path: String(request.params.arguments?.path), userId: 'legacy_user', skipConfirmation: true, permanent: true }); case 'create_folder': log.info('Handling create_folder request'); return await this.createFolder(request.params.arguments); case 'copy_item': log.info('Handling copy_item request'); return await this.copyItem(request.params.arguments); case 'move_item': log.info('Handling move_item request'); return await this.moveItem(request.params.arguments); case 'get_file_metadata': log.info('Handling get_file_metadata request'); return await this.getFileMetadata(request.params.arguments); case 'search_file_db': { log.info('Handling search_file_db request'); const searchOptions: SearchOptions = { query: String(request.params.arguments?.query), path: String(request.params.arguments?.path || ''), maxResults: Number(request.params.arguments?.max_results || 20), fileExtensions: request.params.arguments?.file_extensions as string[] | undefined, fileCategories: request.params.arguments?.file_categories as string[] | undefined, dateRange: request.params.arguments?.date_range as { start: string; end: string } | undefined, includeContentMatch: Boolean(request.params.arguments?.include_content_match), sortBy: (request.params.arguments?.sort_by as SearchOptions['sortBy']) || 'relevance', order: (request.params.arguments?.order as SearchOptions['order']) || 'desc' }; return await this.searchFiles(searchOptions); } case 'get_sharing_link': log.info('Handling get_sharing_link request'); return await this.getSharingLink(request.params.arguments); case 'get_account_info': log.info('Handling get_account_info request'); return await this.getAccountInfo(); case 'get_file_content': log.info('Handling get_file_content request'); return await this.getFileContent(request.params.arguments); default: log.error('Unknown tool requested:', { tool: request.params.name }); throw new McpError( ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}` ); } })(); log.info('Tool request completed successfully:', { tool: request.params.name, result }); return { content: result.content, _meta: {} }; } catch (error) { log.error('Error handling tool request:', { error: error instanceof Error ? error.message : 'Unknown error', stack: error instanceof Error ? error.stack : undefined }); if (error instanceof McpError) { throw error; } // Map Dropbox API errors to MCP errors if (axios.isAxiosError(error)) { const axiosError = error as AxiosError; const status = axiosError.response?.status; const data = axiosError.response?.data as { error?: any }; if (data?.error && typeof data.error === 'string') { throw new McpError( ErrorCode.InternalError, `Dropbox API error: ${data.error}` ); } if (status === 401) { throw new McpError( ErrorCode.InvalidRequest, `Dropbox API authentication error: ${axiosError.message}` ); } throw new McpError( ErrorCode.InternalError, `Dropbox API error: ${axiosError.message}` ); } throw new McpError( ErrorCode.InternalError, error instanceof Error ? error.message : String(error) ); } }); } private sanitizeArgs(args: any): any { if (!args) return args; const sanitized = { ...args }; // Remove sensitive data from logs if (sanitized.content) sanitized.content = '[CONTENT]'; return sanitized; } private async initialize() { await initializeTokenData(); log.info('Dropbox token data initialized successfully'); try { // Verify access token await getValidAccessToken(); log.info('Successfully verified access token'); } catch (error) { log.warn('Failed to verify access token:', error); // Continue initialization } } // Add a simple method to force refresh the token before any API calls private async ensureValidToken(): Promise<void> { try { log.info('Forcibly refreshing access token before request'); await refreshAccessToken(); } catch (err) { log.error('Failed to refresh token:', err); // Continue with request even if refresh fails } } async listFiles(params: any): Promise<any> { try { await this.ensureValidToken(); // Get the path from params const path = params.path || ''; // For root directory, use empty string instead of "/" const normalizedPath = path === '/' ? '' : path; log.info(`Directly listing files at path: "${normalizedPath}" (original: "${path}")`); // Get access token - prioritize environment variable const accessToken = process.env.DROPBOX_ACCESS_TOKEN || await getValidAccessToken(); // Make a direct API call const response = await axios.post( 'https://api.dropboxapi.com/2/files/list_folder', { path: normalizedPath }, { headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json' }, timeout: 15000 } ); // Format the response const entries = response.data.entries.map((entry: any) => ({ name: entry.name, path: entry.path_display, type: entry['.tag'], size: entry.size, isFolder: entry['.tag'] === 'folder' })); return { content: [{ type: 'text', text: JSON.stringify(entries, null, 2) }] }; } catch (error) { log.error('Error listing files:', error); throw error; } } async uploadFile(params: any): Promise<any> { await this.ensureValidToken(); return dbxApi.uploadFile(params.path, params.content); } async downloadFile(params: any): Promise<any> { await this.ensureValidToken(); return dbxApi.downloadFile(params.path); } async deleteItem(params: any): Promise<any> { await this.ensureValidToken(); return dbxApi.deleteItem(params.path); } async safeDeleteItem(params: any): Promise<any> { await this.ensureValidToken(); return dbxApi.safeDeleteItem({ path: params.path, userId: params.userId, reason: params.reason, permanent: params.permanent, retentionDays: params.retentionDays, skipConfirmation: params.skipConfirmation }); } async createFolder(params: any): Promise<any> { await this.ensureValidToken(); return dbxApi.createFolder(params.path); } async copyItem(params: any): Promise<any> { await this.ensureValidToken(); return dbxApi.copyItem(params.from_path, params.to_path); } async moveItem(params: any): Promise<any> { await this.ensureValidToken(); return dbxApi.moveItem(params.from_path, params.to_path); } async getFileMetadata(params: any): Promise<any> { await this.ensureValidToken(); return dbxApi.getFileMetadata(params.path); } async searchFiles(params: any): Promise<any> { await this.ensureValidToken(); return dbxApi.searchFiles(params); } async getSharingLink(params: any): Promise<any> { await this.ensureValidToken(); return dbxApi.getSharingLink(params.path); } async getAccountInfo(): Promise<any> { await this.ensureValidToken(); return dbxApi.getAccountInfo(); } async getFileContent(params: any): Promise<any> { await this.ensureValidToken(); return dbxApi.getFileContent(params.path); } // Public method to run the server async run(): Promise<void> { try { await this.initialize(); await this.server.connect(this.transport); log.info('Dbx MCP server running on stdio'); } catch (error) { log.error('Failed to start server:', { error: error instanceof Error ? error.message : 'Unknown error', stack: error instanceof Error ? error.stack : undefined }); throw error; } } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Albiemark/dbx-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server