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

216 lines (215 loc) 9.46 kB
import { google } from 'googleapis'; import { getValidCredentials } from '../auth.js'; export const schema = { name: 'gdrive_read_excel', description: 'Read Excel and Google Sheets files with proper formatting and table display', inputSchema: { type: 'object', properties: { fileId: { type: 'string', description: 'The ID of the Excel or Google Sheets file to read' }, sheetName: { type: 'string', description: 'Optional sheet name to read from (for multi-sheet files)' }, range: { type: 'string', description: 'Optional cell range in A1 notation (e.g., "A1:D10")' } }, required: ['fileId'] } }; export async function gdrive_read_excel(args) { try { const authClient = await getValidCredentials(); const drive = google.drive({ version: 'v3', auth: authClient }); const sheets = google.sheets({ version: 'v4', auth: authClient }); // First, get file metadata to determine the type const fileMetadata = await drive.files.get({ fileId: args.fileId, fields: 'name,mimeType,webViewLink' }); const fileName = fileMetadata.data.name || 'Unknown File'; const mimeType = fileMetadata.data.mimeType; // Handle Google Sheets if (mimeType === 'application/vnd.google-apps.spreadsheet') { try { // If sheetName and range are specified, use them let range = args.range; if (args.sheetName) { range = args.sheetName + (args.range ? `!${args.range}` : ''); } // Try to get spreadsheet metadata first to find actual sheet names let actualRange = range; if (!range && !args.sheetName) { try { const spreadsheetInfo = await sheets.spreadsheets.get({ spreadsheetId: args.fileId }); const firstSheet = spreadsheetInfo.data.sheets?.[0]; if (firstSheet?.properties?.title) { actualRange = firstSheet.properties.title; } } catch (metaError) { // Fallback to generic range if metadata fetch fails actualRange = 'A1:Z1000'; } } const response = await sheets.spreadsheets.values.get({ spreadsheetId: args.fileId, range: actualRange || 'A1:Z1000', valueRenderOption: 'FORMATTED_VALUE', dateTimeRenderOption: 'FORMATTED_STRING' }); const values = response.data.values; if (!values || values.length === 0) { return { content: [{ type: 'text', text: `Google Sheet "${fileName}" appears to be empty or the specified range contains no data.` }], isError: false }; } // Format as readable table let content = `# Google Sheet: ${fileName}\n\n`; if (args.sheetName) { content += `## Sheet: ${args.sheetName}\n\n`; } if (args.range) { content += `## Range: ${args.range}\n\n`; } // Create markdown table const maxCols = Math.max(...values.map(row => row.length)); // Header row const headers = values[0] || []; content += '| ' + headers.map((header, index) => (header || `Column ${index + 1}`).toString().padEnd(15)).join(' | ') + ' |\n'; // Separator content += '|' + Array(headers.length).fill('').map(() => '----------------').join('|') + '|\n'; // Data rows for (let i = 1; i < values.length; i++) { const row = values[i] || []; content += '| ' + row.map((cell, index) => (cell || '').toString().padEnd(15)).join(' | ') + ' |\n'; } content += `\n**Total Rows:** ${values.length}\n`; content += `**Total Columns:** ${maxCols}\n`; content += `**Link:** ${fileMetadata.data.webViewLink}\n`; return { content: [{ type: 'text', text: content }], isError: false }; } catch (sheetsError) { return { content: [{ type: 'text', text: `Error reading Google Sheet "${fileName}": ${sheetsError.message}` }], isError: true }; } } // Handle Excel files (.xlsx, .xls) if (mimeType === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' || mimeType === 'application/vnd.ms-excel') { try { // For Excel files, try to export as CSV for reading const response = await drive.files.export({ fileId: args.fileId, mimeType: 'text/csv' }); const csvData = response.data; // Parse CSV and format as table const lines = csvData.split('\n').filter(line => line.trim()); if (lines.length === 0) { return { content: [{ type: 'text', text: `Excel file "${fileName}" appears to be empty.` }], isError: false }; } let content = `# Excel File: ${fileName}\n\n`; // Parse CSV and create markdown table const rows = lines.map(line => { // Simple CSV parsing - handles basic cases const cells = []; let current = ''; let inQuotes = false; for (let i = 0; i < line.length; i++) { const char = line[i]; if (char === '"') { inQuotes = !inQuotes; } else if (char === ',' && !inQuotes) { cells.push(current); current = ''; } else { current += char; } } cells.push(current); return cells.map(cell => cell.replace(/^"|"$/g, '').trim()); }); // Header row const headers = rows[0] || []; content += '| ' + headers.map((header, index) => (header || `Column ${index + 1}`).padEnd(15)).join(' | ') + ' |\n'; // Separator content += '|' + Array(headers.length).fill('').map(() => '----------------').join('|') + '|\n'; // Data rows (limit to first 50 for readability) const dataRows = rows.slice(1, 51); for (const row of dataRows) { content += '| ' + row.map(cell => (cell || '').padEnd(15)).join(' | ') + ' |\n'; } if (rows.length > 51) { content += `\n*... showing first 50 rows of ${rows.length - 1} total data rows*\n`; } content += `\n**Total Rows:** ${rows.length}\n`; content += `**Total Columns:** ${headers.length}\n`; content += `**Link:** ${fileMetadata.data.webViewLink}\n`; return { content: [{ type: 'text', text: content }], isError: false }; } catch (excelError) { return { content: [{ type: 'text', text: `Error reading Excel file "${fileName}": ${excelError.message}. Note: Some Excel files may not be exportable. Try converting to Google Sheets format first.` }], isError: true }; } } // Unsupported file type return { content: [{ type: 'text', text: `File "${fileName}" (${mimeType}) is not a supported spreadsheet format. This tool supports Google Sheets and Excel files (.xlsx, .xls).` }], isError: true }; } catch (error) { return { content: [{ type: 'text', text: `Error accessing file: ${error.message}` }], isError: true }; } }