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