UNPKG

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

157 lines (156 loc) 7.09 kB
import { z } from 'zod'; import { ParallelIndexer } from './parallel_indexer.js'; import { google } from 'googleapis'; import { getValidCredentials } from '../auth.js'; export const name = 'gdrive_index_smart'; export const description = 'Smart indexing of Google Drive with advanced options and parallel processing'; export const inputSchema = z.object({ path: z.string().optional().describe('Drive path or folder name to index (searches for best match)'), folderId: z.string().optional().describe('Specific folder ID to index'), recursive: z.boolean().optional().default(true).describe('Include subfolders'), docTypes: z.array(z.enum(['contract', 'invoice', 'proposal', 'project_plan', 'report'])).optional() .describe('Only index specific document types'), maxFiles: z.number().optional().describe('Maximum number of files to index'), showProgress: z.boolean().optional().default(true).describe('Show detailed progress updates') }); export const schema = { name, description, inputSchema: { type: 'object', properties: { path: { type: 'string', description: 'Drive path or folder name to index' }, folderId: { type: 'string', description: 'Specific folder ID to index' }, recursive: { type: 'boolean', description: 'Include subfolders', default: true }, docTypes: { type: 'array', items: { type: 'string', enum: ['contract', 'invoice', 'proposal', 'project_plan', 'report'] }, description: 'Only index specific document types' }, maxFiles: { type: 'number', description: 'Maximum number of files to index' }, showProgress: { type: 'boolean', description: 'Show detailed progress updates', default: true } }, required: [] } }; export async function gdrive_index_smart(args) { try { let targetFolderId; // Resolve folder ID from path if provided if (args.path && !args.folderId) { const authClient = await getValidCredentials(); const drive = google.drive({ version: 'v3', auth: authClient }); // Search for folder by name/path const response = await drive.files.list({ q: `name contains '${args.path}' and mimeType = 'application/vnd.google-apps.folder' and trashed = false`, fields: 'files(id, name, parents)', pageSize: 10 }); if (response.data.files && response.data.files.length > 0) { // If multiple matches, try to find the best one const exactMatch = response.data.files.find(f => f.name === args.path); targetFolderId = exactMatch ? exactMatch.id : response.data.files[0].id; console.log(`Found folder: ${response.data.files[0].name} (${targetFolderId})`); } else { return { content: [{ type: 'text', text: `No folder found matching: ${args.path}` }], isError: true }; } } else if (args.folderId) { targetFolderId = args.folderId; } else { // No folder specified - index entire drive console.log('No folder specified - will index entire Google Drive'); } // Progress tracking const progressUpdates = []; let lastProgressUpdate = ''; const progressCallback = args.showProgress ? (progress) => { const percent = progress.total > 0 ? Math.round((progress.processed / progress.total) * 100) : 0; const rate = progress.processed > 0 && progress.estimatedTimeRemaining ? (progress.processed / ((Date.now() - progress.startTime) / 1000)).toFixed(1) : '0'; const update = `Progress: ${progress.processed}/${progress.total} files (${percent}%) | Success: ${progress.succeeded} | Skipped: ${progress.skipped} | Failed: ${progress.failed} | Rate: ${rate} files/sec`; if (progress.currentFile) { progressUpdates.push(`Processing: ${progress.currentFile}`); } if (update !== lastProgressUpdate) { progressUpdates.push(update); lastProgressUpdate = update; console.log(update); } if (progress.estimatedTimeRemaining) { const minutes = Math.ceil(progress.estimatedTimeRemaining / 60); console.log(`Estimated time remaining: ${minutes} minutes`); } } : undefined; const indexer = new ParallelIndexer(progressCallback); console.log('Starting smart indexing...'); const startTime = Date.now(); let result; if (targetFolderId) { result = await indexer.indexFolder(targetFolderId, args.recursive ?? true); } else { // Index entire drive const authClient = await getValidCredentials(); const drive = google.drive({ version: 'v3', auth: authClient }); // Get root folder const rootResponse = await drive.files.get({ fileId: 'root', fields: 'id' }); result = await indexer.indexFolder(rootResponse.data.id, true); } const summary = [ `Indexing complete!`, ``, `Summary:`, `- Total files found: ${result.total_files}`, `- Successfully indexed: ${result.indexed}`, `- Skipped (up-to-date): ${result.skipped}`, `- Errors: ${result.errors}`, `- Duration: ${result.duration_seconds.toFixed(1)} seconds`, `- Average speed: ${result.files_per_second.toFixed(1)} files/second`, ]; if (result.error_details.length > 0) { summary.push('', 'Errors encountered:'); result.error_details.slice(0, 10).forEach(err => { summary.push(`- ${err.fileName}: ${err.error}`); }); if (result.error_details.length > 10) { summary.push(`... and ${result.error_details.length - 10} more errors`); } } if (args.showProgress && progressUpdates.length > 0) { summary.push('', 'Progress log:', '```'); summary.push(...progressUpdates.slice(-20)); // Last 20 updates summary.push('```'); } return { content: [{ type: 'text', text: summary.join('\n') }], isError: false }; } catch (error) { return { content: [{ type: 'text', text: `Error during smart indexing: ${error instanceof Error ? error.message : 'Unknown error'}` }], isError: true }; } }