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