✓ SAFE: Execute read-only SQL queries (SELECT, PRAGMA, EXPLAIN). Automatically rejects write operations.
execute_read_only_queryExecute read-only SQL queries like SELECT, PRAGMA, and EXPLAIN while automatically blocking write operations for safety.
Instructions
✓ SAFE: Execute read-only SQL queries (SELECT, PRAGMA, EXPLAIN). Automatically rejects write operations.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Read-only SQL query to execute (SELECT, PRAGMA, EXPLAIN only) | |
| params | No | Query parameters (optional) - Use parameterized queries for security | |
| database | No | Database name (optional, uses context if not provided) - Specify target database |
Implementation Reference
- src/tools/handler.ts:203-241 (handler)Handler for the 'execute_read_only_query' tool. Validates the query is SELECT/PRAGMA-only, delegates to database_client.execute_query, and formats the result.
server.tool( { name: 'execute_read_only_query', description: `✓ SAFE: Execute read-only SQL queries (SELECT, PRAGMA, EXPLAIN). Automatically rejects write operations.`, schema: ReadOnlyQuerySchema, }, async ({ query, params = {}, database }) => { try { // Validate that this is a read-only query const normalized_query = query.trim().toLowerCase(); if ( !normalized_query.startsWith('select') && !normalized_query.startsWith('pragma') ) { throw new Error( 'Only SELECT and PRAGMA queries are allowed with execute_read_only_query', ); } const database_name = resolve_database_name(database); if (database) set_current_database(database); const result = await database_client.execute_query( database_name, query, params, ); const formatted_result = format_query_result(result); return create_tool_response({ database: database_name, query, result: formatted_result, }); } catch (error) { return create_tool_error_response(error); } }, ); - src/tools/handler.ts:42-46 (schema)Zod schema for the execute_read_only_query tool inputs: query (required), params (optional), database (optional).
const ReadOnlyQuerySchema = z.object({ query: z.string().describe('Read-only SQL query to execute (SELECT, PRAGMA, EXPLAIN only)'), params: z.record(z.string(), z.any()).optional().describe('Query parameters (optional) - Use parameterized queries for security'), database: z.string().optional().describe('Database name (optional, uses context if not provided) - Specify target database'), }); - src/tools/handler.ts:103-103 (registration)The register_tools function registers all tools (including execute_read_only_query) with the MCP server via server.tool().
export function register_tools(server: McpServer<any>): void { - src/clients/database.ts:120-151 (helper)Helper function execute_query that runs the SQL query against the Turso database client. Uses read-only or full-access permission based on query type.
export async function execute_query( database_name: string, query: string, params: Record<string, any> = {}, ): Promise<ResultSet> { try { // Determine if this is a read-only query const is_read_only = query .trim() .toLowerCase() .startsWith('select'); const permission = is_read_only ? 'read-only' : 'full-access'; const client = await get_database_client( database_name, permission, ); // Execute the query return await client.execute({ sql: query, args: convert_parameters(params), }); } catch (error) { throw new TursoApiError( `Failed to execute query for database ${database_name}: ${ (error as Error).message }`, 500, ); } } - src/tools/handler.ts:366-380 (helper)Helper function that formats query results for display, handling BigInt serialization.
function format_query_result(result: ResultSet): any { // Convert BigInt to string to avoid serialization issues const lastInsertRowid = result.lastInsertRowid !== null && typeof result.lastInsertRowid === 'bigint' ? result.lastInsertRowid.toString() : result.lastInsertRowid; return { rows: result.rows, rowsAffected: result.rowsAffected, lastInsertRowid: lastInsertRowid, columns: result.columns, }; }