Skip to main content
Glama
dbx-server.ts8.13 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 } from '@modelcontextprotocol/sdk/types.js'; import { getValidAccessToken } 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'; // 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' } } } ]; interface SearchOptions { query: string; path?: string; maxResults?: number; fileExtensions?: string[]; fileCategories?: string[]; dateRange?: { start: string; end: string; }; includeContentMatch?: boolean; sortBy?: 'relevance' | 'last_modified_time' | 'file_size'; order?: 'asc' | 'desc'; } class DbxServer { private server: Server; constructor() { this.server = new Server( { name: 'dbx-mcp-server', version: '0.1.0', }, { capabilities: { resources: { types: ["dbx-file", "dbx-folder"], }, tools: { tools: toolDefinitions }, prompts: { listChanged: true } }, } ); this.setupHandlers(); this.setupPromptHandlers(); // Error handling this.server.onerror = (error: Error & { code?: string }) => { log.error('Server error:', { error: error.message, code: error.code, stack: error.stack }); }; } private setupPromptHandlers() { this.server.setRequestHandler(ListPromptsRequestSchema, handleListPrompts); this.server.setRequestHandler(GetPromptRequestSchema, handleGetPrompt); } private setupHandlers() { // Resource handlers this.server.setRequestHandler(ListResourcesRequestSchema, handleListResources); this.server.setRequestHandler(ReadResourceRequestSchema, handleReadResource); this.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({ resourceTemplates })); // Tool handlers this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: toolDefinitions })); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { // Verify authentication if (!config.dropbox.accessToken && !await getValidAccessToken()) { throw new McpError( ErrorCode.InvalidRequest, 'No valid access token available. Please authenticate first.' ); } // Log request (without sensitive data) log.info('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': return await dbxApi.listFiles(String(request.params.arguments?.path || '')); case 'upload_file': return await dbxApi.uploadFile( String(request.params.arguments?.path), String(request.params.arguments?.content) ); case 'download_file': return await dbxApi.downloadFile(String(request.params.arguments?.path)); case 'safe_delete_item': return await dbxApi.safeDeleteItem({ path: String(request.params.arguments?.path), userId: String(request.params.arguments?.userId), skipConfirmation: Boolean(request.params.arguments?.skipConfirmation), retentionDays: Number(request.params.arguments?.retentionDays || config.safety.retentionDays), reason: String(request.params.arguments?.reason || ''), permanent: Boolean(request.params.arguments?.permanent) }); 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 dbxApi.safeDeleteItem({ path: String(request.params.arguments?.path), userId: 'legacy_user', skipConfirmation: true, permanent: true }); case 'create_folder': return await dbxApi.createFolder(String(request.params.arguments?.path)); case 'copy_item': return await dbxApi.copyItem( String(request.params.arguments?.from_path), String(request.params.arguments?.to_path) ); case 'move_item': return await dbxApi.moveItem( String(request.params.arguments?.from_path), String(request.params.arguments?.to_path) ); case 'get_file_metadata': return await dbxApi.getFileMetadata(String(request.params.arguments?.path)); case 'search_file_db': { 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 dbxApi.searchFiles(searchOptions); } case 'get_sharing_link': return await dbxApi.getSharingLink(String(request.params.arguments?.path)); case 'get_account_info': return await dbxApi.getAccountInfo(); case 'get_file_content': return await dbxApi.getFileContent(String(request.params.arguments?.path)); default: throw new McpError( ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}` ); } })(); return { content: result.content, _meta: {} }; }); } 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; } async run() { try { const transport = new StdioServerTransport(); log.info('Connecting to transport...'); await this.server.connect(transport); log.info('Dbx MCP server running on stdio'); } catch (error) { log.error('Error connecting server:', { error: error instanceof Error ? error.message : 'Unknown error', stack: error instanceof Error ? error.stack : undefined }); throw error; } } } export default DbxServer;

Latest Blog Posts

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/amgadabdelhafez/dbx-mcp-server'

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