Skip to main content
Glama
executeCard.ts3.81 kB
import { MetabaseApiClient } from '../../api.js'; import { handleApiError, validatePositiveInteger, validateMetabaseResponse, formatJson, } from '../../utils/index.js'; import { CardExecutionParams, ExecutionResponse } from './types.js'; export async function executeCard( params: CardExecutionParams, requestId: string, apiClient: MetabaseApiClient, logDebug: (message: string, data?: unknown) => void, logInfo: (message: string, data?: unknown) => void, logWarn: (message: string, data?: unknown, error?: Error) => void, logError: (message: string, error: unknown) => void ): Promise<ExecutionResponse> { const { cardId, cardParameters, rowLimit } = params; // Validate positive integer parameters validatePositiveInteger(cardId, 'card_id', requestId, logWarn); validatePositiveInteger(rowLimit, 'row_limit', requestId, logWarn); logDebug(`Executing card ID: ${cardId} with row limit: ${rowLimit}`); // Build card execution request body const cardRequestBody = { parameters: cardParameters, pivot_results: false, format_rows: false, }; try { const response = await apiClient.request<any>(`/api/card/${cardId}/query/json`, { method: 'POST', body: JSON.stringify(cardRequestBody), }); // Check for embedded errors in the response (Metabase returns 200 with embedded errors) validateMetabaseResponse( response, { operation: 'Card execution', resourceId: cardId }, logError ); // Handle different response formats from Metabase cards let originalRowCount = 0; let limitedData = response; // Check if response has numbered keys (actual card response format) const numberedKeys = Object.keys(response || {}).filter( key => /^\d+$/.test(key) && key !== 'data' ); if (numberedKeys.length > 0) { // Response format: {"0": {...}, "1": {...}, "2": {...}, "data": {...}} originalRowCount = numberedKeys.length; // Apply row limit by keeping only the first N numbered entries const limitedKeys = numberedKeys.slice(0, rowLimit); limitedData = { ...response, }; // Remove entries beyond the limit numberedKeys.forEach(key => { if (!limitedKeys.includes(key)) { delete limitedData[key]; } }); } else if (response?.data?.rows) { // Standard format: {"data": {"rows": [...]}} originalRowCount = response.data.rows.length; const limitedRows = response.data.rows.slice(0, rowLimit); limitedData = { ...response, data: { ...response.data, rows: limitedRows, }, }; } const finalRowCount = Math.min(originalRowCount, rowLimit); logInfo( `Successfully executed card: ${cardId}, returned ${finalRowCount} rows (original: ${originalRowCount})` ); if (originalRowCount > rowLimit) { logDebug( `Applied row limit: ${rowLimit} to card results (truncated from ${originalRowCount} rows)` ); } return { content: [ { type: 'text', text: formatJson({ success: true, card_id: cardId, row_count: finalRowCount, original_row_count: originalRowCount, applied_limit: rowLimit, data: limitedData, }), }, ], }; } catch (error: any) { // Check if this is a structured Metabase error response with parameter validation details if (error?.response?.data?.error_type === 'invalid-parameter') { logError(`Card parameter validation failed for card ${cardId}`, error.response.data); } throw handleApiError( error, { operation: 'Card execution', resourceType: 'card', resourceId: cardId }, logError ); } }

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jerichosequitin/Metabase'

If you have feedback or need assistance with the MCP directory API, please join our Discord server