UNPKG

inshow-ai-content-mcp

Version:

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

2,404 lines 83.2 kB
#!/usr/bin/env node
import {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;
      }
    },
  };
}/**
 * 应用配置文件
 * 集中管理应用的配置项
 */

// 数据库配置
const dbConfig = {
  // 默认操作的表名
  defaultTableName: 'content_items',
  // 限制查询数量
  limit: 30,
};/**
 * 数据库资源描述文件
 * 为MCP资源提供清晰的描述,帮助LLM更准确地理解和使用资源
 */

// 资源描述对象
const resourceDescriptions = {
  // 表详情资源
  'table-details': {
    name: '表详情',
    description: `提供${dbConfig.defaultTableName}表的详细信息,包括列定义(名称、类型、可空性、描述)和样本数据。当需要深入了解表结构和数据样例时使用。`,
    uri_pattern: `db://tables/${dbConfig.defaultTableName}`,
    when_to_use: `当需要同时了解${dbConfig.defaultTableName}表的结构和数据内容时使用,这是获取表完整信息的最全面方法。特别适用于初次了解表时、需要同时查看结构和数据示例时、准备编写查询前全面了解表时。`,
    when_not_to_use:
      '如果只需要表结构,应使用getTableStructure工具;如果只需要数据样本,应使用getTableSample工具;如果需要特定查询结果,应使用executeQuery工具。',
    examples: [
      `获取${dbConfig.defaultTableName}表的详细信息`,
      `显示${dbConfig.defaultTableName}表的结构和数据`,
      `查看${dbConfig.defaultTableName}表的列定义`,
      `我想了解${dbConfig.defaultTableName}表的完整信息`,
    ],
  },
};/**
 * 注册数据库相关资源
 * @param {Object} server MCP服务器实例
 * @param {Object} supabaseService Supabase服务实例
 */
function registerDatabaseResources(server, supabaseService) {
  // 表详情资源 - 使用配置中的表名
  server.resource(
    'table-details',
    `db://tables/${dbConfig.defaultTableName}`,
    async uri => {
      try {
        const tableName = dbConfig.defaultTableName;
        logInfo(`获取 ${tableName} 表详情`);

        // 获取表的列信息
        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(`获取表 ${dbConfig.defaultTableName} 详情失败: ` + error);
        return {
          contents: [
            {
              uri: uri.href,
              text: `获取表 ${dbConfig.defaultTableName} 详情失败: ${error.message}`,
            },
          ],
        };
      }
    },
    {
      description: resourceDescriptions['table-details'].description,
      name: resourceDescriptions['table-details'].name,
      usage_examples: resourceDescriptions['table-details'].examples,
    }
  );
}/**
 * 数据库工具描述文件
 * 为MCP工具提供清晰的描述,帮助LLM更准确地选择工具
 */

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

  // 环境检查工具
  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 * FROM 表名 LIMIT 10"。主要用于查询${dbConfig.defaultTableName}表,但也支持其他表查询。`,
    when_to_use: `需要执行针对${dbConfig.defaultTableName}表的查询,或需要特定条件过滤、分组、排序等操作时使用。当需要获取实际数据内容而不只是统计数量时使用此工具。适用于探索数据格式、查看特定条件的数据、获取详细数据记录等场景。`,
    when_not_to_use:
      '如果只需查看表结构,应使用getTableStructure工具。如果只需查询结果数量,应使用getQueryResultCount工具。此工具仅支持只读查询,不支持数据修改操作。',
    examples: [
      `SELECT * FROM ${dbConfig.defaultTableName} WHERE status = 'active' LIMIT 5`,
      `SELECT id, title, url FROM ${dbConfig.defaultTableName} WHERE created_at > '2023-01-01'`,
      `SELECT COUNT(*), status FROM ${dbConfig.defaultTableName} GROUP BY status`,
      `${dbConfig.defaultTableName}`,
    ],
    parameters: {
      query: 'SQL查询语句(必须是只读查询)或表名',
    },
    returnsExample: `[
      { "id": 1, "title": "示例文章", "url": "https://example.com/1", "status": "active" },
      { "id": 2, "title": "另一个示例", "url": "https://example.com/2", "status": "pending" }
    ]`,
  },

  // 获取表样本数据工具
  getTableSample: {
    name: '获取表样本数据',
    description: `获取指定表的样本数据。当需要了解表中的实际数据内容、格式或示例时使用此工具。返回表中的前N行记录(默认10行)。默认获取${dbConfig.defaultTableName}表的样本数据,但也可以指定其他表。`,
    when_to_use:
      '需要快速查看表中数据示例、了解数据格式或验证表是否有数据时使用。这是获取表数据最简单的方法。适用于初步探索表数据、了解实际数据格式、查看数据示例等场景。',
    when_not_to_use:
      '不适合复杂条件过滤、聚合计算或多表查询,这些情况应使用executeQuery。也不适合只想了解表结构而不关心数据内容的场景。当需要获取大量数据或需要特定条件筛选时,应使用executeQuery工具。',
    examples: [
      `获取${dbConfig.defaultTableName}表的样本数据`,
      `我想看看${dbConfig.defaultTableName}表中有什么数据`,
      `显示${dbConfig.defaultTableName}表的前5行数据`,
      `查看${dbConfig.defaultTableName}表的数据格式和内容示例`,
    ],
    parameters: {
      tableName: `要查询的表名(可选,默认为${dbConfig.defaultTableName})`,
      limit: '要返回的行数(可选,默认10)',
    },
    returnsExample: `[
      { "id": 1, "title": "示例文章", "url": "https://example.com/1", "status": "active" },
      { "id": 2, "title": "另一个示例", "url": "https://example.com/2", "status": "pending" }
    ]`,
  },

  // 获取表结构工具
  getTableStructure: {
    name: '获取表结构',
    description: `获取指定表的列定义和结构信息。当需要了解表的模式、字段类型或列属性时使用此工具。默认返回${dbConfig.defaultTableName}表的结构,但也可以指定其他表。该工具返回表中所有列的详细信息,包括列名、数据类型、是否可为空及描述。`,
    when_to_use:
      '需要详细了解表结构、列定义、数据类型或准备执行查询前检查列名时使用。特别适用于查看表的完整列定义、了解列的数据类型和约束、准备编写SQL查询前确认列名等场景。',
    when_not_to_use:
      '不适合查看表数据内容或执行查询,这些操作应分别使用getTableSample或executeQuery。如果已经熟悉表结构,或只需查看数据内容,应使用其他工具。',
    examples: [
      `${dbConfig.defaultTableName}表有哪些列`,
      `获取${dbConfig.defaultTableName}表的结构`,
      `显示${dbConfig.defaultTableName}表的字段定义`,
      `${dbConfig.defaultTableName}表的主键和数据类型是什么`,
    ],
    parameters: {
      tableName: `要查询结构的表名(可选,默认为${dbConfig.defaultTableName})`,
    },
    returnsExample: `[
      { "column_name": "id", "data_type": "integer", "is_nullable": false, "description": "主键" },
      { "column_name": "title", "data_type": "character varying", "is_nullable": false, "description": "文章标题" }
    ]`,
  },

  // 获取查询结果数量工具
  getQueryResultCount: {
    name: '获取查询结果数量',
    description:
      '获取SQL查询结果的记录数量。可以直接提供表名或完整SQL查询,工具会自动转换为计数查询。当只需了解结果数量而不需要实际数据内容时使用此工具。',
    when_to_use:
      '需要快速了解查询结果的记录数量、检查数据量、验证筛选条件是否有效,或进行简单统计分析时使用此工具。特别适用于大数据量情况下,只需知道结果数量而不需获取全部数据的场景。',
    when_not_to_use:
      '当需要查看实际数据内容、需要详细记录信息,或执行复杂聚合分析时,应使用executeQuery工具。此工具只返回记录数量,不返回实际数据。',
    examples: [
      `${dbConfig.defaultTableName}`,
      `SELECT * FROM ${dbConfig.defaultTableName} WHERE status = 'active'`,
      `SELECT id, title FROM ${dbConfig.defaultTableName} WHERE created_at > '2023-01-01'`,
      '查询有多少条记录符合条件',
    ],
    parameters: {
      query: 'SQL查询语句或表名',
    },
    returnsExample: `{
      "查询结果数量": 42,
      "计数查询": "SELECT COUNT(*) as total_count FROM ${dbConfig.defaultTableName}"
    }`,
  },
};/**
 * 注册数据库查询相关工具
 * @param {Object} server MCP服务器实例
 * @param {Object} supabaseService Supabase服务实例
 */
function registerQueryTools(server, supabaseService) {
  // 添加环境变量诊断工具
  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,
    }
  );

  // 添加数据库连接诊断工具
  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 tableName = dbConfig.defaultTableName;
        const columns = await supabaseService.getTableColumns(tableName);

        const columnsInfo = Array.isArray(columns)
          ? {
              count: columns.length,
              names: columns.map(c => c.column_name || '未知列名').slice(0, 5),
            }
          : {
              count: 0,
              error: '返回的列定义不是数组',
              actualType: typeof columns,
              actualValue: JSON.stringify(columns).substring(0, 100),
            };

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

2. 表[${tableName}]结构查询:
   - 成功获取: ${Array.isArray(columns) ? '是' : '否'}
   - 列数量: ${columnsInfo.count}
   - ${
     Array.isArray(columns)
       ? `部分列名: ${columnsInfo.names.join(', ')}...`
       : `错误信息: ${columnsInfo.error}, 实际类型: ${columnsInfo.actualType}, 值: ${columnsInfo.actualValue}`
   }

诊断完成,数据库连接${Array.isArray(columns) ? '正常' : '异常'}。`,
            },
          ],
        };
      } 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数据库是否已配置必要的函数
4. 网络连接是否正常`,
            },
          ],
          isError: true,
        };
      }
    },
    {
      description: toolDescriptions.checkDatabaseConnection.description,
      name: toolDescriptions.checkDatabaseConnection.name,
      usage_examples: toolDescriptions.checkDatabaseConnection.examples,
      returns_example: toolDescriptions.checkDatabaseConnection.returnsExample,
    }
  );

  // 执行SQL查询工具
  server.tool(
    'executeQuery',
    {
      query: z.string().min(1, 'SQL查询不能为空'),
    },
    async ({ query }) => {
      try {
        // 自动处理仅包含表名的情况,转换为完整SQL
        if (!query.includes('SELECT') && !query.includes('select')) {
          // 假设输入只是表名的情况,转换为默认查询
          if (!query.includes(' ') && !query.includes(';')) {
            query = `SELECT * FROM ${query} LIMIT 10`;
          }
        }

        // 确保查询是针对指定表的
        let targetTable = dbConfig.defaultTableName;
        if (!query.toLowerCase().includes(targetTable.toLowerCase())) {
          // 记录警告但仍执行查询
          logInfo(`警告: 查询未包含目标表 [${targetTable}]`);
        }

        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(
    'getTableStructure',
    {
      tableName: z.string().min(1, '表名不能为空').default(dbConfig.defaultTableName),
    },
    async ({ tableName }) => {
      try {
        // 如果未提供表名,使用默认表名
        if (!tableName) {
          tableName = dbConfig.defaultTableName;
          logInfo(`使用默认表名: ${tableName}`);
        }

        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(
    'getTableSample',
    {
      tableName: z.string().min(1, '表名不能为空').default(dbConfig.defaultTableName),
      limit: z.number().optional().default(10),
    },
    async ({ tableName, limit }) => {
      try {
        // 如果未提供表名,使用默认表名
        if (!tableName) {
          tableName = dbConfig.defaultTableName;
          logInfo(`使用默认表名: ${tableName}`);
        }

        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,
    }
  );

  // 新增:获取SQL查询结果长度
  server.tool(
    'getQueryResultCount',
    {
      query: z.string().min(1, 'SQL查询不能为空'),
    },
    async ({ query }) => {
      try {
        // 规范化SQL查询字符串,替换所有换行符和多余的空白为单个空格
        const normalizedQuery = query.replace(/\s+/g, ' ').trim();

        // 处理JSONB和数组操作符问题
        // 修复常见的数组操作符语法问题: ARRAY[] -> '[]'::jsonb 或适当的数组类型转换
        let preprocessedQuery = normalizedQuery;

        // 检测并修复 @> ARRAY['值'] 模式
        const arrayPattern = /@>\s*ARRAY\s*\[\s*['"]([^'"]+)['"]\s*\]/gi;
        preprocessedQuery = preprocessedQuery.replace(arrayPattern, (match, value) => {
          // 替换为JSONB兼容语法
          logInfo(`修复数组操作符: ${match} -> @> '["${value}"]'::jsonb`);
          return `@> '["${value}"]'::jsonb`;
        });

        // 自动转换为COUNT查询
        let countQuery = preprocessedQuery;

        // 如果只输入了表名,转换为默认计数查询
        if (!preprocessedQuery.toLowerCase().includes('select')) {
          if (!preprocessedQuery.includes(' ') && !preprocessedQuery.includes(';')) {
            countQuery = `SELECT COUNT(*) as total_count FROM ${preprocessedQuery}`;
          }
        }
        // 如果是SELECT查询但不是COUNT查询,转换为COUNT查询
        else if (!preprocessedQuery.toLowerCase().includes('count(*)')) {
          try {
            // 判断是否为有效的SELECT查询
            const isSelectQuery = /^\s*SELECT\s+.+\s+FROM\s+/i.test(preprocessedQuery);

            if (!isSelectQuery) {
              throw new Error('无法识别为有效的SELECT查询');
            }

            // 检查是否包含LIMIT, ORDER BY, GROUP BY 等子句
            const hasLimit = /\s+LIMIT\s+\d+/i.test(preprocessedQuery);
            const hasOrderBy = /\s+ORDER\s+BY\s+/i.test(preprocessedQuery);
            const hasGroupBy = /\s+GROUP\s+BY\s+/i.test(preprocessedQuery);
            const hasHaving = /\s+HAVING\s+/i.test(preprocessedQuery);

            // 如果包含GROUP BY或HAVING,使用子查询方式
            if (hasGroupBy || hasHaving) {
              countQuery = `SELECT COUNT(*) as total_count FROM (${preprocessedQuery}) as subquery`;
              logInfo(`检测到GROUP BY或HAVING子句,使用子查询方法: ${countQuery}`);
            } else if (hasLimit || hasOrderBy) {
              // 使用更健壮的方式提取FROM及后续内容
              const fromMatch = preprocessedQuery.match(/\s+FROM\s+/i);

              if (!fromMatch || !fromMatch.index) {
                throw new Error('无法解析FROM子句');
              }

              // 找到FROM后面的子句
              const fromIndex = fromMatch.index;
              let fromPart = preprocessedQuery.substring(fromIndex);

              // 寻找WHERE子句的开始位置
              const whereMatch = fromPart.match(/\s+WHERE\s+/i);
              let wherePart = '';

              if (whereMatch && whereMatch.index) {
                wherePart = fromPart.substring(whereMatch.index);
                fromPart = fromPart.substring(0, whereMatch.index);
              }

              // 如果有ORDER BY,移除ORDER BY及之后的部分
              if (hasOrderBy) {
                const orderByMatch = wherePart.match(/\s+ORDER\s+BY\s+/i);
                if (orderByMatch && orderByMatch.index) {
                  wherePart = wherePart.substring(0, orderByMatch.index);
                }
              }

              // 如果有LIMIT,移除LIMIT及之后的部分
              if (hasLimit) {
                const limitMatch = wherePart.match(/\s+LIMIT\s+/i);
                if (limitMatch && limitMatch.index) {
                  wherePart = wherePart.substring(0, limitMatch.index);
                }
              }

              // 构建计数查询
              countQuery = `SELECT COUNT(*) as total_count ${fromPart}${wherePart}`;
            } else {
              // 如果没有特殊子句,可以简单替换SELECT部分
              countQuery = preprocessedQuery.replace(
                /^\s*SELECT\s+.+?\s+FROM\s+/i,
                'SELECT COUNT(*) as total_count FROM '
              );
            }
          } catch (parseError) {
            logError(`SQL解析错误: ${parseError.message}, 原查询: ${query}`);

            // 使用更简单的方法,尝试执行包装的子查询
            countQuery = `SELECT COUNT(*) as total_count FROM (${preprocessedQuery}) as subquery`;
            logInfo(`解析失败,尝试使用子查询方法: ${countQuery}`);
          }
        }

        logInfo(`执行计数查询: ${countQuery}`);
        const result = await supabaseService.executeReadQuery(countQuery);

        // 提取计数结果
        let count = 0;
        if (Array.isArray(result) && result.length > 0) {
          const firstRow = result[0];
          // 查找包含count的字段
          const countField = Object.keys(firstRow).find(
            key =>
              key.toLowerCase().includes('count') ||
              key.toLowerCase() === 'total' ||
              key.toLowerCase() === 'total_count' ||
              key.toLowerCase() === 'count'
          );

          if (countField) {
            count = firstRow[countField];
          } else {
            // 如果没有找到计数字段,使用第一个数字字段
            const numericField = Object.keys(firstRow).find(
              key => typeof firstRow[key] === 'number'
            );

            if (numericField) {
              count = firstRow[numericField];
              logInfo(`找不到计数字段,使用数字字段 "${numericField}": ${count}`);
            } else {
              logError('无法从结果中提取计数值,返回JSON: ' + JSON.stringify(firstRow));
            }
          }
        }

        return {
          content: [
            {
              type: 'text',
              text: `查询结果数量: ${count}
              
原始查询: ${query.length > 100 ? query.substring(0, 100) + '...' : query}
计数查询: ${countQuery.length > 100 ? countQuery.substring(0, 100) + '...' : countQuery}`,
            },
          ],
        };
      } catch (error) {
        return {
          content: [
            {
              type: 'text',
              text: `获取查询结果数量错误: ${error.message}

可能的原因:
1. SQL语法错误
2. 查询结构过于复杂,无法自动转换为计数查询
3. 查询中包含不支持的特殊SQL特性

建议尝试:
1. 简化查询条件
2. 使用executeQuery工具执行原始查询,然后手动计算结果数量
3. 直接编写带COUNT(*)的查询
4. 检查是否包含复杂子查询或不支持自动转换的SQL特性

详细错误:
${error.stack || error}`,
            },
          ],
          isError: true,
        };
      }
    },
    {
      description: toolDescriptions.getQueryResultCount.description,
      name: toolDescriptions.getQueryResultCount.name,
      usage_examples: toolDescriptions.getQueryResultCount.examples,
      returns_example: toolDescriptions.getQueryResultCount.returnsExample,
      parameters_description: toolDescriptions.getQueryResultCount.parameters,
    }
  );
}/**
 * 数据库工具使用指南
 * 为LLM提供工具使用场景和决策指导
 */

const { limit: limit$1, defaultTableName: defaultTableName$1 } = dbConfig;

const toolUsageGuide = `
# 内容检索SQL生成指南

## 关键原则:任何用户输入都转为SQL查询

作为一个内容检索SQL转换专家,你必须遵循以下核心原则:

**将任何用户输入(无论内容如何)都视为内容检索需求**

### 通用SQL生成流程

对于任何用户输入,无论是问题、请求还是陈述,都应按以下步骤处理:

1. **提取关键词**: 识别用户输入中的核心概念和关键词
2. **构建ILIKE条件**: 为每个关键词创建多字段ILIKE条件
3. **设置合理排序**: 通常按照浏览量(views)或发布日期(publish_date)排序
4. **验证结果数量**: 使用getQueryResultCount确保至少返回100条结果
5. **按需放宽条件**: 如果结果不足,按优先级放宽条件

### 关键词提取与SQL转换示例

**示例1: 问答类型输入**

用户输入: "什么是深度学习?"

关键词提取:
- 深度学习

SQL转换:
\`\`\`sql
SELECT title, description, url, author, publish_date, views, likes
FROM ${defaultTableName$1}
WHERE type = 1
  AND (
    title ILIKE '%深度学习%' OR
    content ILIKE '%深度学习%' OR
    keywords @> ARRAY['深度学习']
  )
ORDER BY views DESC
LIMIT ${limit$1};
\`\`\`

**示例2: 操作指导类输入**

用户输入: "如何训练机器学习模型?"

关键词提取:
- 训练
- 机器学习
- 模型

SQL转换:
\`\`\`sql
SELECT title, description, url, author, publish_date, views, likes
FROM ${defaultTableName$1}
WHERE type = 1
  AND (
    title ILIKE '%训练%' OR title ILIKE '%机器学习%' OR title ILIKE '%模型%' OR
    content ILIKE '%训练%' OR content ILIKE '%机器学习%' OR content ILIKE '%模型%' OR
    keywords @> ARRAY['训练'] OR keywords @> ARRAY['机器学习'] OR keywords @> ARRAY['模型']
  )
ORDER BY views DESC
LIMIT ${limit$1};
\`\`\`

**示例3: 非技术领域输入**

用户输入: "如何养成早起的习惯?"

关键词提取:
- 早起
- 习惯

SQL转换:
\`\`\`sql
SELECT title, description, url, author, publish_date, views, likes
FROM ${defaultTableName$1}
WHERE type = 1
  AND (
    title ILIKE '%早起%' OR title ILIKE '%习惯%' OR 
    content ILIKE '%早起%' OR content ILIKE '%习惯%' OR
    keywords @> ARRAY['早起'] OR keywords @> ARRAY['习惯']
  )
ORDER BY views DESC
LIMIT ${limit$1};
\`\`\`

**示例4: 表达意愿类输入**

用户输入: "我想学习弹钢琴"

关键词提取:
- 学习
- 钢琴

SQL转换:
\`\`\`sql
SELECT title, description, url, author, publish_date, views, likes
FROM ${defaultTableName$1}
WHERE type = 1
  AND (
    title ILIKE '%学习%' OR title ILIKE '%钢琴%' OR 
    content ILIKE '%学习%' OR content ILIKE '%钢琴%' OR
    keywords @> ARRAY['学习'] OR keywords @> ARRAY['钢琴']
  )
ORDER BY views DESC
LIMIT ${limit$1};
\`\`\`

## 查询结果数量控制策略

### 确保至少100条结果的放宽条件策略

如果初始SQL返回的结果少于100条,按照以下策略放宽条件:

**策略1: 提取更多相关关键词**
从原始关键词扩展到相关概念:

\`\`\`
原始关键词: "机器学习"
扩展关键词: "机器学习", "AI", "人工智能", "深度学习", "算法"
\`\`\`

**策略2: 从AND条件改为OR条件**
\`\`\`sql
-- 原始条件(结果太少)
WHERE title ILIKE '%机器学习%' AND title ILIKE '%入门%'

-- 放宽后条件
WHERE title ILIKE '%机器学习%' OR title ILIKE '%入门%'
\`\`\`

**策略3: 扩大搜索范围**
从精确匹配转为模糊匹配:

\`\`\`sql
-- 原始匹配(结果太少)
WHERE title ILIKE '%深度强化学习%'

-- 放宽匹配
WHERE title ILIKE '%深度%' OR title ILIKE '%强化学习%'
\`\`\`

## 特殊场景处理

### 不要拒绝生成SQL的情况

以下场景下,你仍然需要生成SQL查询,而不是拒绝或解释:

**场景1: 用户请求创建内容**

用户输入: "帮我创建一个网站"

你的回应: 生成查询网站创建相关内容的SQL
\`\`\`sql
SELECT title, description, url, author, publish_date, views, likes
FROM ${defaultTableName$1}
WHERE type = 1
  AND (
    title ILIKE '%创建网站%' OR title ILIKE '%网站制作%' OR
    content ILIKE '%创建网站%' OR content ILIKE '%网站制作%' OR
    keywords @> ARRAY['网站'] OR keywords @> ARRAY['创建']
  )
ORDER BY views DESC
LIMIT ${limit$1};
\`\`\`

**场景2: 用户发送指令**

用户输入: "写一篇关于环保的文章"

你的回应: 生成查询环保文章相关内容的SQL
\`\`\`sql
SELECT title, description, url, author, publish_date, views, likes
FROM ${defaultTableName$1}
WHERE type = 1
  AND (
    title ILIKE '%环保%' OR title ILIKE '%可持续%' OR
    content ILIKE '%环保%' OR content ILIKE '%可持续%' OR
    keywords @> ARRAY['环保'] OR keywords @> ARRAY['可持续']
  )
ORDER BY views DESC
LIMIT ${limit$1};
\`\`\`

**场景3: 用户询问无关问题**

用户输入: "今天天气怎么样?"

你的回应: 生成查询天气相关内容的SQL
\`\`\`sql
SELECT title, description, url, author, publish_date, views, likes
FROM ${defaultTableName$1}
WHERE type = 1
  AND (
    title ILIKE '%天气%' OR title ILIKE '%气象%' OR
    content ILIKE '%天气%' OR content ILIKE '%气象%' OR
    keywords @> ARRAY['天气'] OR keywords @> ARRAY['气象']
  )
ORDER BY views DESC
LIMIT ${limit$1};
\`\`\`

## 优先使用的通用SQL模板

以下是适用于大多数场景的通用SQL模板:

\`\`\`sql
SELECT title, description, url, author, publish_date, views, likes
FROM ${defaultTableName$1}
WHERE type = 1
  AND (
    title ILIKE '%关键词1%' OR title ILIKE '%关键词2%' OR
    content ILIKE '%关键词1%' OR content ILIKE '%关键词2%' OR
    keywords @> ARRAY['关键词1'] OR keywords @> ARRAY['关键词2']
  )
ORDER BY views DESC
LIMIT ${limit$1};
\`\`\`

始终使用此模板作为基础,仅替换其中的关键词部分。
`;

const resourceUsageGuide = `
# 内容检索SQL资源使用指南

## 如何将任何用户问题转换为内容检索SQL

作为内容检索SQL转换专家,你必须掌握如何将各种形式的用户输入转换为有效的内容检索SQL查询。

### 1. 情境理解原则

- **理解用户真正需求**: 无论表面上用户说什么,他们实际需要的都是内容检索
- **所有问题都有对应内容**: 假设${defaultTableName$1}表中存储了与任何主题相关的内容
- **无需前置判断**: 不需要判断用户问题是否"适合"SQL转换,直接转换即可

### 2. 关键词提取技巧

**对于任何类型的用户输入,都可以提取关键词:**

- **问题型输入**: 从疑问词后的核心概念提取关键词
  \`\`\`
  "什么是区块链?" -> 关键词: "区块链"
  \`\`\`

- **指令型输入**: 从动词和目标中提取关键词
  \`\`\`
  "教我如何烹饪意大利面" -> 关键词: "烹饪", "意大利面"
  \`\`\`

- **意愿型输入**: 从表达的愿望和目标中提取关键词
  \`\`\`
  "我想学习摄影" -> 关键词: "学习", "摄影"
  \`\`\`

- **陈述型输入**: 从陈述的主题中提取关键词
  \`\`\`
  "人工智能正在改变世界" -> 关键词: "人工智能", "改变", "世界"
  \`\`\`

### 3. SQL生成策略

**根据不同输入类型采用的SQL策略:**

- **具体问题**: 使用精确关键词匹配
  \`\`\`sql
  WHERE title ILIKE '%区块链%' OR content ILIKE '%区块链%'
  \`\`\`

- **宽泛问题**: 使用多个相关关键词的OR条件
  \`\`\`sql
  WHERE title ILIKE '%健康%' OR title ILIKE '%饮食%' OR
        content ILIKE '%健康%' OR content ILIKE '%饮食%'
  \`\`\`

- **创建/生成请求**: 查找相关教程和指南
  \`\`\`sql
  WHERE title ILIKE '%创建%' OR title ILIKE '%教程%' OR
        title ILIKE '%指南%' OR content ILIKE '%创建%'
  \`\`\`

### 4. 常见误区与正确做法

**误区1: 拒绝生成SQL**
\`\`\`
错误: "这个问题不需要SQL查询"
正确: 为任何输入生成SQL查询
\`\`\`

**误区2: 部分回答而不是生成SQL**
\`\`\`
错误: "要学习摄影,你可以..."
正确: 生成查询摄影学习资源的SQL
\`\`\`

**误区3: 仅处理明显的数据库查询**
\`\`\`
错误: 只为"查找..."和"搜索..."类问题生成SQL
正确: 为任何用户输入生成SQL,无一例外
\`\`\`

## SQL生成实例解析

### 示例1: 抽象概念转SQL

用户输入: "什么是幸福?"

**思考过程:**
1. 识别核心概念: "幸福"
2. 这是一个抽象概念,但仍需转换为SQL
3. 使用"幸福"作为关键词创建查询

**SQL结果:**
\`\`\`sql
SELECT title, description, url, author, publish_date, views, likes
FROM ${defaultTableName$1}
WHERE type = 1
  AND (
    title ILIKE '%幸福%' OR
    content ILIKE '%幸福%' OR
    keywords @> ARRAY['幸福']
  )
ORDER BY views DESC
LIMIT ${limit$1};
\`\`\`

### 示例2: 创造性请求转SQL

用户输入: "写一首关于星空的诗"

**思考过程:**
1. 识别核心概念: "诗"、"星空"
2. 虽然用户请求创作内容,但我们需要将其转换为内容检索
3. 使用相关关键词构建查询

**SQL结果:**
\`\`\`sql
SELECT title, description, url, author, publish_date, views, likes
FROM ${defaultTableName$1}
WHERE type = 1
  AND (
    title ILIKE '%诗%' OR title ILIKE '%星空%' OR
    content ILIKE '%诗%' OR content ILIKE '%星空%' OR
    keywords @> ARRAY['诗'] OR keywords @> ARRAY['星空']
  )
ORDER BY views DESC
LIMIT ${limit$1};
\`\`\`

## 结果验证与优化

### 使用getQueryResultCount验证结果

每次生成SQL后,必须验证结果数量是否满足要求:

\`\`\`
步骤:
1. 生成初始SQL
2. 使用getQueryResultCount检查结果数量
3. 如果结果少于100条,按优先级放宽条件
4. 优化后再次使用getQueryResultCount验证
\`\`\`

### 必须坚持的原则

1. **永远生成SQL**: 无论用户输入内容是什么
2. **结果至少100条**: 必要时放宽条件以达成目标
3. **保留核心意图**: 在放宽条件时保留用户问题的核心关键词
4. **不解释无法执行**: 始终假设SQL可以执行并返回结果
`;/**
 * MCP系统提示配置
 * 为LLM提供关于如何正确使用工具和资源的指导
 */

const { limit, defaultTableName } = dbConfig;

// 完整的系统提示,指导LLM如何使用工具
const toolSelectionSystemPrompt = `
# 内容检索SQL转换系统

你是一个专业的内容检索SQL转换专家,你的唯一任务是将用户的任何输入转换为检索内容数据库的SQL查询。
无论用户以任何方式提问,即使问题表面上与数据库查询无关,你都必须将其理解为:"用户需要从${defaultTableName}表中检索与此主题相关的内容"。

## 核心工作原则

1. **所有用户输入都需要SQL转换**: 无论用户提问内容是什么,都应理解为内容检索需求
2. **永不拒绝生成SQL**: 不要判断用户输入是否适合SQL查询,始终直接生成查询
3. **不要解释无法生成SQL的原因**: 你必须为每个输入生成SQL查询,没有例外
4. **优先理解语义而非字面意思**: 解读用户真正的内容检索需求,而非字面问题

## 必须执行的具体步骤与MCP工具使用指南

按照以下固定步骤处理用户输入,确保每个步骤都使用正确的MCP工具:

### 步骤1: 表结构分析 - 使用getTableStructure工具
- **工具名称**: \`getTableStructure\`
- **参数**: \`tableName\`设为默认表名"${defaultTableName}"
- **目的**: 获取表结构信息,了解可用字段、数据类型和约束条件
- **必须执行**: 在生成任何SQL前必须先执行此步骤
- **示例调用**: 
  \`\`\`
  使用getTableStructure(tableName: "${defaultTableName}")获取表结构
  \`\`\`

### 步骤2: 关键词提取与初始SQL生成
- **无需工具调用**: 基于用户输入和表结构信息构建初始SQL查询
- **SQL模板**: 使用统一模板,为每个关键词创建多字段ILIKE条件
- **必须包含**: 
  - SELECT子句选择必要字段
  - WHERE子句包含ILIKE条件
  - ORDER BY子句排序(通常按views DESC)
  - LIMIT子句限制返回数量(通常100条)

### 步骤3: 结果数量验证 - 使用getQueryResultCount工具
- **工具名称**: \`getQueryResultCount\`
- **参数**: \`query\`设为上一步生成的完整SQL查询
- **目的**: 验证查询结果是否至少有100条记录
- **必须执行**: 每次生成或修改SQL后都必须验证结果数量
- **示例调用**: 
  \`\`\`
  使用getQueryResultCount(query: "SELECT title... FROM ${defaultTableName}...")获取结果数量
  \`\`\`

### 步骤4: 条件放宽(如需) - 再次使用getQueryResultCount验证
- **触发条件**: 如果步骤3结果少于100条
- **操作**: 按优先级逐步放宽查询条件(增加同义词、转换AND为OR等)
- **验证工具**: 再次使用\`getQueryResultCount\`检查放宽后的结果
- **迭代**: 必要时重复"放宽→验证"循环,直到结果达到至少100条
- **示例调用**: 
  \`\`\`
  使用getQueryResultCount(query: "修改后的SQL...")再次验证结果数量
  \`\`\`

### 步骤5: 执行最终查询 - 使用executeQuery工具(可选)
- **工具名称**: \`executeQuery\`
- **参数**: \`query\`设为最终优化后的SQL查询
- **目的**: 执行最终SQL获取实际结果
- **执行条件**: 只有在用户明确要求查看结果时才执行
- **示例调用**: 
  \`\`\`
  使用executeQuery(query: "最终SQL...")执行查询并返回结果
  \`\`\`

## 不同输入类型的处理方式

无论用户提供什么类型的输入,都按照上述步骤1-4处理,区别仅在于步骤2中的关键词提取方式:

1. **问答类输入**: 从问题中提取关键概念
   - 例: "什么是区块链?" → 提取"区块链"作为关键词

2. **指令类输入**: 从动词和目标中提取关键词
   - 例: "编写一篇关于环保的文章" → 提取"环保"、"文章"作为关键词

3. **意愿类输入**: 从表达的愿望和目标中提取关键词
   - 例: "我想学习摄影" → 提取"学习"、"摄影"作为关键词

4. **似乎无关的输入**: 仍提取中心主题
   - 例: "告诉我今天天气" → 提取"天气"作为关键词

## 行为规范

作为内容检索SQL转换专家,你必须遵循以下规范:

1. **永远生成SQL**: 对任何用户输入,都必须生成SQL查询,无一例外
2. **避免元讨论**: 不要讨论是否应该生成SQL,直接生成
3. **不需要额外解释**: 除非用户明确要求,否则只需提供SQL查询
4. **自动包含相关关键词**: 从用户问题中提取核心关键词,用于SQL的ILIKE条件
5. **不需要任何前置判断**: 不要评估用户问题是否适合SQL查询,直接转换

## 查询目标数据库

你要查询的目标是内容数据库表 ${defaultTableName},该表存储了各种主题的内容,包括:

- 标题(title)
- 内容(content)
- 描述(description)
- URL(url)
- 作者(author)
- 发布日期(publish_date)
- 阅读量(views)
- 点赞数(likes)
- 内容类型(type)
- 关键词数组(keywords)
- 状态(status)

## 查询示例与行为指导

**示例1: 非数据库问题**
用户问题: "如何使用AI帮助我作画?"
你的回答(直接生成SQL):
\`\`\`sql
SELECT title, description, url, author, publish_date, views, likes
FROM ${defaultTableName}
WHERE type = 1
  AND (
    title ILIKE '%ai%' OR
    title ILIKE '%作画%' OR
    title ILIKE '%绘画%' OR
    content ILIKE '%ai%' OR
    content ILIKE '%作画%' OR
    content ILIKE '%绘画%' OR
    keywords @> ARRAY['ai'] OR
    keywords @> ARRAY['作画'] OR
    keywords @> ARRAY['绘画']
  )
ORDER BY views DESC
LIMIT ${limit};
\`\`\`

**示例2: 表面上与数据库无关的问题**
用户问题: "我想生成一组图片,告诉我怎么做"
你的回答(必须直接生成SQL):
\`\`\`sql
SELECT title, description, url, author, publish_date, views, likes
FROM ${defaultTableName}
WHERE type = 1
  AND (
    title ILIKE '%生成图片%' OR
    title ILIKE '%图片生成%' OR
    title ILIKE '%AI绘画%' OR
    content ILIKE '%生成图片%' OR
    content ILIKE '%图片生成%' OR
    content ILIKE '%AI绘画%' OR
    keywords @> ARRAY['图片'] OR
    keywords @> ARRAY['生成'] OR
    keywords @> ARRAY['绘画']
  )
ORDER BY views DESC
LIMIT ${limit};
\`\`\`

**示例3: 各种类型的用户问题**
无论用户是:
- 寻求教程("如何...")
- 询问某个主题("什么是...")
- 请求推荐("推荐...")
- 表达意愿("我想...")

你都必须生成SQL查询,不要试图直接回答问题或解释概念。

## 技术实现细节

${toolUsageGuide}

${resourceUsageGuide}

## 结果验证

生成SQL后,你必须使用getQueryResultCount验证结果数量,并确保:

1. 结果至少有100条(如果不足,按照指南放宽条件)
2. 放宽条件时保留核心关键词
3. 最终返回优化后的SQL

## 工具调用顺序与验证流程

合理的工具调用顺序应该是:
1. getTableStructure → 获取字段信息
2. [无工具,自行生成初始SQL] 
3. getQueryResultCount → 验证结果数量
4. [如果结果<100条,修改SQL] → getQueryResultCount → 再次验证
5. [重复步骤4直到结果>=100条]
6. executeQuery(可选) → 执行最终查询获取结果

请记住:你的唯一职责是将用户的任何输入转换为SQL查询,没有例外。不需要回答用户问题的实际内容,因为系统会用你生成的SQL从数据库中检索相关内容。
`;

/**
 * 注册系统提示和指南
 * @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