UNPKG

@mcp-apps/kusto-mcp-server

Version:

MCP server for interacting with Kusto databases

182 lines 7.72 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.KustoService = void 0; const azure_kusto_data_1 = require("azure-kusto-data"); class KustoService { static createConnectionString(clusterUrl) { console.log("Using user prompt authentication. Please log in interactively."); return azure_kusto_data_1.KustoConnectionStringBuilder.withUserPrompt(clusterUrl); } static cleanupCache() { if (KustoService.clientCache.size <= KustoService.MAX_CACHE_SIZE) { const now = Date.now(); for (const [url, cachedClient] of KustoService.clientCache.entries()) { if (now - cachedClient.lastUsed > KustoService.CACHE_EXPIRATION_MS) { console.log(`Removing expired cached client for ${url}`); KustoService.clientCache.delete(url); } } return; } const entries = Array.from(KustoService.clientCache.entries()) .sort((a, b) => a[1].lastUsed - b[1].lastUsed); while (entries.length > KustoService.MAX_CACHE_SIZE) { const [url] = entries.shift(); console.log(`Removing oldest cached client for ${url}`); KustoService.clientCache.delete(url); } } /** * Get a cached client for a specific cluster URL and database, or create a new one */ static getClient(clusterUrl, database) { if (!clusterUrl) { throw new Error("Cluster URL is required"); } if (!database) { throw new Error("Database name is required"); } try { // Check if we already have a cached client for this cluster URL const cachedClient = KustoService.clientCache.get(clusterUrl); if (cachedClient && cachedClient.database === database) { console.log(`Using cached Kusto client for ${clusterUrl}`); // Update the last used timestamp cachedClient.lastUsed = Date.now(); return cachedClient.client; } // No cached client found, create a new one console.log(`Creating new Kusto client for ${clusterUrl}`); const connectionString = KustoService.createConnectionString(clusterUrl); const client = new azure_kusto_data_1.Client(connectionString); // Add to cache KustoService.clientCache.set(clusterUrl, { client: client, database: database, lastUsed: Date.now() }); // Clean up cache if needed KustoService.cleanupCache(); return client; } catch (error) { console.error("Failed to get Kusto client:", error.message); throw new Error(`Failed to connect to Kusto: ${error.message}`); } } /** * Execute a KQL query against the Kusto database */ static async executeQuery(clusterUrl, database, query) { try { const client = KustoService.getClient(clusterUrl, database); const response = await client.execute(database, query); return response.primaryResults[0].toJSON(); } catch (error) { console.error("Error executing Kusto query:", error.message); throw new Error(`Error executing query: ${error.message}`); } } /** * Get list of tables in the database */ static async getTables(clusterUrl, database) { try { // Query for internal tables const internalQuery = `.show tables | project TableName, IsExternal = false`; const internalResult = await KustoService.executeQuery(clusterUrl, database, internalQuery); // Query for external tables const externalQuery = `.show external tables | project TableName, IsExternal = true`; const externalResult = await KustoService.executeQuery(clusterUrl, database, externalQuery); // Combine results const internalTables = internalResult.data.map((item) => ({ TableName: item.TableName, IsExternal: false, })); const externalTables = externalResult.data.map((item) => ({ TableName: item.TableName, IsExternal: true, })); return [...internalTables, ...externalTables]; } catch (error) { console.error("Error retrieving tables:", error.message); // Return an empty array instead of throwing when no tables are found return []; } } /** * Get detailed schema information for all tables in the database */ static async getAllTableSchemas(clusterUrl, database) { try { const query = `.show tables | project TableName`; const tables = await KustoService.executeQuery(clusterUrl, database, query); const tableNames = tables.data.map((item) => item.TableName); const schemaInfo = {}; // For each table, get its schema details for (const tableName of tableNames) { try { const tableSchema = await KustoService.getTableSchema(clusterUrl, database, tableName); schemaInfo[tableName] = tableSchema; } catch (error) { console.warn(`Skipping schema retrieval for table ${tableName} due to error`); } } return schemaInfo; } catch (error) { console.error("Error retrieving all table schemas:", error); return {}; // Return an empty object for graceful failure } } /** * Get the schema for a specific table */ static async getTableSchema(clusterUrl, database, tableName, isExternal = false) { try { const query = isExternal ? `.show external table ['${tableName}'] schema as json` : `.show table ['${tableName}'] schema as json`; const result = await KustoService.executeQuery(clusterUrl, database, query); if (!result || !result.data || result.data.length === 0) { throw new Error(`No schema found for table ${tableName}`); } return JSON.parse(result.data[0].Schema); } catch (error) { console.error(`Error getting schema for table ${tableName}:`, error.message); throw new Error(`Failed to get schema for table ${tableName}: ${error.message}`); } } /** * Get sample data from a table (top N rows) */ static async getTableSample(clusterUrl, database, tableName, sampleSize = 10) { try { const query = `${tableName} | take ${sampleSize}`; return await KustoService.executeQuery(clusterUrl, database, query); } catch (error) { console.error(`Error getting sample data from table ${tableName}:`, error.message); throw new Error(`Failed to get sample data from table ${tableName}: ${error.message}`); } } /** * Check if the connection to Kusto is working properly */ static async testConnection(clusterUrl, database) { try { await KustoService.getTables(clusterUrl, database); return true; } catch (error) { return false; } } } exports.KustoService = KustoService; KustoService.clientCache = new Map(); KustoService.MAX_CACHE_SIZE = 10; KustoService.CACHE_EXPIRATION_MS = 30 * 60 * 1000; //# sourceMappingURL=kustoService.js.map