mcp-gdrive-enhanced-markov
Version:
MCP server for Google Drive and Sheets with write capabilities, AI-powered PDF analysis, SQLite document indexing, and advanced directory exploration tools
209 lines (208 loc) • 7.55 kB
JavaScript
import fs from "fs";
import path from "path";
export const schema = {
name: "local_analyze_pdf",
description: "Analyze local PDF files using Gemini 2.5 Flash AI model",
inputSchema: {
type: "object",
properties: {
filePath: {
type: "string",
description: "Absolute path to the local PDF file to analyze",
},
prompt: {
type: "string",
description: "Analysis instructions or questions about the PDF content",
},
useThinking: {
type: "boolean",
description: "Use Gemini 2.5 Flash thinking mode for complex analysis (optional, default: false)",
optional: true,
},
maxReasoningTokens: {
type: "number",
description: "Maximum tokens for reasoning in thinking mode (optional, default: 5000)",
optional: true,
},
},
required: ["filePath", "prompt"],
},
};
// OpenRouter configuration
const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
const OPENROUTER_API_KEY = process.env.OPENROUTER_API_KEY;
async function callOpenRouter(model, messages, maxReasoningTokens, retries = 3) {
if (!OPENROUTER_API_KEY) {
throw new Error("OPENROUTER_API_KEY environment variable is not set");
}
const requestBody = {
model,
messages,
max_tokens: 4096,
temperature: 0.7,
plugins: [
{
id: "file-parser",
pdf: {
engine: "native"
}
}
]
};
if (maxReasoningTokens && model.includes("thinking")) {
requestBody.max_reasoning_tokens = maxReasoningTokens;
}
for (let attempt = 0; attempt < retries; attempt++) {
try {
const response = await fetch(`${OPENROUTER_BASE_URL}/chat/completions`, {
method: "POST",
headers: {
"Authorization": `Bearer ${OPENROUTER_API_KEY}`,
"Content-Type": "application/json",
"HTTP-Referer": "https://github.com/markov-kernel/mcp-gdrive-enhanced",
"X-Title": "Google Drive MCP PDF Analyzer",
},
body: JSON.stringify(requestBody),
});
if (!response.ok) {
const error = await response.text();
console.error(`OpenRouter API error (attempt ${attempt + 1}/${retries}): ${response.status} - ${error}`);
// If it's a 500 error and we have retries left, wait and retry
if (response.status === 500 && attempt < retries - 1) {
await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
continue;
}
throw new Error(`OpenRouter API error: ${response.status} - ${error}`);
}
const data = await response.json();
return data.choices[0].message.content;
}
catch (error) {
if (attempt === retries - 1)
throw error;
console.error(`Request failed (attempt ${attempt + 1}/${retries}):`, error);
await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
}
}
throw new Error("Failed to call OpenRouter API after all retries");
}
export async function analyzeLocalPDF(args) {
try {
const validatedArgs = args;
// Security validation - ensure absolute path
if (!path.isAbsolute(validatedArgs.filePath)) {
return {
content: [
{
type: "text",
text: "Error: File path must be absolute",
},
],
isError: true,
};
}
// Check if file exists
if (!fs.existsSync(validatedArgs.filePath)) {
return {
content: [
{
type: "text",
text: `Error: File not found at path: ${validatedArgs.filePath}`,
},
],
isError: true,
};
}
// Get file stats
const stats = fs.statSync(validatedArgs.filePath);
// Check if it's a file (not directory)
if (!stats.isFile()) {
return {
content: [
{
type: "text",
text: `Error: Path is not a file: ${validatedArgs.filePath}`,
},
],
isError: true,
};
}
// Check file extension
const ext = path.extname(validatedArgs.filePath).toLowerCase();
if (ext !== '.pdf') {
return {
content: [
{
type: "text",
text: `Error: File is not a PDF. Extension: ${ext}`,
},
],
isError: true,
};
}
// Check file size (limit to 20MB for API constraints)
const MAX_PDF_SIZE = 20 * 1024 * 1024; // 20MB
if (stats.size > MAX_PDF_SIZE) {
return {
content: [
{
type: "text",
text: `Error: PDF file size (${Math.round(stats.size / 1024 / 1024)}MB) exceeds 20MB limit`,
},
],
isError: true,
};
}
// Read the PDF file
const pdfBuffer = fs.readFileSync(validatedArgs.filePath);
const pdfBase64 = pdfBuffer.toString("base64");
console.error(`PDF file size: ${Math.round(pdfBuffer.length / 1024)}KB`);
// Choose the appropriate Gemini model
const model = validatedArgs.useThinking
? "google/gemini-2.5-flash-preview-05-20:thinking"
: "google/gemini-2.5-flash-preview-05-20";
const fileName = path.basename(validatedArgs.filePath);
console.error(`Analyzing local PDF "${fileName}" with ${model}...`);
// Call OpenRouter API
const messages = [
{
role: "user",
content: [
{
type: "text",
text: validatedArgs.prompt,
},
{
type: "file",
file: {
filename: fileName,
file_data: `data:application/pdf;base64,${pdfBase64}`,
},
},
],
},
];
const analysisResult = await callOpenRouter(model, messages, validatedArgs.maxReasoningTokens || 5000);
return {
content: [
{
type: "text",
text: `Analysis of "${fileName}":\n\n${analysisResult}`,
},
],
isError: false,
};
}
catch (error) {
console.error("Error analyzing local PDF:", error);
return {
content: [
{
type: "text",
text: `Error analyzing PDF: ${error.message || error}`,
},
],
isError: true,
};
}
}