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