We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/russelenriquez-agile/tableau-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server
/**
* Tool: tableau_query_view
*
* Export data from a Tableau view as CSV or JSON format.
* Supports filtering and row limits.
*/
const tableauClient = require('../../lib/tableau-client');
const logger = require('../../lib/logger');
const name = 'tableau_query_view';
/**
* Execute the query-view-data tool
* @param {Object} args - Tool arguments
* @param {string} args.viewId - The view ID (LUID) to query data from
* @param {string} args.format - Output format: csv or json (default: json)
* @param {number} args.maxRows - Maximum rows to return (default: 10000, max: 100000)
* @param {Object} args.filters - Optional key-value pairs for filters
* @param {Object} config - Server configuration
* @param {string} correlationId - Request correlation ID
* @returns {Promise<Object>} - Query results
*/
async function execute(args, config, correlationId) {
const { viewId, format = 'json', maxRows = 10000, filters } = args;
if (!viewId) {
throw new Error('viewId is required');
}
// Validate maxRows
const validMaxRows = Math.min(Math.max(1, maxRows), 100000);
const log = logger.child({ correlationId, tool: name });
log.info('Querying view data', {
viewId,
format,
maxRows: validMaxRows,
hasFilters: !!filters && Object.keys(filters).length > 0
});
try {
const startTime = Date.now();
const response = await tableauClient.queryViewData(config, viewId, {
maxRows: validMaxRows,
filters
});
const duration = Date.now() - startTime;
// Parse and format the response
let data;
let rowCount = 0;
let columns = [];
if (typeof response === 'string') {
// CSV response
if (format === 'json') {
// Convert CSV to JSON
data = parseCsvToJson(response);
rowCount = data.length;
columns = data.length > 0 ? Object.keys(data[0]) : [];
} else {
data = response;
// Count rows in CSV (minus header)
rowCount = response.split('\n').filter(line => line.trim()).length - 1;
columns = response.split('\n')[0]?.split(',').map(c => c.trim().replace(/"/g, '')) || [];
}
} else if (Array.isArray(response)) {
data = response;
rowCount = response.length;
columns = response.length > 0 ? Object.keys(response[0]) : [];
} else if (response?.data) {
data = response.data;
rowCount = Array.isArray(response.data) ? response.data.length : 0;
columns = rowCount > 0 ? Object.keys(response.data[0]) : [];
} else {
data = response;
rowCount = 0;
}
log.info('View data retrieved', {
viewId,
rowCount,
columnCount: columns.length,
duration
});
// Format output based on requested format
if (format === 'csv' && typeof data !== 'string') {
// Convert JSON to CSV
data = jsonToCsv(data);
}
return {
viewId,
format,
rowCount,
columns,
truncated: rowCount >= validMaxRows,
queryTime: `${duration}ms`,
filters: filters || null,
data,
hints: {
exportPdf: `tableau_export_pdf({ viewId: "${viewId}"${filters ? `, filters: ${JSON.stringify(filters)}` : ''} })`,
exportImage: `tableau_export_image({ viewId: "${viewId}"${filters ? `, filters: ${JSON.stringify(filters)}` : ''} })`
},
note: rowCount >= validMaxRows
? `Results truncated at ${validMaxRows} rows. Increase maxRows parameter to get more data (max: 100000).`
: null
};
} catch (error) {
log.error('Failed to query view data', error);
throw error;
}
}
/**
* Parse CSV string to JSON array
*/
function parseCsvToJson(csv) {
const lines = csv.split('\n').filter(line => line.trim());
if (lines.length === 0) return [];
// Parse header
const headers = parseCSVLine(lines[0]);
// Parse data rows
const data = [];
for (let i = 1; i < lines.length; i++) {
const values = parseCSVLine(lines[i]);
const row = {};
headers.forEach((header, index) => {
row[header] = values[index] || null;
});
data.push(row);
}
return data;
}
/**
* Parse a single CSV line handling quoted values
*/
function parseCSVLine(line) {
const values = [];
let current = '';
let inQuotes = false;
for (let i = 0; i < line.length; i++) {
const char = line[i];
if (char === '"') {
if (inQuotes && line[i + 1] === '"') {
current += '"';
i++; // Skip escaped quote
} else {
inQuotes = !inQuotes;
}
} else if (char === ',' && !inQuotes) {
values.push(current.trim());
current = '';
} else {
current += char;
}
}
values.push(current.trim());
return values;
}
/**
* Convert JSON array to CSV string
*/
function jsonToCsv(data) {
if (!Array.isArray(data) || data.length === 0) {
return '';
}
const headers = Object.keys(data[0]);
const lines = [headers.map(h => `"${h}"`).join(',')];
for (const row of data) {
const values = headers.map(h => {
const val = row[h];
if (val === null || val === undefined) return '';
if (typeof val === 'string') return `"${val.replace(/"/g, '""')}"`;
return String(val);
});
lines.push(values.join(','));
}
return lines.join('\n');
}
module.exports = {
name,
execute
};