UNPKG

inshow-ai-query-mcp

Version:

MCP服务器,用于连接Supabase数据库并提供表结构查询功能

2,367 lines 85.4 kB
#!/usr/bin/env node
import {ResourceTemplate,McpServer}from'@modelcontextprotocol/sdk/server/mcp.js';import dotenv from'dotenv';import {Command}from'commander';import fs from'fs';import path from'path';import {createBrowserClient}from'@supabase/ssr';import {z}from'zod';import {StdioServerTransport}from'@modelcontextprotocol/sdk/server/stdio.js';import express from'express';import {randomUUID}from'node:crypto';import {StreamableHTTPServerTransport}from'@modelcontextprotocol/sdk/server/streamableHttp.js';import {isInitializeRequest}from'@modelcontextprotocol/sdk/types.js';// 判断是否在Inspector模式下运行
const isInspectorMode = process.env.MCP_STDIO === 'true';

/**
 * 安全的日志输出函数,在Inspector模式下通过stderr输出
 * @param {string|Object} message 要输出的消息
 * @param {boolean} isError 是否错误级别日志
 * @param {string} tag 日志标签,默认为'MCP'
 */
function safeLog(message, isError = false, tag = 'MCP') {
  // 处理对象类型消息
  const formattedMessage = typeof message === 'object' ? JSON.stringify(message) : message;

  // 添加标签
  const taggedMessage = tag ? `[${tag}] ${formattedMessage}` : formattedMessage;

  // 在Inspector模式下只通过stderr输出日志
  if (isInspectorMode || isError) {
    console.error(taggedMessage);
  } else {
    console.log(taggedMessage);
  }
}

/**
 * 信息级别日志
 * @param {string|Object} message 日志消息
 * @param {string} tag 日志标签
 */
function logInfo(message, tag = 'INFO') {
  safeLog(message, false, tag);
}

/**
 * 警告级别日志
 * @param {string|Object} message 日志消息
 * @param {string} tag 日志标签
 */
function logWarn(message, tag = 'WARN') {
  safeLog(message, false, tag);
}

/**
 * 错误级别日志
 * @param {string|Object} message 日志消息
 * @param {string} tag 日志标签
 */
function logError(message, tag = 'ERROR') {
  safeLog(message, true, tag);
}

/**
 * 调试级别日志
 * @param {string|Object} message 日志消息
 * @param {string} tag 日志标签
 */
function logDebug(message, tag = 'DEBUG') {
  // 只在非生产环境输出调试日志
  if (process.env.NODE_ENV !== 'production') {
    safeLog(message, false, tag);
  }
}/**
 * 初始化Supabase客户端并提供数据库访问方法
 * @returns {Object} Supabase服务对象
 */
function setupSupabaseService() {
  // 从环境变量获取Supabase配置
  const supabaseUrl = process.env.SUPABASE_URL;
  const supabaseKey = process.env.SUPABASE_KEY;

  if (!supabaseUrl || !supabaseKey) {
    logError('缺少Supabase配置!');
    throw new Error('缺少Supabase配置,请确保设置了SUPABASE_URL和SUPABASE_KEY环境变量');
  }

  logInfo(`正在初始化Supabase客户端,URL: ${supabaseUrl.substring(0, 20)}...`);

  // 创建Supabase客户端
  const supabase = createBrowserClient(supabaseUrl, supabaseKey, {
    auth: {
      persistSession: false, // 服务器端模式不需要持久化会话
    },
  });

  return {
    /**
     * 获取数据库中所有表及其定义
     * @returns {Promise<Array>} 表结构信息数组
     */
    async getTableDefinitions() {
      logInfo('调用 get_table_definitions RPC...');
      try {
        const { data, error } = await supabase.rpc('get_table_definitions');
        logDebug('get_table_definitions RPC 返回数据: ' + JSON.stringify(data));

        if (error) {
          logError('获取表定义失败: ' + error);
          // 尝试确定错误原因
          if (
            error.message &&
            error.message.includes('function get_table_definitions() does not exist')
          ) {
            logError('错误: get_table_definitions 函数不存在,请确保已在 Supabase 中运行安装脚本');
            throw new Error(
              `获取表定义失败: Supabase 数据库缺少必要的函数。请确保已运行 setup-supabase.sql 脚本`
            );
          } else if (error.message && error.message.includes('permission denied')) {
            logError('错误: 权限被拒绝。请检查 Supabase 权限设置');
            throw new Error(
              `获取表定义失败: 权限被拒绝。请确保 RPC 函数已授权给 authenticated 角色`
            );
          }
          throw new Error(`获取表定义失败: ${error.message}`);
        }

        // 确保返回的数据是数组
        if (!data) {
          logWarn('获取表定义返回的数据为空');
          return [];
        }

        if (!Array.isArray(data)) {
          logError(`获取表定义返回的不是数组,而是 ${typeof data}: ` + JSON.stringify(data));
          // 尝试转换数据为数组格式
          if (typeof data === 'object') {
            try {
              const convertedData = Object.keys(data).map(key => ({
                table_name: key,
                description: data[key]?.description || '',
              }));
              logInfo(`已将对象转换为数组,共 ${convertedData.length} 项`);
              return convertedData;
            } catch (conversionError) {
              logError('无法转换对象到数组: ' + conversionError);
            }
          }
          // 如果转换失败,返回空数组
          return [];
        }

        // 验证并过滤数据
        const validatedData = data.filter(item => {
          if (!item || typeof item !== 'object') {
            logWarn('表定义中存在无效项: ' + JSON.stringify(item));
            return false;
          }

          if (!item.table_name) {
            logWarn('表定义缺少表名: ' + JSON.stringify(item));
            return false;
          }

          return true;
        });

        if (validatedData.length !== data.length) {
          logWarn(`过滤掉了 ${data.length - validatedData.length} 个无效项`);
        }

        return validatedData;
      } catch (err) {
        logError('获取表定义时出现异常: ' + err);
        throw err;
      }
    },

    /**
     * 执行只读SQL查询
     * @param {string} query SQL查询语句
     * @returns {Promise<Object>} 查询结果
     */
    async executeReadQuery(query) {
      query = query.trim();

      // 记录原始查询
      logInfo(`原始SQL查询: ${query.substring(0, 100)}${query.length > 100 ? '...' : ''}`);

      // 简化前端验证,避免与后端验证冲突
      // 只进行基本检查以防止明显的危险查询
      const upperQuery = query.toUpperCase();
      const dangerousKeywords = ['DROP DATABASE', 'TRUNCATE ALL', 'DELETE FROM'];

      const containsDangerousKeyword = dangerousKeywords.some(keyword =>
        upperQuery.includes(keyword)
      );

      if (containsDangerousKeyword) {
        logError('危险查询被前端拒绝: ' + query);
        throw new Error('查询包含危险操作,已被拒绝');
      }

      try {
        logDebug(`发送到Supabase RPC的SQL: ${query}`);

        const { data, error } = await supabase.rpc('execute_read_query', { query_text: query });

        if (error) {
          logError('查询执行失败: ' + JSON.stringify(error));
          throw new Error(`查询执行失败: ${error.message}`);
        }

        logInfo(
          `查询执行成功, 结果数量: ${data ? (Array.isArray(data) ? data.length : '非数组') : 0}`
        );
        logDebug(`查询结果: ${JSON.stringify(data).substring(0, 200)}...`);

        return data || [];
      } catch (err) {
        logError(`查询执行时出现异常: ${err.message}`);
        logError(`详细错误信息: ${JSON.stringify(err)}`);
        throw err;
      }
    },

    /**
     * 获取表的列信息
     * @param {string} tableName 表名
     * @returns {Promise<Array>} 列信息数组
     */
    async getTableColumns(tableName) {
      logInfo(`获取表列信息: ${tableName}`);
      try {
        const { data, error } = await supabase.rpc('get_table_columns', { table_name: tableName });

        if (error) {
          logError(`获取表 ${tableName} 列信息失败: ` + JSON.stringify(error));
          throw new Error(`获取表列信息失败: ${error.message}`);
        }

        return data || [];
      } catch (err) {
        logError(`获取表 ${tableName} 列信息时出现异常: ` + err);
        throw err;
      }
    },

    /**
     * 获取表的前N行数据
     * @param {string} tableName 表名
     * @param {number} limit 限制行数
     * @returns {Promise<Array>} 表数据数组
     */
    async getTableSample(tableName, limit = 10) {
      logInfo(`获取表样本数据: ${tableName}, 限制: ${limit} 行`);
      try {
        const { data, error } = await supabase.from(tableName).select('*').limit(limit);

        if (error) {
          logError(`获取表 ${tableName} 样本数据失败: ` + error);
          throw new Error(`获取表样本数据失败: ${error.message}`);
        }

        return data || [];
      } catch (err) {
        logError(`获取表 ${tableName} 样本数据时出现异常: ` + err);
        throw err;
      }
    },
  };
}/**
 * 数据库资源描述文件
 * 为MCP资源提供清晰的描述,帮助LLM更准确地理解和使用资源
 */

// 资源描述对象
const resourceDescriptions = {
  // 数据库概览资源
  'database-overview': {
    name: '数据库概览',
    description:
      '提供数据库中所有表的高级概述,包括表名和描述。当需要了解数据库整体结构时,应首先查看此资源。',
    uri_pattern: 'db://overview',
    when_to_use:
      '在开始任何数据库探索任务时,或需要快速了解数据库包含哪些表时使用。这应该是探索未知数据库的第一步。特别适用于:1)初次接触该数据库时;2)需要理解数据库整体功能和用途时;3)寻找特定功能相关表时;4)用户请求数据库概览或表列表时;5)在执行查询前需要了解可用表。',
    when_not_to_use:
      '当已经知道需要操作的具体表,或需要详细的表结构信息时,应直接使用更具体的资源或工具。不适用于:1)需要查看表内数据时;2)需要了解表间关系时;3)需要获取表的详细结构时;4)需要执行数据查询时;5)用户已明确指定目标表。',
    versus_tools:
      '比起getDatabaseStats工具,此资源更关注表的功能描述而非数据量统计。比起getTableStructure,此资源提供更高层次的概览而非详细列信息。比起executeQuery查询information_schema.tables,此资源提供更友好的格式化表描述。当需要了解数据库结构和表功能时,应优先使用此资源而非工具。',
    examples: [
      '数据库中有哪些表',
      '数据库结构是什么样的',
      '给我一个数据库的总体视图',
      '我需要了解这个数据库包含什么内容',
    ],
  },

  // 表列表资源
  'table-list': {
    name: '数据库表列表',
    description:
      '列出数据库中所有可用的表,以资源列表形式返回。每个表都包含URI和名称,可用于访问详细信息。',
    uri_pattern: 'db://tables',
    when_to_use:
      '当需要以程序化方式获取所有表资源的URI时使用,尤其是需要后续导航到特定表详情时。适用场景:1)准备多表连续探索时;2)需要构建表资源URI时;3)需要以结构化格式获取表列表时;4)为编程处理表列表做准备时;5)需要在回答中包含指向特定表的链接时。',
    when_not_to_use:
      '如果只需要可读的表列表概览,应使用db://overview。如果已知目标表名,可直接访问特定表详情。不适用于:1)需要了解表功能和描述时;2)只关注单个特定表时;3)需要表结构或数据内容时;4)需要了解表间关系时;5)用户更倾向于叙述性描述而非技术性列表时。',
    versus_tools:
      '比起db://overview,此资源返回更结构化的表列表,适合进一步通过URI导航。比起getDatabaseStats,不包含行数统计信息。比起使用executeQuery查询表信息,这提供更标准化的格式和更简洁的访问方式。当需要获取完整表列表作为进一步探索的起点时,应使用此资源。',
    examples: ['列出所有表', '数据库中有哪些可用的表', '显示表资源列表', '获取所有表的URI'],
  },

  // 表详情资源
  'table-details': {
    name: '表详情',
    description:
      '提供特定表的详细信息,包括列定义(名称、类型、可空性、描述)和样本数据。当需要深入了解表结构时使用。',
    uri_pattern: 'db://tables/{tableName}',
    when_to_use:
      '当需要同时了解表的结构和数据内容时使用,这是获取表完整信息的最全面方法。特别适用于:1)初次了解特定表时;2)需要同时查看结构和数据示例时;3)准备编写查询前全面了解表时;4)需要人类可读的表信息时;5)用户请求特定表的完整信息时。',
    when_not_to_use:
      '如果只需要表结构,应使用getTableStructure工具;如果只需要数据样本,应使用getTableSample工具。对于复杂查询,应使用executeQuery。不适用于:1)只关注列定义而不需要示例数据时;2)只需查看数据而不关心结构时;3)需要复杂条件过滤时;4)需要处理大量数据时;5)需要进行数据聚合或分析时。',
    versus_tools:
      '此资源结合了getTableStructure和getTableSample的功能,提供更全面的表视图。返回格式化的文本而非JSON结构,更适合人类阅读。相比executeQuery,这是一种无需编写SQL就能获取表基本信息的简便方法。当用户需要了解表的整体情况而不需要特定查询时,应优先使用此资源。',
    parameters: {
      tableName: '要查看详情的表名',
    },
    examples: [
      '获取users表的详细信息',
      '显示products表的结构和数据',
      '查看orders表的列定义',
      '我想了解customers表的完整信息',
    ],
  },

  // 数据库关系图资源
  'database-relationships': {
    name: '数据库关系图',
    description:
      '展示数据库表之间的关系,包括外键约束和引用。提供表格和PlantUML格式的ER图,帮助理解数据模型。',
    uri_pattern: 'db://relationships',
    when_to_use:
      '当需要了解表之间的关联关系、外键引用或整体数据模型时使用。对于理解复杂数据库的结构非常有用。适用于:1)准备多表联接查询前;2)理解数据模型和实体关系;3)追踪引用完整性和依赖关系;4)分析数据流和业务逻辑;5)用户询问表间关系或引用时。',
    when_not_to_use:
      '如果已知表之间的关系或只关心单一表的结构时,不需要使用此资源。对于数据查询操作,应使用查询工具而非此资源。不适用于:1)只需了解单表结构时;2)需要查看实际数据内容时;3)需要执行数据查询或分析时;4)已经熟悉数据模型时;5)用户明确只关注特定表而非关系时。',
    versus_tools:
      '没有工具可以提供类似的表关系概览。这是理解数据库模型的唯一专用资源。比起executeQuery查询约束信息,此资源提供更直观的关系图和更全面的关系描述。比起单独查询每个表的结构,此资源更有效地展示全局关系。当需要理解数据模型或准备复杂联接查询时,应优先使用此资源。',
    examples: [
      '表之间的关系是什么',
      '数据库ER图',
      '显示数据库的外键约束',
      '如何理解数据库中的表是如何关联的',
    ],
  },
};/**
 * 注册数据库相关资源
 * @param {Object} server MCP服务器实例
 * @param {Object} supabaseService Supabase服务实例
 */
function registerDatabaseResources(server, supabaseService) {
  // 数据库概览资源
  server.resource(
    'database-overview',
    'db://overview',
    async uri => {
      try {
        const tables = await supabaseService.getTableDefinitions();
        let overviewText = '# 数据库结构概览\n\n';

        if (!tables || !Array.isArray(tables) || tables.length === 0) {
          overviewText += '数据库中没有可用的表,或无法获取表定义。\n';
        } else {
          overviewText += tables
            .map(table => {
              return `## ${table.table_name}\n${table.description || '无描述'}\n`;
            })
            .join('\n');
        }

        return {
          contents: [
            {
              uri: uri.href,
              text: overviewText,
            },
          ],
        };
      } catch (error) {
        return {
          contents: [
            {
              uri: uri.href,
              text: `获取数据库概览失败: ${error.message}`,
            },
          ],
        };
      }
    },
    {
      description: resourceDescriptions['database-overview'].description,
      name: resourceDescriptions['database-overview'].name,
      usage_examples: resourceDescriptions['database-overview'].examples,
    }
  );

  // 定义表列表资源处理函数
  async function listTablesHandler() {
    try {
      logInfo('正在获取可用表列表...');
      const tables = await supabaseService.getTableDefinitions();

      if (!tables || !Array.isArray(tables)) {
        logError('获取表列表失败: 返回的不是数组');
        return { resources: [] }; // 返回正确格式的空资源列表
      }

      // 转换为标准资源列表格式,每项包含uri和name属性
      const resources = tables
        .filter(table => table && table.table_name)
        .map(table => ({
          uri: `db://tables/${table.table_name}`,
          name: table.table_name,
          description: table.description || `${table.table_name} 表`,
        }));

      logDebug(`获取到 ${resources.length} 个表资源: ${JSON.stringify(resources)}`);
      return { resources }; // 返回正确格式的资源列表
    } catch (error) {
      logError(`获取表列表失败: ${error}`);
      return { resources: [] }; // 出错时返回正确格式的空资源列表
    }
  }

  // 注册表列表资源
  server.resource(
    'table-list',
    'db://tables',
    async uri => {
      try {
        const resources = await listTablesHandler();
        return {
          contents: [
            {
              uri: uri.href,
              text: JSON.stringify(resources, null, 2),
              mimeType: 'application/json',
            },
          ],
        };
      } catch (error) {
        logError(`获取表列表内容失败: ${error}`);
        return {
          contents: [
            {
              uri: uri.href,
              text: `获取表列表失败: ${error.message}`,
              mimeType: 'text/plain',
            },
          ],
        };
      }
    },
    {
      description: resourceDescriptions['table-list'].description,
      name: resourceDescriptions['table-list'].name,
      usage_examples: resourceDescriptions['table-list'].examples,
    }
  );

  // 表详情资源模板 - 修复资源列表处理
  server.resource(
    'table-details',
    new ResourceTemplate('db://tables/{tableName}', {
      list: listTablesHandler, // 直接使用表列表处理函数
    }),
    async (uri, { tableName }) => {
      try {
        if (!tableName) {
          return {
            contents: [
              {
                uri: uri.href,
                text: `错误: 未提供表名`,
              },
            ],
          };
        }

        // 获取表的列信息
        const columns = await supabaseService.getTableColumns(tableName);

        // 验证列信息
        if (!columns || !Array.isArray(columns)) {
          return {
            contents: [
              {
                uri: uri.href,
                text: `表 "${tableName}" 没有列信息或无法获取列信息`,
              },
            ],
          };
        }

        // 生成格式化的表详情内容
        let tableDetails = `# ${tableName} 表详情\n\n`;
        tableDetails += '## 列定义\n\n';

        if (columns.length === 0) {
          tableDetails += '该表没有列信息。\n';
        } else {
          tableDetails += '| 列名 | 数据类型 | 可为空 | 描述 |\n';
          tableDetails += '|------|----------|--------|------|\n';

          tableDetails += columns
            .map(col => {
              return `| ${col.column_name || '未知'} | ${col.data_type || '未知'} | ${col.is_nullable ? '是' : '否'} | ${col.description || '-'} |`;
            })
            .join('\n');
        }

        try {
          // 获取样本数据
          const sampleData = await supabaseService.getTableSample(tableName, 5);

          if (sampleData && Array.isArray(sampleData) && sampleData.length > 0) {
            tableDetails += '\n\n## 样本数据\n\n```json\n';
            tableDetails += JSON.stringify(sampleData, null, 2);
            tableDetails += '\n```';
          } else {
            tableDetails += '\n\n## 样本数据\n\n无样本数据或表为空。';
          }
        } catch (sampleError) {
          logError(`获取表 "${tableName}" 样本数据失败: ` + sampleError);
          tableDetails += `\n\n## 样本数据\n\n获取样本数据失败: ${sampleError.message}`;
        }

        return {
          contents: [
            {
              uri: uri.href,
              text: tableDetails,
            },
          ],
        };
      } catch (error) {
        logError(`获取表 "${tableName}" 详情失败: ` + error);
        return {
          contents: [
            {
              uri: uri.href,
              text: `获取表 "${tableName}" 详情失败: ${error.message}`,
            },
          ],
        };
      }
    },
    {
      description: resourceDescriptions['table-details'].description,
      name: resourceDescriptions['table-details'].name,
      usage_examples: resourceDescriptions['table-details'].examples,
      parameters_description: resourceDescriptions['table-details'].parameters,
    }
  );

  // 数据库关系图资源
  server.resource(
    'database-relationships',
    'db://relationships',
    async uri => {
      try {
        // 获取表之间的关系
        logInfo('正在获取数据库关系图...');

        const query = `
          SELECT
            tc.table_schema, 
            tc.constraint_name, 
            tc.table_name, 
            kcu.column_name, 
            ccu.table_schema AS foreign_table_schema,
            ccu.table_name AS foreign_table_name,
            ccu.column_name AS foreign_column_name 
          FROM 
            information_schema.table_constraints AS tc 
            JOIN information_schema.key_column_usage AS kcu
              ON tc.constraint_name = kcu.constraint_name
              AND tc.table_schema = kcu.table_schema
            JOIN information_schema.constraint_column_usage AS ccu
              ON ccu.constraint_name = tc.constraint_name
              AND ccu.table_schema = tc.table_schema
          WHERE tc.constraint_type = 'FOREIGN KEY'
        `;

        logDebug(`执行关系查询: ${query.replace(/\s+/g, ' ')}`);

        try {
          const relationships = await supabaseService.executeReadQuery(query);
          logInfo(
            `获取到 ${relationships ? (Array.isArray(relationships) ? relationships.length : '非数组') : 0} 条关系数据`
          );

          let relationshipText = '# 数据库关系图\n\n';
          relationshipText += '## 表之间的关系\n\n';

          // 确保关系数据是数组
          if (!relationships || !Array.isArray(relationships) || relationships.length === 0) {
            relationshipText += '数据库中没有定义外键关系或无法获取关系数据。\n';
            return {
              contents: [
                {
                  uri: uri.href,
                  text: relationshipText,
                },
              ],
            };
          }

          relationshipText += '| 源表 | 源列 | 引用表 | 引用列 |\n';
          relationshipText += '|------|------|--------|--------|\n';

          relationshipText += relationships
            .map(rel => {
              return `| ${rel.table_name} | ${rel.column_name} | ${rel.foreign_table_name} | ${rel.foreign_column_name} |`;
            })
            .join('\n');

          // 添加简单的PlantUML格式的ER图
          relationshipText += '\n\n## ER图 (PlantUML格式)\n\n```plantuml\n';
          relationshipText += '@startuml\n\n';

          // 添加表
          const uniqueTables = new Set();
          relationships.forEach(rel => {
            if (rel && rel.table_name) uniqueTables.add(rel.table_name);
            if (rel && rel.foreign_table_name) uniqueTables.add(rel.foreign_table_name);
          });

          [...uniqueTables].forEach(tableName => {
            relationshipText += `entity "${tableName}" {\n}\n\n`;
          });

          // 添加关系
          relationships.forEach(rel => {
            if (rel && rel.table_name && rel.foreign_table_name && rel.column_name) {
              relationshipText += `"${rel.table_name}" }|--|| "${rel.foreign_table_name}" : ${rel.column_name}\n`;
            }
          });

          relationshipText += '\n@enduml\n```';

          return {
            contents: [
              {
                uri: uri.href,
                text: relationshipText,
              },
            ],
          };
        } catch (queryError) {
          logError(`执行关系查询失败: ${queryError.message}`);

          // 尝试使用替代方案查询
          logInfo('尝试使用替代查询方法...');

          // 返回错误信息但不抛出异常
          return {
            contents: [
              {
                uri: uri.href,
                text: `# 数据库关系图\n\n获取数据库关系失败: ${queryError.message}\n\n可能原因:\n1. 数据库权限不足\n2. 查询语法与数据库版本不兼容\n3. Supabase函数未正确配置`,
              },
            ],
          };
        }
      } catch (error) {
        logError('获取数据库关系失败: ' + error);
        return {
          contents: [
            {
              uri: uri.href,
              text: `获取数据库关系失败: ${error.message}`,
            },
          ],
        };
      }
    },
    {
      description: resourceDescriptions['database-relationships'].description,
      name: resourceDescriptions['database-relationships'].name,
      usage_examples: resourceDescriptions['database-relationships'].examples,
    }
  );
}/**
 * 数据库工具描述文件
 * 为MCP工具提供清晰的描述,帮助LLM更准确地选择工具
 */

// 工具描述对象
const toolDescriptions = {
  // 数据库连接检查工具
  checkDatabaseConnection: {
    name: '检查数据库连接',
    description:
      '检测Supabase数据库连接是否正常工作。当遇到数据库查询错误或需要诊断连接问题时使用此工具。该工具会验证环境配置并测试基本数据库连接功能。',
    when_to_use:
      "仅在怀疑数据库连接有问题时使用,例如查询失败、无法获取表数据或返回错误。作为诊断工具,不应用于正常的数据查询操作。当用户提到'连接错误'、'无法访问数据库'、'查询超时'等问题时,应首先使用此工具。",
    when_not_to_use:
      '不要用于获取数据库结构或表数据,这些操作应使用专门的资源或查询工具。当数据库正常工作时,避免使用此工具作为常规探索数据库的方法。此工具不返回业务数据,仅返回连接诊断结果。',
    examples: [
      '数据库连接似乎有问题',
      '我无法获取任何表的数据',
      '数据库查询返回连接错误',
      '为什么我的查询总是超时或失败',
    ],
    returnsExample: `{
      "配置状态": {
        "SUPABASE_URL": "已设置",
        "SUPABASE_KEY": "已设置"
      },
      "表定义查询": {
        "成功获取": "是",
        "表数量": 5,
        "表名列表": "users, products, orders, categories, inventory"
      }
    }`,
  },

  // 环境检查工具
  checkEnvironment: {
    name: '检查环境配置',
    description:
      '检查应用程序运行环境和配置参数。当怀疑配置问题或需要了解应用环境信息时使用此工具。该工具会安全地显示环境变量和运行时信息,不会暴露敏感信息。',
    when_to_use:
      '在需要了解系统环境或验证配置设置时使用,特别是在怀疑环境变量配置错误或缺失时。当用户询问关于应用程序如何配置、运行环境或系统状态时,这是首选工具。也适用于排查非数据库特定的应用问题。',
    when_not_to_use:
      '不要用于数据库操作或查询,这仅是一个诊断工具。当用户明确要求数据库相关内容或查询时,应优先考虑数据库特定工具。此工具不会修复任何问题,仅提供诊断信息。',
    examples: [
      '应用程序环境是如何配置的',
      '检查当前环境变量',
      '应用运行在什么环境中',
      '确认Supabase配置是否正确设置',
    ],
    returnsExample: `{
      "运行环境": {
        "NODE_VERSION": "v18.12.1",
        "PLATFORM": "darwin",
        "NODE_ENV": "development",
        "运行时间": "35 秒"
      },
      "配置参数": {
        "SUPABASE_URL": "已设置 (https://xxxxx...)",
        "SUPABASE_KEY": "已设置 (长度: 43)",
        "APP_PORT": "9000"
      }
    }`,
  },

  // SQL查询执行工具
  executeQuery: {
    name: '执行SQL查询',
    description:
      '执行只读SQL查询并返回结果。当需要运行自定义SQL来查询、聚合或分析数据时使用此工具。查询必须是只读类型,不支持修改数据的操作。支持复杂的SELECT查询、JOIN操作和子查询。',
    when_to_use:
      '需要执行复杂查询、多表联接、条件过滤、聚合或自定义数据分析时使用。这是最灵活但也最复杂的查询工具。应该在以下情况使用:1)需要特定条件过滤数据;2)需要多表关联查询;3)需要聚合计算如COUNT、SUM、AVG等;4)需要复杂排序或分组;5)简单工具无法满足查询需求时。',
    when_not_to_use:
      '如果只是用于测试样本数据,使用更简单的专用工具如getTableStructure或getTableSample。不要用于数据修改操作。避免在以下情况使用:1)只需表的基本信息时;2)只查看前N行数据时;3)只需了解表关系时;4)有更简单的专用工具可用时。避免使用此工具查询information_schema以获取元数据,应使用专用资源代替。',
    examples: [
      'SELECT * FROM users WHERE age > 30 LIMIT 5',
      'SELECT p.name, c.category_name FROM products p JOIN categories c ON p.category_id = c.id',
      'SELECT count(*), avg(price) FROM products GROUP BY category_id',
      'SELECT u.name, COUNT(o.id) FROM users u LEFT JOIN orders o ON u.id = o.user_id GROUP BY u.id LIMIT 10',
    ],
    parameters: {
      query: 'SQL查询语句(必须是只读查询)',
    },
    returnsExample: `[
      { "id": 1, "name": "John Doe", "email": "john@example.com" },
      { "id": 2, "name": "Jane Smith", "email": "jane@example.com" }
    ]`,
  },

  // 获取表样本数据工具
  getTableSample: {
    name: '获取表样本数据',
    description:
      '获取指定表的样本数据。当需要了解表中的实际数据内容、格式或示例时使用此工具。返回表中的前N行记录(默认10行)。仅用于获取测试数据,不适用于直接获取SQL数据,如需要通过SQL查询获取数据,请使用executeQuery。',
    when_to_use:
      '需要快速查看表中数据示例、了解数据格式或验证表是否有数据时使用。这是获取表数据最简单的方法。适用于:1)初步探索表数据;2)了解实际数据格式;3)查看数据示例而不关心特定条件;4)验证表是否包含数据;5)用户直接要求查看表内容而未指定复杂条件时。',
    when_not_to_use:
      '不适合复杂条件过滤、聚合计算或多表查询,这些情况应使用executeQuery。也不适合只想了解表结构而不关心数据内容的场景。不应用于:1)需要特定条件筛选数据时;2)需要排序或分组时;3)需要多表联接时;4)表数据量很大且只关注统计信息时;5)用户明确要求的查询超出了简单取样的范围。',
    examples: [
      '获取users表的样本数据',
      '我想看看products表中有什么数据',
      '显示orders表的前5行数据',
      '查看customer表的数据格式和内容示例',
    ],
    parameters: {
      tableName: '要查询的表名(必需)',
      limit: '要返回的行数(可选,默认10)',
    },
    returnsExample: `[
      { "id": 1, "product_name": "Laptop", "price": 1200 },
      { "id": 2, "product_name": "Smartphone", "price": 800 }
    ]`,
  },

  // 获取表结构工具
  getTableStructure: {
    name: '获取表结构',
    description:
      '获取指定表的列定义和结构信息。当需要了解表的模式、字段类型或列属性时使用此工具。该工具返回表中所有列的详细信息,包括列名、数据类型、是否可为空及描述。',
    when_to_use:
      '需要详细了解表结构、列定义、数据类型或准备执行查询前检查列名时使用。特别适用于:1)查看表的完整列定义;2)了解列的数据类型和约束;3)准备编写SQL查询前确认列名;4)了解主键和外键列;5)用户明确询问表结构或表定义时。',
    when_not_to_use:
      '不适合查看表数据内容或执行查询,这些操作应分别使用getTableSample或executeQuery。若需同时了解表结构和示例数据,可考虑使用db://tables/{tableName}资源。不应用于:1)只关心数据内容不关心结构时;2)需要了解表间关系时(应使用db://relationships);3)需要执行数据筛选或聚合时;4)用户没有明确要求表结构细节时。',
    examples: [
      'users表有哪些列',
      '获取products表的结构',
      '显示orders表的字段定义',
      'customer表的主键和数据类型是什么',
    ],
    parameters: {
      tableName: '要查询结构的表名(必需)',
    },
    returnsExample: `[
      { "column_name": "id", "data_type": "integer", "is_nullable": false, "description": "主键" },
      { "column_name": "name", "data_type": "character varying", "is_nullable": false, "description": "用户名" }
    ]`,
  },

  // 获取数据库统计信息工具
  getDatabaseStats: {
    name: '获取数据库统计信息',
    description:
      '获取数据库中所有表的行数统计信息。当需要了解数据库整体情况、表大小或数据分布时使用此工具。该工具返回所有公共模式中表的名称和行数,帮助识别大表和空表。',
    when_to_use:
      '需要了解数据库整体规模、表大小对比或识别最大/空表时使用。这有助于数据库性能分析和查询规划。适合于:1)评估表大小和数据分布;2)找出数据量最大的表;3)检测空表;4)为查询优化提供数据量参考;5)用户询问数据量或表大小相关问题时。',
    when_not_to_use:
      '不适合查询特定表数据或表结构,这些操作应分别使用getTableSample/executeQuery或getTableStructure。不适用于:1)需要了解表结构时;2)需要查看实际数据内容时;3)需要了解表间关系时;4)需要执行具体业务查询时;5)用户对数据量没有明确关注点时。此工具仅提供数量统计,不提供数据内容或结构信息。',
    examples: [
      '数据库中有多少数据',
      '各个表的行数是多少',
      '哪些表包含最多的数据',
      '数据库中有哪些空表',
    ],
    returnsExample: `[
      { "table_schema": "public", "table_name": "users", "row_count": 1250 },
      { "table_schema": "public", "table_name": "products", "row_count": 3764 },
      { "table_schema": "public", "table_name": "orders", "row_count": 8927 }
    ]`,
  },
};/**
 * 注册数据库查询相关工具
 * @param {Object} server MCP服务器实例
 * @param {Object} supabaseService Supabase服务实例
 */
function registerQueryTools(server, supabaseService) {
  // 添加诊断工具
  server.tool(
    'checkDatabaseConnection',
    {},
    async () => {
      try {
        logInfo('开始数据库连接诊断...');

        // 获取Supabase环境配置状态
        const supabaseUrl = process.env.SUPABASE_URL;
        const supabaseKey = process.env.SUPABASE_KEY;

        const configStatus = {
          hasUrl: !!supabaseUrl,
          hasKey: !!supabaseKey,
          urlStart: supabaseUrl ? supabaseUrl.substring(0, 10) + '...' : 'undefined',
          keyLength: supabaseKey ? supabaseKey.length : 0,
        };

        // 尝试获取表定义
        logInfo('正在尝试获取表定义...');
        const tables = await supabaseService.getTableDefinitions();

        const tablesInfo = Array.isArray(tables)
          ? {
              count: tables.length,
              names: tables.map(t => t.table_name || '未知表名'),
            }
          : {
              count: 0,
              error: '返回的表定义不是数组',
              actualType: typeof tables,
              actualValue: JSON.stringify(tables).substring(0, 100),
            };

        return {
          content: [
            {
              type: 'text',
              text: `数据库连接诊断结果:
              
1. 配置状态:
   - SUPABASE_URL 配置: ${configStatus.hasUrl ? '已设置' : '未设置'} (${configStatus.urlStart})
   - SUPABASE_KEY 配置: ${configStatus.hasKey ? '已设置' : '未设置'} (长度: ${configStatus.keyLength})

2. 表定义查询:
   - 成功获取: ${Array.isArray(tables) ? '是' : '否'}
   - 表数量: ${tablesInfo.count}
   - ${
     Array.isArray(tables)
       ? `表名列表: ${tablesInfo.names.join(', ')}`
       : `错误信息: ${tablesInfo.error}, 实际类型: ${tablesInfo.actualType}, 值: ${tablesInfo.actualValue}`
   }

诊断完成,数据库连接${Array.isArray(tables) ? '正常' : '异常'}。`,
            },
          ],
        };
      } catch (error) {
        logError('数据库连接诊断失败: ' + error);

        return {
          content: [
            {
              type: 'text',
              text: `数据库连接诊断错误:
              
错误信息: ${error.message}
错误类型: ${error.name}
调用栈: ${error.stack}

请检查:
1. Supabase配置是否正确设置 (SUPABASE_URL, SUPABASE_KEY)
2. Supabase服务是否在线并可访问
3. Supabase数据库是否已配置必要的函数 (get_table_definitions 等)
4. 网络连接是否正常`,
            },
          ],
          isError: true,
        };
      }
    },
    {
      description: toolDescriptions.checkDatabaseConnection.description,
      name: toolDescriptions.checkDatabaseConnection.name,
      usage_examples: toolDescriptions.checkDatabaseConnection.examples,
      returns_example: toolDescriptions.checkDatabaseConnection.returnsExample,
    }
  );

  // 添加环境变量诊断工具
  server.tool(
    'checkEnvironment',
    {},
    async () => {
      try {
        // 安全地获取环境变量信息,不暴露完整密钥
        const envInfo = {
          NODE_ENV: process.env.NODE_ENV || '未设置',
          SUPABASE_URL: process.env.SUPABASE_URL
            ? `已设置 (${process.env.SUPABASE_URL.substring(0, 15)}...)`
            : '未设置',
          SUPABASE_KEY: process.env.SUPABASE_KEY
            ? `已设置 (长度: ${process.env.SUPABASE_KEY.length})`
            : '未设置',
          MCP_STDIO: process.env.MCP_STDIO || '未设置',
          APP_PORT: process.env.APP_PORT || '默认 (9000)',
          NODE_VERSION: process.version,
          PLATFORM: process.platform,
          UPTIME: `${Math.floor(process.uptime())} 秒`,
        };

        return {
          content: [
            {
              type: 'text',
              text: `环境变量诊断结果:
              
运行环境:
- NODE_VERSION: ${envInfo.NODE_VERSION}
- PLATFORM: ${envInfo.PLATFORM}
- NODE_ENV: ${envInfo.NODE_ENV}
- 运行时间: ${envInfo.UPTIME}

配置参数:
- SUPABASE_URL: ${envInfo.SUPABASE_URL}
- SUPABASE_KEY: ${envInfo.SUPABASE_KEY}
- MCP_STDIO: ${envInfo.MCP_STDIO}
- APP_PORT: ${envInfo.APP_PORT}

如果您看到"未设置"的关键变量,请确保正确配置 .env 文件。`,
            },
          ],
        };
      } catch (error) {
        return {
          content: [
            {
              type: 'text',
              text: `环境变量诊断错误: ${error.message}`,
            },
          ],
          isError: true,
        };
      }
    },
    {
      description: toolDescriptions.checkEnvironment.description,
      name: toolDescriptions.checkEnvironment.name,
      usage_examples: toolDescriptions.checkEnvironment.examples,
      returns_example: toolDescriptions.checkEnvironment.returnsExample,
    }
  );

  // 执行SQL查询工具
  server.tool(
    'executeQuery',
    {
      query: z.string().min(1, 'SQL查询不能为空'),
    },
    async ({ query }) => {
      try {
        const result = await supabaseService.executeReadQuery(query);
        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify(result, null, 2),
            },
          ],
        };
      } catch (error) {
        return {
          content: [
            {
              type: 'text',
              text: `查询执行错误: ${error.message}`,
            },
          ],
          isError: true,
        };
      }
    },
    {
      description: toolDescriptions.executeQuery.description,
      name: toolDescriptions.executeQuery.name,
      usage_examples: toolDescriptions.executeQuery.examples,
      returns_example: toolDescriptions.executeQuery.returnsExample,
      parameters_description: toolDescriptions.executeQuery.parameters,
    }
  );

  // 获取表样本数据工具
  server.tool(
    'getTableSample',
    {
      tableName: z.string().min(1, '表名不能为空'),
      limit: z.number().optional().default(10),
    },
    async ({ tableName, limit }) => {
      try {
        const result = await supabaseService.getTableSample(tableName, limit);
        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify(result, null, 2),
            },
          ],
        };
      } catch (error) {
        return {
          content: [
            {
              type: 'text',
              text: `获取表样本数据错误: ${error.message}`,
            },
          ],
          isError: true,
        };
      }
    },
    {
      description: toolDescriptions.getTableSample.description,
      name: toolDescriptions.getTableSample.name,
      usage_examples: toolDescriptions.getTableSample.examples,
      returns_example: toolDescriptions.getTableSample.returnsExample,
      parameters_description: toolDescriptions.getTableSample.parameters,
    }
  );

  // 获取表结构工具
  server.tool(
    'getTableStructure',
    {
      tableName: z.string().min(1, '表名不能为空'),
    },
    async ({ tableName }) => {
      try {
        const columns = await supabaseService.getTableColumns(tableName);
        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify(columns, null, 2),
            },
          ],
        };
      } catch (error) {
        return {
          content: [
            {
              type: 'text',
              text: `获取表结构错误: ${error.message}`,
            },
          ],
          isError: true,
        };
      }
    },
    {
      description: toolDescriptions.getTableStructure.description,
      name: toolDescriptions.getTableStructure.name,
      usage_examples: toolDescriptions.getTableStructure.examples,
      returns_example: toolDescriptions.getTableStructure.returnsExample,
      parameters_description: toolDescriptions.getTableStructure.parameters,
    }
  );

  // 获取数据库统计信息工具
  server.tool(
    'getDatabaseStats',
    {},
    async () => {
      try {
        const query = `
          SELECT 
            table_schema,
            table_name,
            (xpath('/row/cnt/text()', query_to_xml('select count(*) as cnt from "' || table_schema || '"."' || table_name || '"', true, false, '')))[1]::text::int as row_count
          FROM 
            information_schema.tables
          WHERE 
            table_schema NOT IN ('pg_catalog', 'information_schema')
            AND table_type = 'BASE TABLE'
          ORDER BY 
            table_schema, table_name
        `;

        const result = await supabaseService.executeReadQuery(query);
        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify(result, null, 2),
            },
          ],
        };
      } catch (error) {
        return {
          content: [
            {
              type: 'text',
              text: `获取数据库统计信息错误: ${error.message}`,
            },
          ],
          isError: true,
        };
      }
    },
    {
      description: toolDescriptions.getDatabaseStats.description,
      name: toolDescriptions.getDatabaseStats.name,
      usage_examples: toolDescriptions.getDatabaseStats.examples,
      returns_example: toolDescriptions.getDatabaseStats.returnsExample,
    }
  );
}/**
 * 数据库工具使用指南
 * 为LLM提供工具使用场景和决策指导
 */

const toolUsageGuide = `
# Inshow AI Query MCP 工具使用指南

## 工具选择决策树

根据用户意图选择最合适的工具或资源:

### 1. 诊断问题和故障排除
如果用户询问为什么查询失败或报错:
- **首选**: \`checkDatabaseConnection\` - 验证数据库连接是否正常
- **其次**: \`checkEnvironment\` - 检查环境配置是否正确
- **然后**: 根据诊断结果提供具体修复建议

### 2. 数据库结构探索
如果用户想了解数据库结构:
- **首选**: \`db://overview\` - 获取所有表的概览和描述
- **然后**: \`db://relationships\` - 了解表之间的关系
- **最后**: \`db://tables/{tableName}\` 或 \`getTableStructure\` - 查看特定表结构

### 3. 数据查询和浏览
如果用户想查看或分析表数据:
- **简单查看**: \`getTableSample\` - 获取表的示例数据
- **结构化数据**: \`getTableStructure\` - 了解表结构
- **复杂查询**: \`executeQuery\` - 执行自定义SQL查询
- **统计信息**: \`getDatabaseStats\` - 获取表行数统计

### 4. 查询路径决策图

查询路径决策流程:
1. 首先检查是否有连接/环境问题?
   - 如果有: 使用checkDatabaseConnection或checkEnvironment
   - 如果没有: 继续下一步

2. 需要了解数据库整体结构?
   - 如果是: 访问db://overview
   - 如果否: 继续下一步

3. 需要了解表之间的关系?
   - 如果是: 访问db://relationships
   - 如果否: 继续下一步

4. 是否只关注特定表?
   - 如果否: 返回使用db://overview
   - 如果是: 继续下一步

5. 是否同时需要表结构和数据示例?
   - 如果是: 访问db://tables/{tableName}
   - 如果否: 继续下一步

6. 是否只需要查看表结构?
   - 如果是: 使用getTableStructure
   - 如果否: 继续下一步

7. 是否只需要表数据示例?
   - 如果是: 使用getTableSample
   - 如果否: 继续下一步

8. 是否需要复杂查询或数据分析?
   - 如果是: 使用executeQuery

## 工具使用场景对比表

| 用户需求 | 最佳工具/资源 | 理由 |
|---------|-------------|------|
| 初次了解数据库 | \`db://overview\` | 提供所有表的概览和描述,最适合初步探索 |
| 理解数据模型 | \`db://relationships\` | 展示表之间的关系和外键约束 |
| 了解特定表结构 | \`getTableStructure\` | 返回详细的列定义和数据类型 |
| 快速查看表数据 | \`getTableSample\` | 返回表的前N行数据,简单直接 |
| 复杂数据查询 | \`executeQuery\` | 支持自定义SQL查询,最灵活 |
| 表大小对比 | \`getDatabaseStats\` | 返回所有表的行数统计 |
| 连接问题排查 | \`checkDatabaseConnection\` | 诊断数据库连接和配置问题 |
| 环境配置检查 | \`checkEnvironment\` | 显示应用环境和配置参数 |

### 特定工具详细对比

#### 1. \`getTableStructure\` vs \`db://tables/{tableName}\`

- **getTableStructure**:
  - 返回JSON格式的列定义
  - 只包含表结构,不包含数据
  - 适合需要程序处理的场景
  - 例如: "获取users表的列定义"

- **db://tables/{tableName}**:
  - 返回格式化的Markdown文本
  - 同时包含表结构和样本数据
  - 提供更全面的表信息
  - 例如: "我想全面了解products表"

#### 2. \`getTableSample\` vs \`executeQuery\`

- **getTableSample**:
  - 简单API,只需表名参数
  - 返回前N行数据,无需编写SQL
  - 无法过滤或排序数据
  - 例如: "显示customers表的样本数据"

- **executeQuery**:
  - 需要编写SQL查询
  - 支持复杂条件、联接、排序、分组
  - 可以自定义返回的列和行
  - 例如: "查询近30天的高价值订单"

## SQL查询最佳实践

使用 \`executeQuery\` 工具时的建议:

### 1. 性能与安全

- **限制结果集大小**
  - 总是使用LIMIT子句限制返回行数
  - 对于大表,考虑先使用COUNT查询了解数据量

- **选择性查询**
  - 避免SELECT *,只选择需要的列
  - 使用WHERE子句过滤不必要的数据

- **优化联接操作**
  - 联接前先评估表大小(getDatabaseStats)
  - 小表放在联接右侧,大表放在左侧
  - 对多表联接使用适当的JOINs类型

### 2. 常见查询模式模板

**基本聚合查询**:
\`\`\`sql
SELECT 
  category,
  COUNT(*) as count,
  AVG(price) as avg_price,
  SUM(quantity) as total_quantity
FROM 
  products
WHERE 
  created_at >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY 
  category
ORDER BY 
  count DESC
LIMIT 10
\`\`\`

**多表联接查询**:
\`\`\`sql
SELECT 
  u.username,
  COUNT(o.id) AS order_count,
  SUM(o.total) AS total_spent
FROM 
  users u
  LEFT JOIN orders o ON u.id = o.user_id
WHERE 
  u.status = 'active'
  AND o.created_at >= CURRENT_DATE - INTERVAL '90 days'
GROUP BY 
  u.id, u.username
ORDER BY 
  total_spent DESC
LIMIT 15
\`\`\`

**子查询与CTE示例**:
\`\`\`sql
-- 使用CTE提高可读性
WITH recent_orders AS (
  SELECT 
    user_id, 
    COUNT(*) as order_count,
    SUM(total) as total_spent
  FROM 
    orders
  WHERE 
    created_at >= CURRENT_DATE - INTERVAL '30 days'
  GROUP BY 
    user_id
)

SELECT 
  u.username,
  u.email,
  COALESCE(ro.order_count, 0) as order_count,
  COALESCE(ro.total_spent, 0) as total_spent
FROM 
  users u
  LEFT JOIN recent_orders ro ON u.id = ro.user_id
ORDER BY 
  total_spent DESC
LIMIT 20
\`\`\`

### 3. 查询错误防范

- **表不存在**: 先使用db://overview确认表名
- **列不存在**: 先使用getTableStructure获取正确列名
- **数据类型不匹配**: 注意日期、数字和文本类型的正确使用
- **NULL值处理**: 使用COALESCE或IS NULL/IS NOT NULL正确处理

### 4. 简单vs复杂工具选择

- **只需查看少量数据时,避免使用executeQuery**
  - 不要写: \`executeQuery("SELECT * FROM users LIMIT 10")\`
  - 应该用: \`getTableSample({ tableName: "users", limit: 10 })\`

- **只查看表结构时,避免复杂SQL**
  - 不要写: \`executeQuery("SELECT column_name, data_type FROM information_schema.columns WHERE table_name='users'")\`
  - 应该用: \`getTableStructure({ tableName: "users" })\`

- **多表关系查看时,优先使用资源而非查询**
  - 不要写: \`executeQuery("SELECT * FROM information_schema.table_constraints WHERE constraint_type='FOREIGN KEY'")\`
  - 应该用: \`访问db://relationships资源\`
`;

const resourceUsageGuide = `
# Inshow AI Query MCP 资源使用指南

## 资源类型与用途

MCP提供四种主要数据库资源类型,每种都有特定用途:

### 1. 数据库概览 (\`db://overview\`)
- **定义**: 数据库中所有表的高级概述
- **内容**: 表名和表描述列表
- **格式**: Markdown文本,适合人类阅读
- **使用时机**: 
  - 初次了解数据库时
  - 需要快速了解可用表时
  - 探索未知数据库的第一步

### 2. 表列表 (\`db://tables\`)
- **定义**: 所有表的结构化资源列表
- **内容**: 包含URI和表名的列表
- **格式**: JSON结构,适合程序处理
- **使用时机**:
  - 需要以程序方式获取表列表时
  - 计划后续访问多个特定表时
  - 需要根据表名构建URI时

### 3. 表详情 (\`db://tables/{tableName}\`)
- **定义**: 特定表的详细信息
- **内容**: 列定义和样本数据
- **格式**: 格式化Markdown,包含表格和代码区
- **使用时机**:
  - 需要同时了解表结构和数据时
  - 希望获得人类可读的表完整信息时
  - 需要列定义和数据示例的组合视图时

### 4. 数据库关系图 (\`db://relationships\`)
- **定义**: 表之间的关系和引用
- **内容**: 外键约束和PlantUML格式ER图
- **格式**: Markdown文本与表格
- **使用时机**:
  - 需要理解数据模型时
  - 准备编写多表联接查询前
  - 分析表之间的依赖关系时

## 资源选择决策流程

### 探索未知数据库
1. 获取数据库概览: \`db://overview\`
2. 查看表之间的关系: \`db://relationships\`
3. 深入了解关键表: \`db://tables/{重要表名}\`

### 特定表的深入分析
1. 获取表详情: \`db://tables/{tableName}\`
2. 检查表与其他表的关系: \`db://relationships\` (找到相关表)
3. 获取关联表详情: \`db://tables/{关联表名}\`

### 为复杂查询做准备
1. 了解表结构: \`db://tables/{tableName}\` 或 \`getTableStructure\`
2. 检查表关系: \`db://relationships\`
3. 获取样本数据: \`getTableSample\` 或通过表详情资源
4. 设计并执行查询: \`executeQuery\`

## 资源与工具联合使用模式

最有效的策略是结合使用资源和工具,形成完整的工作流:

1. **探索阶段**
   - 使用资源了解数据库结构: \`db://overview\`, \`db://relationships\`
   - 检查数据库规模: \`getDatabaseStats\`

2. **分析阶段**
   - 查看特定表的详细信息: \`db://tables/{tableName}\`
   - 获取更多样本数据: \`getTableSample\`

3. **查询阶段**
   - 执行特定查询: \`executeQuery\`
   - 分析查询结果,提供见解

每个阶段使用的工具或资源应该基于当前任务的具体需求选择,避免使用过于复杂的工具来完成简单任务。

## 常见错误模式和防范

### 1. 工具过度选择

**错误模式**: 使用过于复杂的工具完成简单任务。

**示例**:
\`\`\`
用户: "显示所有表名称"
错误: 使用executeQuery("SELECT table_name FROM information_schema.tables WHERE table_schema='public'")
正确: 访问db://overview获取表概览
\`\`\`

### 2. 工具不足选择

**错误模式**: 使用能力不足的工具尝试完成复杂任务。

**示例**:
\`\`\`
用户: "查找去年消费超过1000元的VIP客户"
错误: 尝试使用getTableSample然后手动筛选
正确: 使用executeQuery执行JOIN查询和复杂条件筛选
\`\`\`

### 3. 忽略资源直接查询

**错误模式**: 跳过资源直接编写SQL查询,缺少必要上下文。

**示例**:
\`\`\`
用户: "如何查询订单和用户关联数据"
错误: 直接构建JOIN查询但不了解表关系
正确: 先查看db://relationships了解关系,再构建查询
\`\`\`
`;/**
 * MCP系统提示配置
 * 为LLM提供关于如何正确使用工具和资源的指导
 */

// 完整的系统提示,指导LLM如何使用工具
const toolSelectionSystemPrompt = `
# Inshow AI Query MCP 系统

你是一个专门处理数据库查询和分析的助手,可以访问Supabase数据库并执行SQL查询。
你的主要任务是帮助用户探索数据库、执行查询、分析数据以及解决可能出现的问题。

## 核心工具选择原则

1. **最小复杂度原则**: 总是选择能完成任务的最简单工具。仅当简单工具无法满足需求时,才使用更复杂的工具。
2. **工具专用性原则**: 优先选择为特定任务设计的专用工具,而非通用工具。
3. **渐进探索原则**: 在未知数据库上,先获取概览,再深入具体表,最后执行查询。

## 可用工具

你可以使用以下工具和资源来完成任务:

### 诊断工具
- checkDatabaseConnection - 诊断数据库连接问题
- checkEnvironment - 检查环境配置

### 数据查询工具
- executeQuery - 执行SQL查询 (仅当其他工具无法满足需求时使用)
- getTableSample - 获取表样本数据 (优先用于简单数据查看)
- getTableStructure - 获取表结构 (优先用于结构查询)
- getDatabaseStats - 获取数据库统计信息 (优先用于表大小分析)

### 数据库资源
- db://overview - 数据库概览 (探索的第一步)
- db://tables - 表列表 (用于获取表资源)
- db://tables/{tableName} - 特定表详情 (同时查看结构和数据)
- db://relationships - 数据库关系图 (了解表间关系)

${toolUsageGuide}

${resourceUsageGuide}

## 处理用户请求的方法

1. **意图识别**:
   - 首先确定用户是需要探索、查询、分析还是诊断
   - 识别用户是否知道具体表名或只是泛泛而谈

2. **工具选择原则**:
   - 总是优先使用专用工具,避免过度依赖executeQuery
   - 对于结构性探索,使用资源而不是查询
   - 对于简单数据查看,使用getTableSample而非executeQuery
   - 只有在需要条件过滤、多表联接、聚合计算时才使用executeQuery

3. **渐进式探索路径**:
   - 未知数据库: db://overview -> db://relationships -> 特定表详情
   - 已知表: getTableStructure -> getTableSample -> 根据需要executeQuery
   - 错误情况: checkDatabaseConnection -> checkEnvironment -> 排查具体问题

4. **常见错误防范**:
   - 避免对简单查询使用复杂工具
   - 避免在不了解表结构的情况下直接编写查询
   - 避免跳过资源直接使用工具
   - 遇到错误时,优先使用诊断工具而非重试

始终优先选择最简单、最专用的方法来满足用户需求,遵循"最小复杂度原则"。
如果用户请求的操作可以通过多种工具完成,优先选择专为该任务设计的工具。
`;

/**
 * 注册系统提示和指南
 * @param {Object} server MCP服务器实例
 */
function registerSystemPrompts(server) {
  // 为所有会话添加系统提示
  server.prompt('system-prompt', {}, () => ({
    messages: [
      {
        role: 'system',
        content: {
          type: 'text',
          text: toolSelectionSystemPrompt,
        },
      },
    ],
  }));

  // 注册特定任务的提示

  // 表探索提示
  server.prompt('explore-tables', {}, () => ({
    messages: [
      {
        role: 'system',
        content: {
          type: 'text',
          text: `作为数据库助手,请遵循工具选择最佳实践,首先使用资源和工具来探索数据库结构:

1. 获取数据库概览 (db://overview) - 了解所有可用表和它们的用途
2. 获取表之间的关系 (db://relationships) - 理解数据模型和引用关系
3. 获取各表的行数统计 (getDatabaseStats) - 了解数据分布情况

根据这些信息,为用户提供一个结构化的数据库总结,包括:
- 主要实体表及其用途
- 关键关系和数据流程
- 推荐的数据探索途径

记住:避免直接使用executeQuery来获取schema信息,而应使用专门的资源和工具。`,
        },
      },
    ],
  }));

  // SQL建议提示
  server.prompt('sql-assistant', {}, () => ({
    messages: [
      {
        role: 'system',
        content: {
          type: 'text',
          text: `作为SQL助手,请先了解数据库结构再构建查询:

1. 数据准备工作:
   - 使用getTableStructure获取相关表的列定义
   - 使用db://relationships了解表之间的关系
   - 使用getDatabaseStats评估表大小,优化查询设计

2. 遵循查询构建最佳实践:
   - 总是使用LIMIT限制结果数量
   - 只选择必要的列,避免SELECT *
   - 对大表使用适当的WHERE条件
   - 小表放在JOIN右侧,大表放在左侧
   - 使用CTE提高复杂查询的可读性

3. 查询验证:
   - 确认表名和列名是正确的
   - 验证数据类型匹配,特别是日期和数字类型
   - 检查是否处理了NULL值

记住:只有在需要复杂条件、多表联接或聚合计算时才使用executeQuery,对于简单的数据获取优先使用getTableSample。`,
        },
      },
    ],
  }));

  // 错误排查提示
  server.prompt('troubleshoot-database', {}, () => ({
    messages: [
      {
        role: 'system',
        content: {
          type: 'text',
          text: `作为数据库故障排除专家,请系统地帮助用户解决问题:

1. 诊断阶段:
   - 首先使用checkDatabaseConnection验证数据库连接状态
   - 使用checkEnvironment检查环境配置
   - 根据错误消息确定问题类别(连接、权限、查询语法、功能)

2. 分析阶段:
   - 对于结构问题,使用db://overview和db://relationships验证表和关系
   - 对于数据问题,使用getTableSample获取示例数据
   - 对于查询问题,通过getTableStructure确认正确的列名和类型

3. 解决阶段:
   - 提供清晰的故障原因解释
   - 给出具体的修复步骤
   - 提供防止问题再次发生的建议

记住:采用渐进式排查,从基础设施开始,再到数据结构,最后到查询细节。避免直接重试失败的操作,优先理解并解决根本问题。`,
        },
      },
    ],
  }));

  // 数据分析提示
  server.prompt('data-analysis', {}, () => ({
    messages: [
      {
        role: 'system',
        content: {
          type: 'text',
          text: `作为数据分析助手,请遵循结构化的分析流程:

1. 准备阶段:
   - 使用getDatabaseStats了解表大小和数据分布
   - 使用db://relationships理解数据模型和实体关系
   - 使用getTableStructure确认分析所需的列和数据类型

2. 分析阶段:
   - 对于简单分析,优先使用专用工具而非复杂查询
   - 对于复杂分析,使用executeQuery执行聚合和统计
   - 总是添加LIMIT子句,避免返回过大结果集
   - 优先使用CTE或子查询提高复杂分析的可读性

3. 解释阶段:
   - 提供清晰的分析结果解释
   - 突出关键发现和见解
   - 提供可能的进一步分析建议

记住:有效的数据分析依赖于对数据结构的正确理解,始终先了解数据模型再进行分析。复杂分析应分解为多个步骤,而不是一个巨大的查询。`,
        },
      },
    ],
  }));
}/**
 * 注册数据分析相关提示
 * @param {Object} server MCP服务器实例
 */
function registerDataAnalysisPrompts(server) {
  // 首先注册系统提示和指南
  registerSystemPrompts(server);

  // 数据探索提示
  server.prompt(
    'data-exploration',
    {
      tableName: z.string().min(1, '表名不能为空'),
    },
    ({ tableName }) => ({
      messages: [
        {
          role: 'user',
          content: {
            type: 'text',
            text: `请帮我探索 ${tableName} 表的数据。我想了解这个表的基本结构、列的数据类型以及可能存在的数据模式或趋势。请使用适当的SQL查询来分析数据,并以易于理解的方式呈现结果。`,
          },
        },
      ],
    }),
    {
      description: '探索指定表的数据结构和内容,生成分析报告',
      arguments_description: {
        tableName: '要探索的表名(必需)',
      },
      usage_examples: ['探索用户表的基本结构和数据模式', '分析订单表中可能存在的趋势'],
    }
  );

  // 数据分析提示
  server.prompt(
    'data-analysis-prompt',
    {
      query: z.string().min(1, '查询不能为空'),
    },
    ({ query }) => ({
      messages: [
        {
          role: 'user',
          content: {
            type: 'text',
            text: `请帮我分析以下SQL查询的结果:\n\n\`\`\`sql\n${query}\n\`\`\`\n\n执行这个查询,并提供详细的数据分析,包括:\n1. 结果的总体概述\n2. 发现的关键趋势或模式\n3. 数据分布情况\n4. 异常值或有趣的观察\n5. 可能的业务洞见\n\n如果可能,请考虑使用统计方法来支持你的分析。`,
          },
        },
      ],
    }),
    {
      description: '分析SQL查询结果,提供数据洞察和统计分析',
      arguments_description: {
        query: '要执行和分析的SQL查询(必需)',
      },
      usage_examples: [
        '分析销售数据的查询结果,查找销售趋势',
        '分析用户行为查询结果,发现用户模式',
      ],
    }
  );

  // 数据可视化建议提示
  server.prompt(
    'visualization-suggestion',
    {
      query: z.string().min(1, '查询不能为空'),
    },
    ({ query }) => ({
      messages: [
        {
          role: 'user',
          content: {
            type: 'text',
            text: `我执行了以下SQL查询:\n\n\`\`\`sql\n${query}\n\`\`\`\n\n请帮我确定最适合可视化这些数据的图表类型。请考虑以下因素:\n1. 查询返回的数据类型(分类、连续、时间序列等)\n2. 变量之间的关系\n3. 想要强调的见解类型\n\n对于你推荐的每种图表类型,请解释为什么它适合这些数据,以及如何构建这种可视化以最好地传达数据中的信息。`,
          },
        },
      ],
    }),
    {
      description: '为SQL查询结果推荐最佳可视化图表类型',
      arguments_description: {
        query: '要可视化结果的SQL查询(必需)',
      },
      usage_examples: ['为销售报表数据推荐合适的图表类型', '针对时间序列数据建议最佳可视化方式'],
    }
  );

  // SQL查询生成提示
  server.prompt(
    'generate-sql',
    {
      description: z.string().min(1, '描述不能为空'),
    },
    ({ description }) => ({
      messages: [
        {
          role: 'user',
          content: {
            type: 'text',
            text: `请根据以下描述生成SQL查询:\n\n${description}\n\n请首先分析我的需求,然后生成符合PostgreSQL语法的SQL查询。确保查询是高效的,并包含适当的注释来解释复杂的部分。如果有多种方法可以实现,请提供最优的解决方案并解释你的选择理由。`,
          },
        },
      ],
    }),
    {
      description: '根据自然语言描述生成SQL查询',
      arguments_description: {
        description: '查询需求的自然语言描述(必需)',
      },
      usage_examples: ['生成查询过去30天内最活跃用户的SQL', '创建按类别统计产品销量的查询'],
    }
  );

  // 数据质量评估提示
  server.prompt(
    'data-quality-assessment',
    {
      tableName: z.string().min(1, '表名不能为空'),
    },
    ({ tableName }) => ({
      messages: [
        {
          role: 'user',
          content: {
            type: 'text',
            text: `请帮我评估 ${tableName} 表的数据质量。我需要了解:\n\n1. 完整性:是否存在缺失值?各列的缺失率如何?\n2. 准确性:数据是否在合理范围内?是否存在异常值?\n3. 一致性:数据格式是否一致?是否存在重复记录?\n4. 及时性:数据是否最新?\n\n请使用适当的SQL查询来检查这些问题,并提供改进数据质量的建议。`,
          },
        },
      ],
    }),
    {
      description: '评估表数据质量,包括完整性、准确性、一致性和及时性',
      arguments_description: {
        tableName: '要评估质量的表名(必需)',
      },
      usage_examples: ['检查客户表的数据质量问题', '评估产品数据的完整性和一致性'],
    }
  );

  // 数据关系探索提示
  server.prompt(
    'relationship-exploration',
    {
      tableA: z.string().min(1, '第一个表名不能为空'),
      tableB: z.string().min(1, '第二个表名不能为空'),
    },
    ({ tableA, tableB }) => ({
      messages: [
        {
          role: 'user',
          content: {
            type: 'text',
            text: `请帮我探索 ${tableA} 和 ${tableB} 这两个表之间的关系。我想了解:\n\n1. 这两个表是如何关联的(通过哪些字段)\n2. 它们之间的基数关系(一对一、一对多、多对多)\n3. 两个表中数据的相关模式或趋势\n\n请使用适当的SQL查询(如JOIN操作)来分析这些关系,并以清晰的方式解释发现。如果可能,请提供优化这些关系的建议。`,
          },
        },
      ],
    }),
    {
      description: '探索两个表之间的关系和数据模式',
      arguments_description: {
        tableA: '第一个表名(必需)',
        tableB: '第二个表名(必需)',
      },
      usage_examples: ['分析用户表和订单表之间的关系', '探索产品表和类别表之间的数据关联'],
    }
  );

  // 表格数据摘要提示
  server.prompt(
    'table-summary',
    {
      tableName: z.string().min(1, '表名不能为空'),
      limit: z.number().optional().default(1000),
    },
    ({ tableName, limit }) => ({
      messages: [
        {
          role: 'user',
          content: {
            type: 'text',
            text: `请为 ${tableName} 表提供一个简洁的数据摘要。分析表中最多 ${limit} 行数据,包括:
1. 数值列的基本统计信息(最小值、最大值、平均值、中位数等)
2. 分类列的值分布和频率
3. 日期列的时间范围和分布
4. 识别的主要数据特征和模式

使用适当的SQL查询来生成这些统计信息,并以清晰、结构化的方式呈现结果。`,
          },
        },
      ],
    }),
    {
      description: '生成表数据的统计摘要和关键特征',
      arguments_description: {
        tableName: '要分析的表名(必需)',
        limit: '分析的最大行数(可选,默认1000)',
      },
      usage_examples: ['生成客户表的数据统计摘要', '分析销售表的前500行数据特征'],
    }
  );

  // 数据库健康检查提示
  server.prompt(
    'database-health-check',
    {},
    () => ({
      messages: [
        {
          role: 'user',
          content: {
            type: 'text',
            text: `请对数据库进行全面的健康检查,分析以下方面:
1. 表大小和行数统计
2. 查询性能指标
3. 数据完整性检查
4. 可能的优化机会

使用适当的诊断工具和查询收集这些信息,并提供一份简明的报告,包括任何发现的问题和改进建议。`,
          },
        },
      ],
    }),
    {
      description: '执行数据库健康检查,评估性能和完整性',
      usage_examples: ['进行数据库全面健康检查', '检查数据库性能和完整性'],
    }
  );
}/**
 * 启动基于stdio的MCP服务器
 * @param {Object} server - MCP服务器实例
 * @param {Object} options - 配置选项
 * @returns {Promise<StdioServerTransport>} 已连接的STDIO传输实例
 */
async function startStdioServer(server, options = {}) {
  logInfo('MCP服务器运行于STDIO模式');

  // 创建并连接STDIO传输
  const stdioTransport = new StdioServerTransport();

  try {
    // 连接到MCP服务器
    await server.connect(stdioTransport);
    logInfo('STDIO传输已连接');
    return stdioTransport;
  } catch (error) {
    logError(`STDIO传输连接失败: ${error.message}`);
    throw error;
  }
}/**
 * 启动基于Streamable HTTP的MCP服务器
 * 遵循MCP协议的Streamable HTTP规范,支持会话恢复、无状态服务和流式响应
 *
 * @param {Object} server - MCP服务器实例
 * @param {Object} supabaseService - Supabase服务实例
 * @param {Object} options - 配置选项
 * @returns {Object} Express应用实例
 */
function startHttpServer(server, supabaseService, options = {}) {
  const app = express();
  app.use(express.json({ limit: '10mb' }));

  // 启用CORS支持以便跨域调用
  app.use((req, res, next) => {
    res.header('Access-Control-Allow-Origin', options.corsOrigin || '*');
    res.header('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
    res.header(
      'Access-Control-Allow-Headers',
      'Origin, X-Requested-With, Content-Type, Accept, mcp-session-id'
    );
    res.header('Access-Control-Expose-Headers', 'mcp-session-id');

    if (req.method === 'OPTIONS') {
      return res.status(200).end();
    }
    next();
  });

  // 会话管理 - 存储传输实例
  const transports = {};

  // 会话保活与清理 - 自动清理长时间未活动的会话
  const SESSION_TIMEOUT = options.sessionTimeout || 30 * 60 * 1000; // 默认30分钟

  if (!options.disableSessionCleanup) {
    setInterval(() => {
      const now = Date.now();
      Object.entries(transports).forEach(([sessionId, transport]) => {
        if (transport.lastActivity && now - transport.lastActivity > SESSION_TIMEOUT) {
          logInfo(`会话 ${sessionId} 超时,正在清理`);
          delete transports[sessionId];
          if (typeof transport.close === 'function') {
            transport.close();
          }
        }
      });
    }, 60000); // 每分钟检查一次
  }

  // 错误处理中间件
  app.use((err, req, res, next) => {
    logError(`Express错误: ${err.message}`);
    console.error(err.stack);
    res.status(500).json({
      jsonrpc: '2.0',
      error: {
        code: -32000,
        message: `服务器内部错误: ${err.message}`,
      },
      id: null,
    });
  });

  // 统一的MCP端点,处理初始化和后续请求
  app.post('/mcp', async (req, res) => {
    // 检查现有会话ID
    const sessionId = req.headers['mcp-session-id'];
    let transport;

    if (sessionId && transports[sessionId]) {
      // 复用现有传输
      transport = transports[sessionId];
      transport.lastActivity = Date.now(); // 更新会话活动时间
    } else if (!sessionId && isInitializeRequest(req.body)) {
      // 创建新的初始化请求 (无状态或有状态都支持)
      transport = new StreamableHTTPServerTransport({
        sessionIdGenerator: () => randomUUID(),
        onsessioninitialized: newSessionId => {
          // 按会话ID存储传输
          transports[newSessionId] = transport;
          transport.lastActivity = Date.now();

          // 记录会话创建信息
          if (options.debug) {
            logInfo(`新会话已创建: ${newSessionId}`);
          }
        },
      });

      // 传输关闭时清理
      transport.onclose = () => {
        if (transport.sessionId) {
          if (options.debug) {
            logInfo(`会话已关闭: ${transport.sessionId}`);
          }
          delete transports[transport.sessionId];
        }
      };

      // 连接到MCP服务器
      await server.connect(transport);
    } else {
      // 无效请求
      res.status(400).json({
        jsonrpc: '2.0',
        error: {
          code: -32000,
          message: 'Bad Request: No valid session ID provided',
        },
        id: null,
      });
      return;
    }

    // 处理请求 - 对于响应可以是普通HTTP或SSE流
    await transport.handleRequest(req, res, req.body);
  });

  // 处理GET请求,用于建立SSE流或获取服务端通知
  app.get('/mcp', async (req, res) => {
    const sessionId = req.headers['mcp-session-id'];

    // 特殊情况:没有会话ID但允许创建的场景
    if (!sessionId && options.allowAnonymousSSE) {
      // 创建匿名会话并建立SSE
      const transport = new StreamableHTTPServerTransport({
        sessionIdGenerator: () => randomUUID(),
        onsessioninitialized: newSessionId => {
          transports[newSessionId] = transport;
          transport.lastActivity = Date.now();

          if (options.debug) {
            logInfo(`匿名SSE会话已创建: ${newSessionId}`);
          }
        },
      });

      transport.onclose = () => {
        if (transport.sessionId) {
          delete transports[transport.sessionId];
        }
      };

      await server.connect(transport);
      await transport.handleRequest(req, res);
      return;
    }

    // 常规情况:需要有效会话ID
    if (!sessionId || !transports[sessionId]) {
      res.status(400).json({
        jsonrpc: '2.0',
        error: {
          code: -32000,
          message: 'Invalid or missing session ID',
        },
        id: null,
      });
      return;
    }

    const transport = transports[sessionId];
    transport.lastActivity = Date.now(); // 更新会话活动时间

    // 处理SSE流请求
    await transport.handleRequest(req, res);
  });

  // 处理DELETE请求,用于会话终止
  app.delete('/mcp', async (req, res) => {
    const sessionId = req.headers['mcp-session-id'];
    if (!sessionId || !transports[sessionId]) {
      res.status(400).json({
        jsonrpc: '2.0',
        error: {
          code: -32000,
          message: 'Invalid or missing session ID',
        },
        id: null,
      });
      return;
    }

    const transport = transports[sessionId];

    // 优雅关闭会话
    try {
      if (typeof transport.close === 'function') {
        await transport.close();
      }
      delete transports[sessionId];
      res.status(200).json({ success: true, message: 'Session terminated' });

      if (options.debug) {
        logInfo(`会话已主动终止: ${sessionId}`);
      }
    } catch (error) {
      logError(`终止会话 ${sessionId} 时发生错误: ${error.message}`);
      res.status(500).json({
        jsonrpc: '2.0',
        error: {
          code: -32000,
          message: `Failed to terminate session: ${error.message}`,
        },
        id: null,
      });
    }
  });

  // 健康检查端点
  app.get('/health', async (req, res) => {
    try {
      // 尝试获取表定义以验证数据库连接
      const tables = await supabaseService.getTableDefinitions();
      res.json({
        status: 'ok',
        uptime: process.uptime(),
        timestamp: new Date().toISOString(),
        version: '1.0.0',
        protocol: 'Streamable HTTP',
        activeSessions: Object.keys(transports).length,
        database: {
          connected: true,
          tables: tables.length,
        },
      });
    } catch (error) {
      logError(`健康检查失败: ${error.message}`);
      res.status(500).json({
        status: 'error',
        uptime: process.uptime(),
        timestamp: new Date().toISOString(),
        error: error.message,
        database: {
          connected: false,
        },
      });
    }
  });

  // 启动服务器
  const APP_PORT = options.port || 9000;
  const appServer = app.listen(APP_PORT, () => {
    logInfo(`MCP Streamable HTTP 服务器已启动,端口: ${APP_PORT}`);
    if (options.debug) {
      logInfo(`服务地址: http://localhost:${APP_PORT}/mcp`);
      logInfo(`健康检查: http://localhost:${APP_PORT}/health`);
    }
  });

  // 优雅关闭
  process.on('SIGTERM', () => {
    logInfo('收到SIGTERM信号,正在优雅关闭服务...');
    appServer.close(() => {
      logInfo('服务器已关闭');
      process.exit(0);
    });
  });

  return app;
}// 设置命令行参数
const program = new Command();

program
  .name('inshow-ai-query-mcp')
  .description('Inshow AI Query MCP Service')
  .version('1.0.0')
  .option('-p, --port <number>', '服务端口号', '9000')
  .option('-m, --mode <string>', '运行模式 (stdio|http)', 'stdio')
  .option('-d, --debug', '启用调试模式', false)
  .option('-e, --env <path>', '环境变量文件路径', '.env')
  .option('--supabase-url <url>', 'Supabase项目URL')
  .option('--supabase-key <key>', 'Supabase项目API密钥')
  .option('--supabase-config <path>', 'Supabase配置文件路径(JSON格式)')
  .option('--allow-anonymous', '允许匿名SSE连接(仅HTTP模式)', false)
  .option('--session-timeout <minutes>', 'HTTP会话超时时间(分钟,默认30分钟)', '30')
  .option('--disable-session-cleanup', '禁用HTTP会话自动清理', false)
  .option('--cors-origin <origin>', '允许的CORS来源', '*');

program.parse(process.argv);
const options = program.opts();
logInfo(options);
// 加载环境变量
dotenv.config({ path: options.env });
dotenv.config({ path: '.env.local' });

// 如果提供了Supabase配置文件,加载配置
if (options.supabaseConfig) {
  try {
    const configPath = options.supabaseConfig;
    const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
    process.env.SUPABASE_URL = config.url;
    process.env.SUPABASE_KEY = config.key;
    logInfo(`已从配置文件加载Supabase配置: ${configPath}`);
  } catch (error) {
    logError(`加载Supabase配置文件失败: ${error.message}`);
    process.exit(1);
  }
}

// 命令行参数优先级高于配置文件和环境变量
if (options.supabaseUrl) {
  process.env.SUPABASE_URL = options.supabaseUrl;
}
if (options.supabaseKey) {
  process.env.SUPABASE_KEY = options.supabaseKey;
}

// 验证必需的环境变量
function validateEnvVariables() {
  const requiredVars = ['SUPABASE_KEY'];
  const missing = requiredVars.filter(varName => !process.env[varName]);

  if (missing.length > 0) {
    logError('缺少必需的环境变量: ' + missing.join(', '));
    logError('请通过以下方式提供Supabase配置:');
    logError('1. 环境变量文件 (.env)');
    logError('2. 命令行参数 (--supabase-key)');
    logError('3. 配置文件 (--supabase-config)');
    process.exit(1);
  }

  if (options.debug) {
    logInfo('Supabase配置验证通过');
    logInfo(`Supabase URL: ${process.env.SUPABASE_URL}`);
    logInfo(`Supabase Key: ${process.env.SUPABASE_KEY.substring(0, 8)}...`);
  }
}

// 在启动前验证环境变量
validateEnvVariables();

// 检测运行模式
const isStdioMode = options.mode === 'stdio';

if (options.debug) {
  logInfo('调试模式已启用');
  logInfo(`运行配置: ${JSON.stringify(options, null, 2)}`);
}

// 创建MCP服务器
const server = new McpServer({
  name: 'Inshow AI Query',
  version: '1.0.0',
});

// 添加全局错误处理
server.onError = (error, request) => {
  if (request) {
    logError(`处理请求 ${request.method} 时发生错误: ${error.message}`);
    logError(`请求详情: ${JSON.stringify(request, null, 2)}`);
  } else {
    logError(`MCP服务器错误: ${error.message}`);
  }
  logError(`错误调用栈: ${error.stack}`);
  // 返回带有详细信息的错误
  return {
    code: -32603, // 内部错误
    message: `MCP 错误: ${error.message}`,
    data: {
      stack: error.stack,
    },
  };
};

// 加载MCP调试规范
try {
  const currentDir = process.cwd();
  const rulesPath = path.join(currentDir, 'docs', 'mcp-rules.md');

  if (fs.existsSync(rulesPath)) {
    const rulesContent = fs.readFileSync(rulesPath, 'utf8');
    server.rule('mcp-rules', 'MCP应用的调试规范', rulesContent);
    logInfo('已加载MCP应用调试规范');
  } else {
    logWarn('未找到MCP应用调试规范文件: ' + rulesPath);
  }
} catch (error) {
  logError('加载MCP应用调试规范失败: ' + error.message);
}

// 初始化Supabase服务
const supabaseService = setupSupabaseService();

// 注册资源、工具和提示
logInfo('正在注册数据库资源...');
registerDatabaseResources(server, supabaseService);
logInfo('正在注册数据库查询工具...');
registerQueryTools(server, supabaseService);
logInfo('正在注册数据分析提示...');
registerDataAnalysisPrompts(server);

// 根据运行模式启动相应的服务
if (isStdioMode) {
  // 启动STDIO模式服务器
  startStdioServer(server, { debug: options.debug });
} else {
  // 配置HTTP服务器选项
  const httpOptions = {
    port: options.port,
    debug: options.debug,
    allowAnonymousSSE: options.allowAnonymous,
    disableSessionCleanup: options.disableSessionCleanup,
    sessionTimeout: parseInt(options.sessionTimeout) * 60 * 1000, // 转换为毫秒
    corsOrigin: options.corsOrigin,
  };

  // 输出HTTP服务器配置信息
  if (options.debug) {
    logInfo(`HTTP服务器配置: ${JSON.stringify(httpOptions, null, 2)}`);
    logInfo('正在启动Streamable HTTP服务器...');
  }

  // 启动HTTP服务器模式
  startHttpServer(server, supabaseService, httpOptions);
}//# sourceMappingURL=index.js.map