Skip to main content
Glama
read-query.tsโ€ข4.63 kB
import { BaseTool, type ToolExecutionContext, type ToolExecutionResult } from '../lib/base-tool.js'; import { formatPerformanceMetrics } from '../utils/performance.js'; import { ReadQueryInputSchema, type ReadQueryInput } from '../schemas/read-query.js'; import { DEFAULT_CONFIG } from '../lib/constants.js'; import type { ResultSet } from '@libsql/client'; export class ReadQueryTool extends BaseTool { readonly name = 'read-query'; readonly description = 'Execute SELECT queries on the libSQL database'; readonly inputSchema = ReadQueryInputSchema; protected async executeImpl(context: ToolExecutionContext): Promise<ToolExecutionResult> { const { query, parameters } = context.arguments as ReadQueryInput; try { const startTime = Date.now(); // Execute query with timeout handling const result = await Promise.race([ parameters && parameters.length > 0 ? context.connection.execute(query, parameters) : context.connection.execute(query), new Promise((_, reject) => setTimeout(() => reject(new Error(`Query timeout after ${DEFAULT_CONFIG.queryTimeout}ms`)), DEFAULT_CONFIG.queryTimeout) ) ]) as ResultSet; const executionTime = Date.now() - startTime; // Check result size limit if (result.rows.length > DEFAULT_CONFIG.maxResultSize) { return { content: [ { type: 'text', text: `Error: Query result too large (${result.rows.length} rows, max ${DEFAULT_CONFIG.maxResultSize})` } ], isError: true }; } const metrics = formatPerformanceMetrics({ executionTime, rowsReturned: result.rows.length }); // Format the results let output = 'Query executed successfully\n\n'; if (result.rows.length === 0) { output += 'No rows returned.\n'; } else { // Format as a table const columns = result.columns || []; output += `Found ${result.rows.length} row(s):\n\n`; if (columns.length > 0) { // Calculate column widths for better formatting const columnWidths = columns.map((col: string) => { const headerWidth = col.length; const maxDataWidth = Math.max( ...result.rows.slice(0, 100).map((row: Record<string, unknown>) => { const value = row[col]; return value === null ? 4 : String(value).length; // 4 for 'NULL' }) ); return Math.max(headerWidth, maxDataWidth, 3); // Minimum 3 chars }); // Add column headers with proper spacing const headerRow = columns.map((col: string, i: number) => col.padEnd(columnWidths[i] || 0) ).join(' | '); output += `${headerRow}\n`; // Add separator const separator = columnWidths.map((width: number) => '-'.repeat(width)).join('-+-'); output += `${separator}\n`; // Add rows (limit to first 100 rows for display) const displayRows = result.rows.slice(0, 100); for (const row of displayRows) { const rowValues = columns.map((col: string, i: number) => { const value = row[col]; const displayValue = value === null ? 'NULL' : String(value); return displayValue.padEnd(columnWidths[i] || 0); }); output += `${rowValues.join(' | ')}\n`; } if (result.rows.length > 100) { output += `\n... and ${result.rows.length - 100} more rows (use LIMIT clause to see more)\n`; } } else { // Fallback for queries without column metadata output += JSON.stringify(result.rows.slice(0, 10), null, 2); if (result.rows.length > 10) { output += `\n... and ${result.rows.length - 10} more rows\n`; } } } output += `\nPerformance: ${metrics}`; // Add query info if parameters were used if (parameters && parameters.length > 0) { output += `\nParameters: ${parameters.length} parameter(s) used`; } return { content: [ { type: 'text', text: output } ] }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [ { type: 'text', text: `Error executing query: ${errorMessage}` } ], isError: true }; } } }

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/Xexr/mcp-libsql'

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