UNPKG

@sqlitecloud/mcp-server

Version:

Model Context Protocol server for SQLite Cloud database

205 lines (204 loc) 10.6 kB
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; export class SQLiteCloudMcpServer { mcpServer; registry; constructor() { this.registry = {}; this.mcpServer = this.initializeServer(); this.setupServer(); } async connect(transport) { const mcpTransport = transport.mcpTransport; let sessionId = mcpTransport.sessionId; if (!sessionId) { sessionId = 'anonymous'; mcpTransport.sessionId = sessionId; } mcpTransport.onerror = error => { console.error('Error in transport:', error); delete this.registry[sessionId]; }; mcpTransport.onclose = () => { delete this.registry[sessionId]; }; this.registry[sessionId] = transport; await this.mcpServer.connect(mcpTransport); } getTransport(sessionId) { const transport = this.registry[sessionId]; if (!transport) { throw new Error(`Transport not found for session ID: ${sessionId}`); } return transport; } addCustomTool(name, description, parameters, handler) { // TODO: keep a registered list of tools to check existence and to implement removal this.mcpServer.tool(name, description, parameters, async (parameters, extra) => { if (!extra.sessionId) { throw new Error('Session ID is required'); } const customerResult = await handler(parameters, this.getTransport(extra.sessionId)); return { content: [{ type: 'text', text: JSON.stringify(customerResult) }] }; }); } removeCustomTool(name) { throw new Error('Not implemented'); } initializeServer() { return new McpServer({ name: 'sqlitecloud-mcp-server', version: '0.0.1', description: 'MCP Server for SQLite Cloud: https://sqlitecloud.io' }, { capabilities: { tools: {} }, instructions: 'This server provides tools to interact with SQLite databases on SQLite Cloud, execute SQL queries, manage table schemas and analyze performance metrics.' }); } setupServer() { this.mcpServer.tool('read-query', 'Execute a SELECT query on the SQLite database on SQLite Cloud', { query: z.string().describe('SELECT SQL query to execute') }, async ({ query }, extra) => { if (!query.trim().toUpperCase().startsWith('SELECT')) { throw new Error('Only SELECT queries are allowed for read-query'); } if (!extra.sessionId) { throw new Error('Session ID is required'); } const results = await this.getTransport(extra.sessionId).executeQuery(query); return { content: [{ type: 'text', text: JSON.stringify(results) }] }; }); this.mcpServer.tool('write-query', 'Execute a INSERT, UPDATE, or DELETE query on the SQLite database on SQLite Cloud', { query: z.string().describe('SELECT SQL query to execute') }, async ({ query }, extra) => { if (query.trim().toUpperCase().startsWith('SELECT')) { throw new Error('SELECT queries are not allowed for write_query'); } if (!extra.sessionId) { throw new Error('Session ID is required'); } const results = await this.getTransport(extra.sessionId).executeQuery(query); return { content: [{ type: 'text', text: JSON.stringify(results) }] }; }); this.mcpServer.tool('create-table', 'Create a new table in the SQLite database on SQLite Cloud', { query: z.string().describe('CREATE TABLE SQL statement') }, async ({ query }, extra) => { if (!query.trim().toUpperCase().startsWith('CREATE TABLE')) { throw new Error('Only CREATE TABLE statements are allowed'); } if (!extra.sessionId) { throw new Error('Session ID is required'); } const results = await this.getTransport(extra.sessionId).executeQuery(query); return { content: [{ type: 'text', text: 'Table created successfully' }] }; }); this.mcpServer.tool('list-tables', 'List all tables in the SQLite database on SQLite Cloud', {}, async ({}, extra) => { if (!extra.sessionId) { throw new Error('Session ID is required'); } const results = await this.getTransport(extra.sessionId).executeQuery("SELECT name FROM sqlite_master WHERE type='table'"); return { content: [{ type: 'text', text: JSON.stringify(results) }] }; }); this.mcpServer.tool('describe-table', 'Get the schema information for a specific table on SQLite Cloud database', { tableName: z.string().describe('Name of the table to describe') }, async ({ tableName }, extra) => { if (!extra.sessionId) { throw new Error('Session ID is required'); } const results = await this.getTransport(extra.sessionId).executeQuery(`PRAGMA table_info(${tableName})`); return { content: [{ type: 'text', text: JSON.stringify(results) }] }; }); this.mcpServer.tool('list-commands', 'List all available commands and their descriptions from the SQLite database and an external documentation page.', {}, async ({}, extra) => { try { if (!extra.sessionId) { throw new Error('Session ID is required'); } const results = await this.getTransport(extra.sessionId).executeQuery('LIST COMMANDS;'); // Download the documentation page const documentationUrl = 'https://raw.githubusercontent.com/sqlitecloud/docs/refs/heads/main/sqlite-cloud/reference/general-commands.mdx'; const response = await fetch(documentationUrl, { redirect: 'follow' }); const documentationContent = await response.text(); return { content: [ { type: 'text', text: JSON.stringify(results) }, { type: 'text', text: documentationContent } ] }; } catch (error) { throw new Error('Failed to list commands and fetch documentation.', { cause: error }); } }); this.mcpServer.tool('execute-command', 'Execute only SQLite Cloud commands listed in the `list-commands` tool. You can use the `list-commands` tool to see the available commands.', { command: z.string().describe('SQLite Cloud available command to execute') }, async ({ command }, extra) => { if (!extra.sessionId) { throw new Error('Session ID is required'); } const results = await this.getTransport(extra.sessionId).executeQuery(command); return { content: [{ type: 'text', text: JSON.stringify(results) }] }; }); this.mcpServer.tool('list-analyzer', 'Returns a rowset with the slowest queries performed on the connected this.mcpServer. Supports filtering with GROUPID, DATABASE, GROUPED, and NODE options.', { groupId: z.string().optional().describe('Group ID to filter the results'), database: z.string().optional().describe('Database name to filter the results'), grouped: z.boolean().optional().describe('Whether to group the slowest queries'), node: z.string().optional().describe('Node ID to execute the command on a specific cluster node') }, async ({ groupId, database, grouped, node }, extra) => { let query = 'LIST ANALYZER'; if (groupId) query += ` GROUPID ${groupId}`; if (database) query += ` DATABASE ${database}`; if (grouped) query += ' GROUPED'; if (node) query += ` NODE ${node}`; if (!extra.sessionId) { throw new Error('Session ID is required'); } const results = await this.getTransport(extra.sessionId).executeQuery(query); return { content: [{ type: 'text', text: JSON.stringify(results) }] }; }); this.mcpServer.tool('analyzer-plan-id', 'Gathers information about the indexes used in the query plan of a query execution.', { queryId: z.string().describe('Query ID to analyze'), node: z.string().optional().describe('SQLite Cloud Node ID to execute the command on a specific cluster node') }, async ({ queryId, node }, extra) => { let query = `ANALYZER PLAN ID ${queryId}`; if (node) query += ` NODE ${node}`; if (!extra.sessionId) { throw new Error('Session ID is required'); } const results = await this.getTransport(extra.sessionId).executeQuery(query); return { content: [{ type: 'text', text: JSON.stringify(results) }] }; }); this.mcpServer.tool('analyzer-reset', 'Resets the statistics about a specific query, group of queries, or database.', { queryId: z.string().optional().describe('Query ID to reset'), groupId: z.string().optional().describe('Group ID to reset'), database: z.string().optional().describe('Database name to reset'), all: z.boolean().optional().describe('Whether to reset all statistics'), node: z.string().optional().describe('SQLite Cloud Node ID to execute the command on a specific cluster node') }, async ({ queryId, groupId, database, all, node }, extra) => { let query = 'ANALYZER RESET'; if (queryId) query += ` ID ${queryId}`; if (groupId) query += ` GROUPID ${groupId}`; if (database) query += ` DATABASE ${database}`; if (all) query += ' ALL'; if (node) query += ` NODE ${node}`; if (!extra.sessionId) { throw new Error('Session ID is required'); } const results = await this.getTransport(extra.sessionId).executeQuery(query); return { content: [{ type: 'text', text: JSON.stringify(results) }] }; }); } }