UNPKG

dbx-mcp-server

Version:

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

181 lines 6.78 kB
import { log } from './config.js'; import * as dbxApi from './dbx-api.js'; const MAX_PDF_SIZE = 20 * 1024 * 1024; // 20MB limit export async function analyzePDF(args) { try { log.info('Starting PDF analysis', { path: args.path, prompt: args.prompt }); // Get file metadata first const metadata = await dbxApi.getRawFileMetadata(args.path); // Validate file type if (!metadata.name.toLowerCase().endsWith('.pdf')) { return { content: [{ type: "text", text: `Error: File "${metadata.name}" is not a PDF file.`, }], isError: true, }; } // Check file size if (metadata.size > MAX_PDF_SIZE) { return { content: [{ type: "text", text: `Error: PDF file size (${Math.round(metadata.size / 1024 / 1024)}MB) exceeds 20MB limit. Maximum allowed size is 20MB.`, }], isError: true, }; } log.info('PDF validation passed', { fileName: metadata.name, size: metadata.size }); // Download PDF content const fileBuffer = await dbxApi.downloadFileAsBuffer(args.path); // Analyze with custom prompt (not using structured extraction) const analysisResult = await callOpenRouterDirectly(fileBuffer, metadata.name, args.prompt, args.useThinking, args.maxReasoningTokens); log.info('PDF analysis completed successfully', { fileName: metadata.name, responseLength: analysisResult.length }); return { content: [{ type: "text", text: `Analysis of "${metadata.name}":\n\n${analysisResult}`, }], isError: false, }; } catch (error) { log.error('PDF analysis failed', { path: args.path, error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined }); return { content: [{ type: "text", text: `Error analyzing PDF: ${error instanceof Error ? error.message : String(error)}`, }], isError: true, }; } } async function callOpenRouterDirectly(fileBuffer, fileName, prompt, useThinking = false, maxReasoningTokens) { const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1"; const apiKey = process.env.OPENROUTER_API_KEY; if (!apiKey) { throw new Error("OPENROUTER_API_KEY environment variable is not set"); } // Convert buffer to base64 const fileBase64 = fileBuffer.toString("base64"); const dataUri = `data:application/pdf;base64,${fileBase64}`; // Choose AI model const model = useThinking ? "google/gemini-2.5-flash-preview-05-20:thinking" : "google/gemini-2.5-flash-preview-05-20"; // Prepare API request const messages = [{ role: "user", content: [ { type: "text", text: prompt, }, { type: "file", file: { filename: fileName, file_data: dataUri, }, }, ], }]; const requestBody = { model, messages, plugins: [ { id: "file-parser", pdf: { engine: "native" } } ] }; if (maxReasoningTokens && model.includes("thinking")) { requestBody.max_reasoning_tokens = maxReasoningTokens; } // Call OpenRouter API with retry logic const maxRetries = 3; let lastError; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { log.info('Calling OpenRouter API', { model, attempt, maxRetries, fileName }); const response = await fetch(`${OPENROUTER_BASE_URL}/chat/completions`, { method: "POST", headers: { "Authorization": `Bearer ${apiKey}`, "Content-Type": "application/json", "HTTP-Referer": "https://github.com/markov-github/dbx-mcp-server", "X-Title": "Dropbox MCP PDF Analyzer", }, body: JSON.stringify(requestBody), }); if (!response.ok) { const errorText = await response.text(); const error = new Error(`OpenRouter API error: ${response.status} - ${errorText}`); log.warn('OpenRouter API error', { status: response.status, error: errorText, attempt, maxRetries, fileName }); // Retry on server errors (5xx) if (response.status >= 500 && attempt < maxRetries) { const delay = Math.pow(2, attempt - 1) * 1000; log.info(`Retrying after ${delay}ms...`, { fileName }); await new Promise(resolve => setTimeout(resolve, delay)); lastError = error; continue; } throw error; } const data = await response.json(); const content = data.choices[0]?.message?.content; if (!content) { throw new Error('No content returned from OpenRouter API'); } log.info('OpenRouter API call successful', { attempt, fileName, tokensUsed: data.usage?.total_tokens || 'unknown' }); return content; } catch (error) { lastError = error instanceof Error ? error : new Error(String(error)); if (attempt === maxRetries) { log.error('All OpenRouter API retry attempts failed', { error: lastError.message, maxRetries, fileName }); break; } log.warn('OpenRouter API attempt failed, retrying...', { attempt, error: lastError.message, fileName }); const delay = Math.pow(2, attempt - 1) * 1000; await new Promise(resolve => setTimeout(resolve, delay)); } } throw lastError; } //# sourceMappingURL=pdf-analyzer.js.map