I wrote an mcp server myself, all functions are available, but it shows a red dot instead of a green dot in the settings

I wrote an mcp server myself, all functions are available, but it shows a red dot instead of a green dot in the settings.
It just shows a red dot, and all tool interfaces are tested without any problems. Please tell me where the problem is, and the code is attached below.

const mysql = require('mysql2/promise');
const fs = require('fs');
const path = require('path');

// In-memory log storage
const operationLogs = [];
const MAX_LOGS = 1000;

// Get log directory and filename
const getLogConfig = () => {
  const logDir = process.env.MCP_LOG_DIR || './logs';
  const logFile = process.env.MCP_LOG_FILE || 'mcp-mysql.log';
  return {
    dir: logDir,
    file: logFile,
    fullPath: path.join(logDir, logFile)
  };
};

// Ensure log directory exists
const ensureLogDir = () => {
  const { dir } = getLogConfig();
  if (!fs.existsSync(dir)) {
    fs.mkdirSync(dir, { recursive: true });
  }
};

// Log recording function - record all requests and responses
const logRequest = (method, params, result, error = null) => {
  const logEntry = {
    id: Date.now(),
    method,
    params: JSON.stringify(params),
    result: result ? JSON.stringify(result) : null,
    error: error ? error.toString() : null,
    created_at: new Date().toISOString()
  };

  operationLogs.unshift(logEntry);
  if (operationLogs.length > MAX_LOGS) {
    operationLogs.splice(MAX_LOGS);
  }

  // Record request and response data
  const logLine = `${logEntry.created_at} | ${method} | ${logEntry.params} | ${error || 'SUCCESS'} | RESPONSE: ${logEntry.result || 'null'}\n`;

  try {
    ensureLogDir();
    const { fullPath } = getLogConfig();
    fs.appendFileSync(fullPath, logLine, 'utf8');
  } catch (err) {
    console.error('Failed to write log file:', err.message);
  }
};

// SQL operation log recording function
const logSqlOperation = (sql, result, error = null) => {
  const logEntry = {
    id: Date.now(),
    sql,
    result: result ? JSON.stringify(result) : null,
    error: error ? error.toString() : null,
    created_at: new Date().toISOString()
  };

  const logLine = `${logEntry.created_at} | SQL: ${sql} | ${error || 'SUCCESS'}\n`;

  try {
    ensureLogDir();
    const { fullPath } = getLogConfig();
    fs.appendFileSync(fullPath, logLine, 'utf8');
  } catch (err) {
    console.error('Failed to write log file:', err.message);
  }
};

// MySQL 连接配置
const getDbConfig = () => ({
  host: process.env.MYSQL_HOST || 'localhost',
  port: process.env.MYSQL_PORT ? parseInt(process.env.MYSQL_PORT) : 3306,
  user: process.env.MYSQL_USER || 'root',
  password: process.env.MYSQL_PASSWORD || '',
  database: process.env.MYSQL_DATABASE || '',
});

const ALLOW_DDL = process.env.ALLOW_DDL === 'true';

// Tool handlers
const handlers = {
  // Execute SQL query
  sql_query: async (params) => {
    const { sql } = params;

    if (!sql || typeof sql !== 'string') {
      throw new Error('Missing sql parameter');
    }

    const ddlRegex = /^(create|alter|drop|truncate|rename|comment)\s/i;
    if (ddlRegex.test(sql.trim()) && !ALLOW_DDL) {
      throw new Error('DDL operations are not allowed');
    }

    let conn;
    try {
      conn = await mysql.createConnection(getDbConfig());
      const [result] = await conn.query(sql);

      logSqlOperation(sql, result);

      return {
        result: result,
        affectedRows: result.affectedRows || 0,
        insertId: result.insertId,
      };
    } catch (err) {
      logSqlOperation(sql, null, err.message);
      throw new Error(`SQL execution error: ${err.message}`);
    } finally {
      if (conn) await conn.end();
    }
  },

  // Get database information
  get_database_info: async (params) => {
    let conn;
    try {
      conn = await mysql.createConnection(getDbConfig());
      const [databases] = await conn.query('SHOW DATABASES');
      const [tables] = await conn.query('SHOW TABLES');

      return {
        databases: databases.map(db => db.Database || Object.values(db)[0]),
        tables: tables.map(table => Object.values(table)[0]),
        config: {
          host: getDbConfig().host,
          port: getDbConfig().port,
          database: getDbConfig().database,
          allowDDL: ALLOW_DDL,
        }
      };
    } catch (err) {
      throw new Error(`Failed to get database information: ${err.message}`);
    } finally {
      if (conn) await conn.end();
    }
  },

  // Get operation logs
  get_operation_logs: async (params) => {
    const { limit = 50, offset = 0 } = params || {};

    // Validate parameters
    if (typeof limit !== 'number' || limit < 1 || limit > 1000) {
      throw new Error('limit parameter must be between 1-1000');
    }

    if (typeof offset !== 'number' || offset < 0) {
      throw new Error('offset parameter must be greater than or equal to 0');
    }

    // Return logs from memory
    const logs = operationLogs.slice(offset, offset + limit);

    return {
      logs: logs,
      total: operationLogs.length,
      limit: limit,
      offset: offset,
      hasMore: offset + limit < operationLogs.length
    };
  }
};

// Final MCP Server
class FinalMCPServer {
  constructor() {
    this.name = 'mysql-mcp-server';
    this.version = '1.0.0';
    this.initialized = false;
  }

  // Handle JSON-RPC requests
  async handleRequest(request) {
    try {
      const { jsonrpc, id, method, params } = request;

      if (jsonrpc !== '2.0') {
        logRequest('Unsupported JSON-RPC version', { jsonrpc }, null, 'Unsupported JSON-RPC version');
        throw new Error('Unsupported JSON-RPC version');
      }

      // Check initialization status (except for initialize method itself)
      if (method !== 'initialize' && !this.initialized) {
        throw new Error('Server not initialized');
      }

      let result = null;
      let error = null;

      try {
        if (method === 'initialize') {
          // If already initialized, return success but don't re-initialize
          if (!this.initialized) {
            this.initialized = true;
            
            // Record actual client information
            const clientInfo = params?.clientInfo || {};
            logRequest('initialize', { 
              protocolVersion: params?.protocolVersion || '2025-06-18', 
              capabilities: params?.capabilities || {}, 
              clientInfo: clientInfo 
            }, null, null);
          }
          
          // Build server capabilities to match client capabilities
          const serverCapabilities = {
            tools: {
              listChanged: false
            }
          };
          
          // If client supports prompts, we also support it
          if (params?.capabilities?.prompts) {
            serverCapabilities.prompts = {
              listChanged: false
            };
          }
          
          // If client supports resources, we also support it
          if (params?.capabilities?.resources) {
            serverCapabilities.resources = {
              listChanged: false
            };
          }
          
          // If client supports logging, we also support it
          if (params?.capabilities?.logging) {
            serverCapabilities.logging = {
              listChanged: false
            };
          }
          
          // If client supports roots, we also support it
          if (params?.capabilities?.roots) {
            serverCapabilities.roots = {
              listChanged: false
            };
          }
          
          result = {
            protocolVersion: params?.protocolVersion || '2025-06-18',
            capabilities: serverCapabilities,
            serverInfo: {
              name: this.name,
              version: this.version
            }
          };
        } else if (method === 'tools/list') {
          result = {
            tools: [
              {
                name: 'sql_query',
                description: 'Execute SQL query, supports DDL and DML operations',
                inputSchema: {
                  type: 'object',
                  properties: {
                    sql: {
                      type: 'string',
                      description: 'SQL statement to execute'
                    }
                  },
                  required: ['sql']
                }
              },
              {
                name: 'get_database_info',
                description: 'Get database information, including database list, table list and configuration information',
                inputSchema: {
                  type: 'object',
                  properties: {}
                }
              },
              {
                name: 'get_operation_logs',
                description: 'Get operation logs',
                inputSchema: {
                  type: 'object',
                  properties: {
                    limit: {
                      type: 'number',
                      description: 'Limit count, default 50'
                    },
                    offset: {
                      type: 'number',
                      description: 'Offset, default 0'
                    }
                  }
                }
              }
            ]
          };
        } else if (method === 'prompts/list') {
          // Return empty prompts list since we don't provide prompts functionality
          // Ensure return format complies with MCP protocol standard
          result = {
            prompts: []
          };
        } else if (method === 'prompts/call') {
          // Handle prompts call, but we don't provide prompts functionality
          // Return error instead of throwing exception to maintain protocol consistency
          result = {
            messages: [
              {
                role: 'assistant',
                content: [
                  {
                    type: 'text',
                    text: 'Unsupported prompts call'
                  }
                ]
              }
            ]
          };
        } else if (method === 'resources/list') {
          // Return empty resources list since we don't provide resources functionality
          result = {
            resources: []
          };
        } else if (method === 'resources/read') {
          // Handle resources read, but we don't provide resources functionality
          // Return error instead of throwing exception to maintain protocol consistency
          result = {
            contents: [
              {
                uri: 'error://unsupported',
                text: 'Unsupported resources read'
              }
            ]
          };
        } else if (method === 'logging/list') {
          // Return empty logging list since we don't provide logging functionality
          result = {
            logs: []
          };
        } else if (method === 'logging/read') {
          // Handle logging read, but we don't provide logging functionality
          // Return error instead of throwing exception to maintain protocol consistency
          result = {
            contents: [
              {
                uri: 'error://unsupported',
                text: 'Unsupported logging read'
              }
            ]
          };
        } else if (method === 'roots/list') {
          // Return empty roots list since we don't provide roots functionality
          result = {
            roots: []
          };
        } else if (method === 'roots/read') {
          // Handle roots read, but we don't provide roots functionality
          // Return error instead of throwing exception to maintain protocol consistency
          result = {
            contents: [
              {
                uri: 'error://unsupported',
                text: 'Unsupported roots read'
              }
            ]
          };
        } else if (method === 'tools/call') {
          const { name, arguments: args } = params || {};

          if (!name) {
            throw new Error('Missing tool name');
          }

          if (!handlers[name]) {
            throw new Error(`Unknown tool: ${name}`);
          }

          result = await handlers[name](args || {});

          // Tool call results need to be wrapped in content
          // Ensure return format complies with MCP protocol
          result = {
            content: [
              {
                type: 'text',
                text: JSON.stringify(result, null, 2)
              }
            ]
          };
        } else if (method === 'ping') {
          result = { pong: true };
        } else if (method === 'shutdown') {
          // Handle shutdown request
          result = null;
          // Delay exit to give client time to process response
          setTimeout(() => {
            process.exit(0);
          }, 100);
        } else if (method === 'notifications/initialized') {
          // Handle initialization notification - this is a standard notification in MCP protocol
          // Notifications don't need to return results, so result remains null
          result = null;
        } else if (method === 'notifications/exit') {
          // Handle exit notification
          result = null;
          process.exit(0);
        } else {
          throw new Error(`Unknown method: ${method}`);
        }
      } catch (err) {
        error = err.message;
        throw err;
      } finally {
        // Record all requests to log, ensure parameters are not undefined
        const safeParams = params || {};
        logRequest(method, safeParams, result, error);
      }

      // For notification methods, no response is needed
      if (method === 'notifications/initialized' || method === 'notifications/exit') {
        return null;
      }
      
      // shutdown method needs to return response
      if (method === 'shutdown') {
        return {
          jsonrpc: '2.0',
          id,
          result: null
        };
      }

      // Ensure all methods return correct response format
      return {
        jsonrpc: '2.0',
        id,
        result
      };
    } catch (error) {
      // Use standard MCP error codes
      let errorCode = -32603; // Internal error
      let errorMessage = error.message;
      
      if (error.message.includes('Server not initialized')) {
        errorCode = -32002; // Server not initialized
      } else if (error.message.includes('Unknown method')) {
        errorCode = -32601; // Method not found
      } else if (error.message.includes('Unsupported JSON-RPC version')) {
        errorCode = -32600; // Invalid Request
      }
      
      return {
        jsonrpc: '2.0',
        id: request.id,
        error: {
          code: errorCode,
          message: errorMessage
        }
      };
    }
  }

  // Start server
  async start() {
    console.error('MCP MySQL server started');

    // Display log configuration
    const logConfig = getLogConfig();
    console.error(`Log directory: ${logConfig.dir}`);
    console.error(`Log file: ${logConfig.fullPath}`);

    // Listen to stdin
    process.stdin.setEncoding('utf8');

    process.stdin.on('data', async (data) => {
      try {
        const lines = data.toString().trim().split('\n');

        for (const line of lines) {
          if (line.trim()) {
            const request = JSON.parse(line);
            const response = await this.handleRequest(request);
            console.log(JSON.stringify(response));
          }
        }
      } catch (error) {
        console.error('Error processing request:', error.message);
      }
    });

    // Handle process signals
    process.on('SIGTERM', () => {
      console.error('Received SIGTERM signal, shutting down server...');
      logRequest('SIGTERM', { signal: 'SIGTERM' }, { status: 'shutting_down' }, null);
      process.exit(0);
    });

    process.on('SIGINT', () => {
      console.error('Received SIGINT signal, shutting down server...');
      logRequest('SIGINT', { signal: 'SIGINT' }, { status: 'shutting_down' }, null);
      process.exit(0);
    });

    // Handle uncaught exceptions
    process.on('uncaughtException', (error) => {
      console.error('Uncaught exception:', error);
      logRequest('uncaughtException', { error: error.message, stack: error.stack }, null, error.message);
      process.exit(1);
    });

    process.on('unhandledRejection', (reason, promise) => {
      console.error('Unhandled Promise rejection:', reason);
      logRequest('unhandledRejection', { reason: reason.toString(), promise: promise.toString() }, null, reason.toString());
      process.exit(1);
    });

    // Record server startup
    logRequest('server_start', {
      name: this.name,
      version: this.version,
      logDir: logConfig.dir,
      logFile: logConfig.fullPath
    }, { status: 'started' }, null);
  }
}

// Start server
async function main() {
  const server = new FinalMCPServer();
  await server.start();
}

main().catch(console.error); 

Hey, does this work in a regular terminal? Also, you can check the logs in Cursor by opening the Output panel: go to View → Output, and in the dropdown menu, select MCP logs. Also, do you have a Git repo for your MCP server so we can check it out?

I uploaded the file to npm and ran it through npx. But now there is a problem. When the mcp.json configuration is updated, how can I get the latest environment variables without reopening the cursor? Or can I synchronize the latest configuration through switches?

Github:GitHub - liliangshan/mcp-server-mysql: MCP Tool server application with MySQL DDL operations, permission control and operation logs, supports npx/global commands.
npm code: @liangshanli/mcp-server-mysql - npm

Seems you have the same issue I faced: Cursor IDE's MCP server name caching Issue - always Red status

Try to rename your server. It is related to Cursor caching.

I have solved it. I pushed mcp to npmjs and then used it normally. Because the backend cannot stop automatically, I must use while to keep the application online.

1 Like