From 08312a0d0d9cddbc1bab1d08771c36b9a7b77fd0 Mon Sep 17 00:00:00 2001 From: HuiVoonC <108331037+CHuiV123@users.noreply.github.com> Date: Tue, 22 Jul 2025 14:49:21 +0800 Subject: [PATCH 01/22] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f517802..3efd5e2 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,9 @@ [![smithery badge](https://smithery.ai/badge/mysql-mcp-server)](https://smithery.ai/server/mysql-mcp-server) [![MseeP.ai Security Assessment Badge](https://mseep.net/mseep-audited.png)](https://mseep.ai/app/designcomputer-mysql-mcp-server) # MySQL MCP Server -A Model Context Protocol (MCP) implementation that enables secure interaction with MySQL databases. This server component facilitates communication between AI applications (hosts/clients) and MySQL databases, making database exploration and analysis safer and more structured through a controlled interface. +A Model Context Protocol (MCP) implementation that enables secure interaction with MySQL databases running on python server. This server component facilitates communication between AI applications (hosts/clients) and MySQL databases, making database exploration and analysis safer and more structured through a controlled interface. -> **Note**: MySQL MCP Server is not designed to be used as a standalone server, but rather as a communication protocol implementation between AI applications and MySQL databases. +> **Note**: MySQL MCP Server is not designed to be used as a standalone server, but rather as a communication protocol implementation between AI applications and MySQL databases. This modified version of MySQL MCP Server is compatible with langflow MCPTools component via STDIO protocol. ## Features - List available MySQL tables as resources From 43a4010c51a31b9a11513b47c80a557dc1efccaa Mon Sep 17 00:00:00 2001 From: HuiVoonC <108331037+CHuiV123@users.noreply.github.com> Date: Tue, 22 Jul 2025 14:51:25 +0800 Subject: [PATCH 02/22] Update requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 604e871..dee439f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ mcp>=1.0.0 -mysql-connector-python>=9.1.0 +PyMySQL From 56c1e2d115b496461d80d2fe20abd750c280951d Mon Sep 17 00:00:00 2001 From: HuiVoonC <108331037+CHuiV123@users.noreply.github.com> Date: Tue, 22 Jul 2025 15:36:09 +0800 Subject: [PATCH 03/22] Langflow Compatible Server.py Update code section to be compatible with Langflow STDIO protocol --- src/mysql_mcp_server/server.py | 145 +++++++++------------------------ 1 file changed, 40 insertions(+), 105 deletions(-) diff --git a/src/mysql_mcp_server/server.py b/src/mysql_mcp_server/server.py index 15fcdbd..0225748 100644 --- a/src/mysql_mcp_server/server.py +++ b/src/mysql_mcp_server/server.py @@ -1,3 +1,4 @@ +# server.py (MCP STDIO-Compatible) import asyncio import logging import os @@ -6,111 +7,79 @@ from mcp.server import Server from mcp.types import Resource, Tool, TextContent from pydantic import AnyUrl +from mcp.server.stdio import stdio_server -# Configure logging +# Log to stderr to not interfere with STDIO stream logging.basicConfig( level=logging.INFO, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + stream=sys.stderr ) logger = logging.getLogger("mysql_mcp_server") def get_db_config(): - """Get database configuration from environment variables.""" config = { "host": os.getenv("MYSQL_HOST", "localhost"), "port": int(os.getenv("MYSQL_PORT", "3306")), "user": os.getenv("MYSQL_USER"), "password": os.getenv("MYSQL_PASSWORD"), "database": os.getenv("MYSQL_DATABASE"), - # Add charset and collation to avoid utf8mb4_0900_ai_ci issues with older MySQL versions - # These can be overridden via environment variables for specific MySQL versions "charset": os.getenv("MYSQL_CHARSET", "utf8mb4"), "collation": os.getenv("MYSQL_COLLATION", "utf8mb4_unicode_ci"), - # Disable autocommit for better transaction control - "autocommit": True, - # Set SQL mode for better compatibility - can be overridden "sql_mode": os.getenv("MYSQL_SQL_MODE", "TRADITIONAL") } - - # Remove None values to let MySQL connector use defaults if not specified config = {k: v for k, v in config.items() if v is not None} if not all([config.get("user"), config.get("password"), config.get("database")]): - logger.error("Missing required database configuration. Please check environment variables:") - logger.error("MYSQL_USER, MYSQL_PASSWORD, and MYSQL_DATABASE are required") + logger.error("Missing required database configuration.") raise ValueError("Missing required database configuration") return config -# Initialize server app = Server("mysql_mcp_server") @app.list_resources() async def list_resources() -> list[Resource]: - """List MySQL tables as resources.""" config = get_db_config() try: - logger.info(f"Connecting to MySQL with charset: {config.get('charset')}, collation: {config.get('collation')}") with connect(**config) as conn: - logger.info(f"Successfully connected to MySQL server version: {conn.get_server_info()}") with conn.cursor() as cursor: cursor.execute("SHOW TABLES") tables = cursor.fetchall() - logger.info(f"Found tables: {tables}") - - resources = [] - for table in tables: - resources.append( - Resource( - uri=f"mysql://{table[0]}/data", - name=f"Table: {table[0]}", - mimeType="text/plain", - description=f"Data in table: {table[0]}" - ) + return [ + Resource( + uri=f"mysql://{table[0]}/data", + name=f"Table: {table[0]}", + mimeType="text/plain", + description=f"Data in table: {table[0]}" ) - return resources + for table in tables + ] except Error as e: - logger.error(f"Failed to list resources: {str(e)}") - logger.error(f"Error code: {e.errno}, SQL state: {e.sqlstate}") + logger.error(f"Error listing resources: {e}") return [] @app.read_resource() async def read_resource(uri: AnyUrl) -> str: - """Read table contents.""" config = get_db_config() - uri_str = str(uri) - logger.info(f"Reading resource: {uri_str}") - - if not uri_str.startswith("mysql://"): - raise ValueError(f"Invalid URI scheme: {uri_str}") - - parts = uri_str[8:].split('/') - table = parts[0] - + table = str(uri)[8:].split("/")[0] try: - logger.info(f"Connecting to MySQL with charset: {config.get('charset')}, collation: {config.get('collation')}") with connect(**config) as conn: - logger.info(f"Successfully connected to MySQL server version: {conn.get_server_info()}") with conn.cursor() as cursor: cursor.execute(f"SELECT * FROM {table} LIMIT 100") columns = [desc[0] for desc in cursor.description] rows = cursor.fetchall() - result = [",".join(map(str, row)) for row in rows] - return "\n".join([",".join(columns)] + result) - + return "\n".join([",".join(columns)] + [",".join(map(str, row)) for row in rows]) except Error as e: - logger.error(f"Database error reading resource {uri}: {str(e)}") - logger.error(f"Error code: {e.errno}, SQL state: {e.sqlstate}") - raise RuntimeError(f"Database error: {str(e)}") + logger.error(f"Error reading resource: {e}") + raise RuntimeError(f"Error: {e}") @app.list_tools() async def list_tools() -> list[Tool]: - """List available MySQL tools.""" - logger.info("Listing tools...") return [ Tool( name="execute_sql", - description="Execute an SQL query on the MySQL server", + description="Run custom SQL queries.", inputSchema={ "type": "object", "properties": { @@ -126,77 +95,43 @@ async def list_tools() -> list[Tool]: @app.call_tool() async def call_tool(name: str, arguments: dict) -> list[TextContent]: - """Execute SQL commands.""" config = get_db_config() - logger.info(f"Calling tool: {name} with arguments: {arguments}") + logger.info(f"Tool called: {name} with arguments: {arguments}") if name != "execute_sql": raise ValueError(f"Unknown tool: {name}") query = arguments.get("query") if not query: - raise ValueError("Query is required") + raise ValueError("Missing 'query' parameter") try: - logger.info(f"Connecting to MySQL with charset: {config.get('charset')}, collation: {config.get('collation')}") - with connect(**config) as conn: - logger.info(f"Successfully connected to MySQL server version: {conn.get_server_info()}") + conn = connect(**config) + conn.autocommit = True # ✅ Explicitly enable autocommit + + with conn: with conn.cursor() as cursor: + logger.info(f"Executing query: {query}") cursor.execute(query) - # Special handling for SHOW TABLES - if query.strip().upper().startswith("SHOW TABLES"): - tables = cursor.fetchall() - result = ["Tables_in_" + config["database"]] # Header - result.extend([table[0] for table in tables]) - return [TextContent(type="text", text="\n".join(result))] - - # Handle all other queries that return result sets (SELECT, SHOW, DESCRIBE etc.) - elif cursor.description is not None: + if cursor.description: + # SELECT query columns = [desc[0] for desc in cursor.description] - try: - rows = cursor.fetchall() - result = [",".join(map(str, row)) for row in rows] - return [TextContent(type="text", text="\n".join([",".join(columns)] + result))] - except Error as e: - logger.warning(f"Error fetching results: {str(e)}") - return [TextContent(type="text", text=f"Query executed but error fetching results: {str(e)}")] - - # Non-SELECT queries + rows = cursor.fetchall() + result = [",".join(columns)] + [",".join(map(str, row)) for row in rows] + return [TextContent(type="text", text="\n".join(result))] else: - conn.commit() - return [TextContent(type="text", text=f"Query executed successfully. Rows affected: {cursor.rowcount}")] + # INSERT / UPDATE / DELETE + return [TextContent(type="text", text=f"Success. Rows affected: {cursor.rowcount}")] except Error as e: - logger.error(f"Error executing SQL '{query}': {e}") - logger.error(f"Error code: {e.errno}, SQL state: {e.sqlstate}") - return [TextContent(type="text", text=f"Error executing query: {str(e)}")] + logger.error(f"SQL Error: {e}") + return [TextContent(type="text", text=f"SQL Error: {e}")] async def main(): - """Main entry point to run the MCP server.""" - from mcp.server.stdio import stdio_server - - # Add additional debug output - print("Starting MySQL MCP server with config:", file=sys.stderr) - config = get_db_config() - print(f"Host: {config['host']}", file=sys.stderr) - print(f"Port: {config['port']}", file=sys.stderr) - print(f"User: {config['user']}", file=sys.stderr) - print(f"Database: {config['database']}", file=sys.stderr) - - logger.info("Starting MySQL MCP server...") - logger.info(f"Database config: {config['host']}/{config['database']} as {config['user']}") - - async with stdio_server() as (read_stream, write_stream): - try: - await app.run( - read_stream, - write_stream, - app.create_initialization_options() - ) - except Exception as e: - logger.error(f"Server error: {str(e)}", exc_info=True) - raise + logger.info("Starting MCP STDIO server...") + async with stdio_server() as (reader, writer): + await app.run(reader, writer, app.create_initialization_options()) if __name__ == "__main__": - asyncio.run(main()) \ No newline at end of file + asyncio.run(main()) From bfd3d645d6057123500b0e3d837221079f83e772 Mon Sep 17 00:00:00 2001 From: HuiVoonC <108331037+CHuiV123@users.noreply.github.com> Date: Tue, 22 Jul 2025 15:42:51 +0800 Subject: [PATCH 04/22] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3efd5e2..0eafa5a 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ # MySQL MCP Server A Model Context Protocol (MCP) implementation that enables secure interaction with MySQL databases running on python server. This server component facilitates communication between AI applications (hosts/clients) and MySQL databases, making database exploration and analysis safer and more structured through a controlled interface. -> **Note**: MySQL MCP Server is not designed to be used as a standalone server, but rather as a communication protocol implementation between AI applications and MySQL databases. This modified version of MySQL MCP Server is compatible with langflow MCPTools component via STDIO protocol. +> **Note**: MySQL MCP Server is not designed to be used as a standalone server, but rather as a communication protocol implementation between AI applications and MySQL databases. This modified version of MySQL MCP Server is compatible with [Langflow][https://github.com/]MCPTools component via STDIO protocol. ## Features - List available MySQL tables as resources From fff77f6cc5ee4ea44ee6f25f68e56c8e890271d0 Mon Sep 17 00:00:00 2001 From: HuiVoonC <108331037+CHuiV123@users.noreply.github.com> Date: Tue, 22 Jul 2025 15:43:25 +0800 Subject: [PATCH 05/22] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0eafa5a..413854f 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ # MySQL MCP Server A Model Context Protocol (MCP) implementation that enables secure interaction with MySQL databases running on python server. This server component facilitates communication between AI applications (hosts/clients) and MySQL databases, making database exploration and analysis safer and more structured through a controlled interface. -> **Note**: MySQL MCP Server is not designed to be used as a standalone server, but rather as a communication protocol implementation between AI applications and MySQL databases. This modified version of MySQL MCP Server is compatible with [Langflow][https://github.com/]MCPTools component via STDIO protocol. +> **Note**: MySQL MCP Server is not designed to be used as a standalone server, but rather as a communication protocol implementation between AI applications and MySQL databases. This modified version of MySQL MCP Server is compatible with [Langflow](https://github.com/langflow-ai/langflow) MCPTools component via STDIO protocol. ## Features - List available MySQL tables as resources From 221ed9c33b6de58bbf7ed636d391d0afb1e3ee5e Mon Sep 17 00:00:00 2001 From: HuiVoonC <108331037+CHuiV123@users.noreply.github.com> Date: Tue, 22 Jul 2025 15:48:08 +0800 Subject: [PATCH 06/22] Update README.md --- README.md | 71 ++----------------------------------------------------- 1 file changed, 2 insertions(+), 69 deletions(-) diff --git a/README.md b/README.md index 413854f..4f1e5ed 100644 --- a/README.md +++ b/README.md @@ -14,79 +14,12 @@ A Model Context Protocol (MCP) implementation that enables secure interaction wi - Secure database access through environment variables - Comprehensive logging -## Installation +## Installation ### Manual Installation -```bash -pip install mysql-mcp-server -``` +```git clone ``` -### Installing via Smithery -To install MySQL MCP Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/mysql-mcp-server): -```bash -npx -y @smithery/cli install mysql-mcp-server --client claude -``` -## Configuration -Set the following environment variables: -```bash -MYSQL_HOST=localhost # Database host -MYSQL_PORT=3306 # Optional: Database port (defaults to 3306 if not specified) -MYSQL_USER=your_username -MYSQL_PASSWORD=your_password -MYSQL_DATABASE=your_database -``` - -## Usage -### With Claude Desktop -Add this to your `claude_desktop_config.json`: -```json -{ - "mcpServers": { - "mysql": { - "command": "uv", - "args": [ - "--directory", - "path/to/mysql_mcp_server", - "run", - "mysql_mcp_server" - ], - "env": { - "MYSQL_HOST": "localhost", - "MYSQL_PORT": "3306", - "MYSQL_USER": "your_username", - "MYSQL_PASSWORD": "your_password", - "MYSQL_DATABASE": "your_database" - } - } - } -} -``` -### With Visual Studio Code -Add this to your `mcp.json`: -```json -{ - "servers": { - "mysql": { - "type": "stdio", - "command": "uvx", - "args": [ - "--from", - "mysql-mcp-server", - "mysql_mcp_server" - ], - "env": { - "MYSQL_HOST": "localhost", - "MYSQL_PORT": "3306", - "MYSQL_USER": "your_username", - "MYSQL_PASSWORD": "your_password", - "MYSQL_DATABASE": "your_database" - } - } - } -} -``` -Note: Will need to install uv for this to work ### Debugging with MCP Inspector While MySQL MCP Server isn't intended to be run standalone or directly from the command line with Python, you can use the MCP Inspector to debug it. From c6b6cacb0b6aff18f93b09da4340e8d6b36ced49 Mon Sep 17 00:00:00 2001 From: HuiVoonC <108331037+CHuiV123@users.noreply.github.com> Date: Tue, 22 Jul 2025 15:49:34 +0800 Subject: [PATCH 07/22] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4f1e5ed..f5d911a 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ A Model Context Protocol (MCP) implementation that enables secure interaction wi ## Installation ### Manual Installation -```git clone ``` +```git clone https://github.com/CHuiV123/mysql_mcp_server_LangflowCompatible.git``` From cb992124a450746cba6d77815ec8fa1dab0d53ec Mon Sep 17 00:00:00 2001 From: HuiVoonC <108331037+CHuiV123@users.noreply.github.com> Date: Tue, 22 Jul 2025 15:50:11 +0800 Subject: [PATCH 08/22] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f5d911a..f4b69f9 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,9 @@ A Model Context Protocol (MCP) implementation that enables secure interaction wi ## Installation ### Manual Installation -```git clone https://github.com/CHuiV123/mysql_mcp_server_LangflowCompatible.git``` +```bash +git clone https://github.com/CHuiV123/mysql_mcp_server_LangflowCompatible.git +``` From b8ab92b405102656290835deed1aa48bc36863e3 Mon Sep 17 00:00:00 2001 From: HuiVoonC <108331037+CHuiV123@users.noreply.github.com> Date: Tue, 22 Jul 2025 16:07:13 +0800 Subject: [PATCH 09/22] Update README.md --- README.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/README.md b/README.md index f4b69f9..6927608 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,42 @@ A Model Context Protocol (MCP) implementation that enables secure interaction wi ## Installation ### Manual Installation + +1. Open up Windows CMD, cd to desktop location. +```bash +cd desktop +``` + +2. ```bash git clone https://github.com/CHuiV123/mysql_mcp_server_LangflowCompatible.git ``` +3. Get into the folder directory: +```bash +cd mysql_mcp_server_LangflowCompatible +``` +4. Install dependencies +```bash +pip install -r requirements.txt +``` + +5. set up environment variable as below, replace all the "<-CHANGE THIS" section: +```bash +set MYSQL_HOST="YOUR_DATABASE_HOST" <-CHANGE THIS +set MYSQL_PORT=3306 <-CHANGE THIS IF YOUR DATABASE IS NOT RUNNING ON 3306 +set MYSQL_USER="YOUR_USER_NAME" <-CHANGE THIS +set MYSQL_PASSWORD="YOUR_PASSWORD" <-CHANGE THIS +set MYSQL_DATABASE="yOUR_DATABASE_NAME" <-CHANGE THIS +``` + +6. Start the server: +```bash +uv --directory . run mysql_mcp_server +``` + +Upon successful server start up, you shall see "mysql_mcp_server - INFO - Starting MCP STDIO server..." ### Debugging with MCP Inspector @@ -72,6 +103,19 @@ See [MySQL Security Configuration Guide](https://github.com/designcomputer/mysql ⚠️ IMPORTANT: Always follow the principle of least privilege when configuring database access. +'---------' + +## Setting up in Langflow MCPTools + +1. Go to Langflow setting, look for MCP server. +2. +3. Click 'Add MCP Server" + + + + + + ## License MIT License - see LICENSE file for details. From f6705337745d81cb4a9a00c7e994ededa2e58561 Mon Sep 17 00:00:00 2001 From: HuiVoonC <108331037+CHuiV123@users.noreply.github.com> Date: Tue, 22 Jul 2025 16:15:35 +0800 Subject: [PATCH 10/22] Update README.md --- README.md | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 6927608..2122703 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,26 @@ uv --directory . run mysql_mcp_server Upon successful server start up, you shall see "mysql_mcp_server - INFO - Starting MCP STDIO server..." +## Setting up in Langflow MCPTools +*** pre-requisite, at this point MySQL MCP server should be already up and running. + +1. Go to Langflow setting, look for MCP server. +2. Click **Add MCP Server** +3. Select **STDIO** +4. Server name **MySQL_MCP** +5. Command **python** +6. Arguments **your directory path of server.py** +7. Environment variable +``` +MYSQL_HOST= **YOUR_HOST** +MYSQL_PORT= **YOUR_PORT** +MYSQL_USER= **YOUR_USER_NAME** +MYSQL_PASSWORD= **YOUR_PASSWORD** +MYSQL_DATABASE= **YOUR_DATABASE_NAME** +``` +8. Click **Add server** +9. Restart Langflow + ### Debugging with MCP Inspector While MySQL MCP Server isn't intended to be run standalone or directly from the command line with Python, you can use the MCP Inspector to debug it. @@ -103,19 +123,6 @@ See [MySQL Security Configuration Guide](https://github.com/designcomputer/mysql ⚠️ IMPORTANT: Always follow the principle of least privilege when configuring database access. -'---------' - -## Setting up in Langflow MCPTools - -1. Go to Langflow setting, look for MCP server. -2. -3. Click 'Add MCP Server" - - - - - - ## License MIT License - see LICENSE file for details. From 1aeef3bf591b13cf9de7a7d5ee42554f63e4510f Mon Sep 17 00:00:00 2001 From: HuiVoonC <108331037+CHuiV123@users.noreply.github.com> Date: Tue, 22 Jul 2025 16:16:36 +0800 Subject: [PATCH 11/22] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2122703..11a5f35 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ Upon successful server start up, you shall see "mysql_mcp_server - INFO - Starti 4. Server name **MySQL_MCP** 5. Command **python** 6. Arguments **your directory path of server.py** -7. Environment variable +7. Add environment variable ``` MYSQL_HOST= **YOUR_HOST** MYSQL_PORT= **YOUR_PORT** From c4d58f5a69b06060ebbbdc49841d083f709155cb Mon Sep 17 00:00:00 2001 From: HuiVoonC <108331037+CHuiV123@users.noreply.github.com> Date: Tue, 22 Jul 2025 16:21:58 +0800 Subject: [PATCH 12/22] Update requirements.txt --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index dee439f..f6a8e95 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ mcp>=1.0.0 PyMySQL +uv From 8a5a1a0e0c365a7ab6a159195309ee6c673aab5e Mon Sep 17 00:00:00 2001 From: HuiVoonC <108331037+CHuiV123@users.noreply.github.com> Date: Tue, 22 Jul 2025 16:27:53 +0800 Subject: [PATCH 13/22] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 11a5f35..867d881 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ A Model Context Protocol (MCP) implementation that enables secure interaction wi cd desktop ``` -2. +2. Cloning the repo ```bash git clone https://github.com/CHuiV123/mysql_mcp_server_LangflowCompatible.git ``` From ff1105061e15d0a4eece132ff93128e745b0b9b2 Mon Sep 17 00:00:00 2001 From: HuiVoonC <108331037+CHuiV123@users.noreply.github.com> Date: Tue, 22 Jul 2025 16:29:18 +0800 Subject: [PATCH 14/22] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 867d881..95bf03e 100644 --- a/README.md +++ b/README.md @@ -59,9 +59,9 @@ Upon successful server start up, you shall see "mysql_mcp_server - INFO - Starti 1. Go to Langflow setting, look for MCP server. 2. Click **Add MCP Server** 3. Select **STDIO** -4. Server name **MySQL_MCP** -5. Command **python** -6. Arguments **your directory path of server.py** +4. Server name= **MySQL_MCP** +5. Command= **python** +6. Arguments= **your directory path of server.py** 7. Add environment variable ``` MYSQL_HOST= **YOUR_HOST** From 55c1871436a84e5c238a6ef0da9dac9c9d79aea8 Mon Sep 17 00:00:00 2001 From: HuiVoonC <108331037+CHuiV123@users.noreply.github.com> Date: Tue, 22 Jul 2025 16:38:16 +0800 Subject: [PATCH 15/22] Update server.py --- src/mysql_mcp_server/server.py | 99 +++++++++++++++++++++++----------- 1 file changed, 68 insertions(+), 31 deletions(-) diff --git a/src/mysql_mcp_server/server.py b/src/mysql_mcp_server/server.py index 0225748..0510560 100644 --- a/src/mysql_mcp_server/server.py +++ b/src/mysql_mcp_server/server.py @@ -40,46 +40,70 @@ def get_db_config(): @app.list_resources() async def list_resources() -> list[Resource]: + """List MySQL tables as resources.""" config = get_db_config() try: + logger.info(f"Connecting to MySQL with charset: {config.get('charset')}, collation: {config.get('collation')}") with connect(**config) as conn: + logger.info(f"Successfully connected to MySQL server version: {conn.get_server_info()}") with conn.cursor() as cursor: cursor.execute("SHOW TABLES") tables = cursor.fetchall() - return [ - Resource( - uri=f"mysql://{table[0]}/data", - name=f"Table: {table[0]}", - mimeType="text/plain", - description=f"Data in table: {table[0]}" + logger.info(f"Found tables: {tables}") + + resources = [] + for table in tables: + resources.append( + Resource( + uri=f"mysql://{table[0]}/data", + name=f"Table: {table[0]}", + mimeType="text/plain", + description=f"Data in table: {table[0]}" + ) ) - for table in tables - ] + return resources except Error as e: - logger.error(f"Error listing resources: {e}") + logger.error(f"Failed to list resources: {str(e)}") + logger.error(f"Error code: {e.errno}, SQL state: {e.sqlstate}") return [] @app.read_resource() async def read_resource(uri: AnyUrl) -> str: + """Read table contents.""" config = get_db_config() - table = str(uri)[8:].split("/")[0] + uri_str = str(uri) + logger.info(f"Reading resource: {uri_str}") + + if not uri_str.startswith("mysql://"): + raise ValueError(f"Invalid URI scheme: {uri_str}") + + parts = uri_str[8:].split('/') + table = parts[0] + try: + logger.info(f"Connecting to MySQL with charset: {config.get('charset')}, collation: {config.get('collation')}") with connect(**config) as conn: + logger.info(f"Successfully connected to MySQL server version: {conn.get_server_info()}") with conn.cursor() as cursor: cursor.execute(f"SELECT * FROM {table} LIMIT 100") columns = [desc[0] for desc in cursor.description] rows = cursor.fetchall() - return "\n".join([",".join(columns)] + [",".join(map(str, row)) for row in rows]) + result = [",".join(map(str, row)) for row in rows] + return "\n".join([",".join(columns)] + result) + except Error as e: - logger.error(f"Error reading resource: {e}") - raise RuntimeError(f"Error: {e}") + logger.error(f"Database error reading resource {uri}: {str(e)}") + logger.error(f"Error code: {e.errno}, SQL state: {e.sqlstate}") + raise RuntimeError(f"Database error: {str(e)}") @app.list_tools() async def list_tools() -> list[Tool]: + """List available MySQL tools.""" + logger.info("Listing tools...") return [ Tool( name="execute_sql", - description="Run custom SQL queries.", + description="Execute an SQL query on the MySQL server", inputSchema={ "type": "object", "properties": { @@ -95,39 +119,52 @@ async def list_tools() -> list[Tool]: @app.call_tool() async def call_tool(name: str, arguments: dict) -> list[TextContent]: + """Execute SQL commands.""" config = get_db_config() - logger.info(f"Tool called: {name} with arguments: {arguments}") + logger.info(f"Calling tool: {name} with arguments: {arguments}") if name != "execute_sql": raise ValueError(f"Unknown tool: {name}") query = arguments.get("query") if not query: - raise ValueError("Missing 'query' parameter") + raise ValueError("Query is required") try: - conn = connect(**config) - conn.autocommit = True # ✅ Explicitly enable autocommit - - with conn: + logger.info(f"Connecting to MySQL with charset: {config.get('charset')}, collation: {config.get('collation')}") + with connect(**config) as conn: + logger.info(f"Successfully connected to MySQL server version: {conn.get_server_info()}") with conn.cursor() as cursor: - logger.info(f"Executing query: {query}") cursor.execute(query) - if cursor.description: - # SELECT query - columns = [desc[0] for desc in cursor.description] - rows = cursor.fetchall() - result = [",".join(columns)] + [",".join(map(str, row)) for row in rows] + # Special handling for SHOW TABLES + if query.strip().upper().startswith("SHOW TABLES"): + tables = cursor.fetchall() + result = ["Tables_in_" + config["database"]] # Header + result.extend([table[0] for table in tables]) return [TextContent(type="text", text="\n".join(result))] + + # Handle all other queries that return result sets (SELECT, SHOW, DESCRIBE etc.) + elif cursor.description is not None: + columns = [desc[0] for desc in cursor.description] + try: + rows = cursor.fetchall() + result = [",".join(map(str, row)) for row in rows] + return [TextContent(type="text", text="\n".join([",".join(columns)] + result))] + except Error as e: + logger.warning(f"Error fetching results: {str(e)}") + return [TextContent(type="text", text=f"Query executed but error fetching results: {str(e)}")] + + # Non-SELECT queries else: - # INSERT / UPDATE / DELETE - return [TextContent(type="text", text=f"Success. Rows affected: {cursor.rowcount}")] + conn.commit() + return [TextContent(type="text", text=f"Query executed successfully. Rows affected: {cursor.rowcount}")] except Error as e: - logger.error(f"SQL Error: {e}") - return [TextContent(type="text", text=f"SQL Error: {e}")] - + logger.error(f"Error executing SQL '{query}': {e}") + logger.error(f"Error code: {e.errno}, SQL state: {e.sqlstate}") + return [TextContent(type="text", text=f"Error executing query: {str(e)}")] + async def main(): logger.info("Starting MCP STDIO server...") async with stdio_server() as (reader, writer): From 84b302756cbc58625e8bc3f3bd15c68683d7e5da Mon Sep 17 00:00:00 2001 From: HuiVoonC <108331037+CHuiV123@users.noreply.github.com> Date: Fri, 1 Aug 2025 15:44:43 +0800 Subject: [PATCH 16/22] Update server.py --- src/mysql_mcp_server/server.py | 102 +++++++++++++-------------------- 1 file changed, 40 insertions(+), 62 deletions(-) diff --git a/src/mysql_mcp_server/server.py b/src/mysql_mcp_server/server.py index 0510560..a9ee57a 100644 --- a/src/mysql_mcp_server/server.py +++ b/src/mysql_mcp_server/server.py @@ -1,4 +1,5 @@ -# server.py (MCP STDIO-Compatible) +# server.py (Optimized for Langflow MCP via STDIO) + import asyncio import logging import os @@ -9,7 +10,7 @@ from pydantic import AnyUrl from mcp.server.stdio import stdio_server -# Log to stderr to not interfere with STDIO stream +# --- Logging setup: log to stderr (not STDIO) logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', @@ -17,6 +18,7 @@ ) logger = logging.getLogger("mysql_mcp_server") +# --- Database config from environment def get_db_config(): config = { "host": os.getenv("MYSQL_HOST", "localhost"), @@ -26,80 +28,62 @@ def get_db_config(): "database": os.getenv("MYSQL_DATABASE"), "charset": os.getenv("MYSQL_CHARSET", "utf8mb4"), "collation": os.getenv("MYSQL_COLLATION", "utf8mb4_unicode_ci"), + "autocommit": True, "sql_mode": os.getenv("MYSQL_SQL_MODE", "TRADITIONAL") } config = {k: v for k, v in config.items() if v is not None} - if not all([config.get("user"), config.get("password"), config.get("database")]): - logger.error("Missing required database configuration.") + logger.error("Missing required database configuration: MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE") raise ValueError("Missing required database configuration") - return config +# --- MCP Server Setup app = Server("mysql_mcp_server") @app.list_resources() async def list_resources() -> list[Resource]: - """List MySQL tables as resources.""" config = get_db_config() try: - logger.info(f"Connecting to MySQL with charset: {config.get('charset')}, collation: {config.get('collation')}") with connect(**config) as conn: - logger.info(f"Successfully connected to MySQL server version: {conn.get_server_info()}") with conn.cursor() as cursor: cursor.execute("SHOW TABLES") tables = cursor.fetchall() - logger.info(f"Found tables: {tables}") - - resources = [] - for table in tables: - resources.append( - Resource( - uri=f"mysql://{table[0]}/data", - name=f"Table: {table[0]}", - mimeType="text/plain", - description=f"Data in table: {table[0]}" - ) + return [ + Resource( + uri=f"mysql://{table[0]}/data", + name=f"Table: {table[0]}", + mimeType="text/plain", + description=f"Data in table: {table[0]}" ) - return resources + for table in tables + ] except Error as e: - logger.error(f"Failed to list resources: {str(e)}") - logger.error(f"Error code: {e.errno}, SQL state: {e.sqlstate}") + logger.error(f"Error listing resources: {e} | Code: {e.errno}, SQL state: {e.sqlstate}") return [] @app.read_resource() async def read_resource(uri: AnyUrl) -> str: - """Read table contents.""" config = get_db_config() uri_str = str(uri) - logger.info(f"Reading resource: {uri_str}") if not uri_str.startswith("mysql://"): - raise ValueError(f"Invalid URI scheme: {uri_str}") - - parts = uri_str[8:].split('/') - table = parts[0] + raise ValueError(f"Invalid URI: {uri_str}") + table = uri_str[8:].split('/')[0] try: - logger.info(f"Connecting to MySQL with charset: {config.get('charset')}, collation: {config.get('collation')}") with connect(**config) as conn: - logger.info(f"Successfully connected to MySQL server version: {conn.get_server_info()}") with conn.cursor() as cursor: cursor.execute(f"SELECT * FROM {table} LIMIT 100") columns = [desc[0] for desc in cursor.description] rows = cursor.fetchall() result = [",".join(map(str, row)) for row in rows] return "\n".join([",".join(columns)] + result) - except Error as e: - logger.error(f"Database error reading resource {uri}: {str(e)}") - logger.error(f"Error code: {e.errno}, SQL state: {e.sqlstate}") + logger.error(f"Error reading {uri_str}: {e} | Code: {e.errno}, SQL state: {e.sqlstate}") raise RuntimeError(f"Database error: {str(e)}") @app.list_tools() async def list_tools() -> list[Tool]: - """List available MySQL tools.""" - logger.info("Listing tools...") return [ Tool( name="execute_sql", @@ -119,56 +103,50 @@ async def list_tools() -> list[Tool]: @app.call_tool() async def call_tool(name: str, arguments: dict) -> list[TextContent]: - """Execute SQL commands.""" config = get_db_config() - logger.info(f"Calling tool: {name} with arguments: {arguments}") if name != "execute_sql": raise ValueError(f"Unknown tool: {name}") query = arguments.get("query") if not query: - raise ValueError("Query is required") + raise ValueError("Missing query") try: - logger.info(f"Connecting to MySQL with charset: {config.get('charset')}, collation: {config.get('collation')}") with connect(**config) as conn: - logger.info(f"Successfully connected to MySQL server version: {conn.get_server_info()}") with conn.cursor() as cursor: cursor.execute(query) - # Special handling for SHOW TABLES - if query.strip().upper().startswith("SHOW TABLES"): - tables = cursor.fetchall() - result = ["Tables_in_" + config["database"]] # Header - result.extend([table[0] for table in tables]) - return [TextContent(type="text", text="\n".join(result))] - - # Handle all other queries that return result sets (SELECT, SHOW, DESCRIBE etc.) - elif cursor.description is not None: + # Result sets (SELECT, SHOW, etc.) + if cursor.description: columns = [desc[0] for desc in cursor.description] try: rows = cursor.fetchall() result = [",".join(map(str, row)) for row in rows] return [TextContent(type="text", text="\n".join([",".join(columns)] + result))] except Error as e: - logger.warning(f"Error fetching results: {str(e)}") - return [TextContent(type="text", text=f"Query executed but error fetching results: {str(e)}")] - - # Non-SELECT queries - else: - conn.commit() - return [TextContent(type="text", text=f"Query executed successfully. Rows affected: {cursor.rowcount}")] + logger.warning(f"Query executed but fetch failed: {e}") + return [TextContent(type="text", text=f"Fetch error: {e}")] + # Non-result queries + conn.commit() + return [TextContent(type="text", text=f"Query executed. Rows affected: {cursor.rowcount}")] except Error as e: - logger.error(f"Error executing SQL '{query}': {e}") - logger.error(f"Error code: {e.errno}, SQL state: {e.sqlstate}") + logger.error(f"SQL execution error: {e} | Code: {e.errno}, SQL state: {e.sqlstate}") return [TextContent(type="text", text=f"Error executing query: {str(e)}")] - + +# --- Entry point async def main(): - logger.info("Starting MCP STDIO server...") - async with stdio_server() as (reader, writer): - await app.run(reader, writer, app.create_initialization_options()) + print("Starting MySQL MCP Server...", file=sys.stderr) + config = get_db_config() + print(f"🔧 DB Config -> Host: {config['host']} | User: {config['user']} | DB: {config['database']}", file=sys.stderr) + + try: + async with stdio_server() as (reader, writer): + await app.run(reader, writer, app.create_initialization_options()) + except Exception as e: + logger.error(f"Fatal server error: {e}", exc_info=True) + raise if __name__ == "__main__": asyncio.run(main()) From e7132cb7296fdaffa001291fd6d5b85e00d5f2c4 Mon Sep 17 00:00:00 2001 From: HuiVoonC <108331037+CHuiV123@users.noreply.github.com> Date: Fri, 1 Aug 2025 15:57:14 +0800 Subject: [PATCH 17/22] Update server.py --- src/mysql_mcp_server/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mysql_mcp_server/server.py b/src/mysql_mcp_server/server.py index a9ee57a..37dbf4d 100644 --- a/src/mysql_mcp_server/server.py +++ b/src/mysql_mcp_server/server.py @@ -139,7 +139,7 @@ async def call_tool(name: str, arguments: dict) -> list[TextContent]: async def main(): print("Starting MySQL MCP Server...", file=sys.stderr) config = get_db_config() - print(f"🔧 DB Config -> Host: {config['host']} | User: {config['user']} | DB: {config['database']}", file=sys.stderr) + print(f"DB Config -> Host: {config['host']} | User: {config['user']} | DB: {config['database']}", file=sys.stderr) try: async with stdio_server() as (reader, writer): From 8bfd41039532bbf2a925618e9278f7d11bb83126 Mon Sep 17 00:00:00 2001 From: HuiVoonC <108331037+CHuiV123@users.noreply.github.com> Date: Fri, 1 Aug 2025 16:03:25 +0800 Subject: [PATCH 18/22] Update server.py --- src/mysql_mcp_server/server.py | 110 ++++++++++++++++++++------------- 1 file changed, 66 insertions(+), 44 deletions(-) diff --git a/src/mysql_mcp_server/server.py b/src/mysql_mcp_server/server.py index 37dbf4d..4f6d9fc 100644 --- a/src/mysql_mcp_server/server.py +++ b/src/mysql_mcp_server/server.py @@ -1,5 +1,4 @@ -# server.py (Optimized for Langflow MCP via STDIO) - +# server.py (Langflow MCP STDIO-Compatible) import asyncio import logging import os @@ -10,7 +9,7 @@ from pydantic import AnyUrl from mcp.server.stdio import stdio_server -# --- Logging setup: log to stderr (not STDIO) +# Log to stderr to not interfere with STDIO stream logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', @@ -18,72 +17,89 @@ ) logger = logging.getLogger("mysql_mcp_server") -# --- Database config from environment def get_db_config(): config = { - "host": os.getenv("MYSQL_HOST", "localhost"), - "port": int(os.getenv("MYSQL_PORT", "3306")), - "user": os.getenv("MYSQL_USER"), - "password": os.getenv("MYSQL_PASSWORD"), - "database": os.getenv("MYSQL_DATABASE"), + "host": "localhost", + "port": 3306, + "user": "root", + "password": "admin", + "database": "langflow", "charset": os.getenv("MYSQL_CHARSET", "utf8mb4"), "collation": os.getenv("MYSQL_COLLATION", "utf8mb4_unicode_ci"), - "autocommit": True, "sql_mode": os.getenv("MYSQL_SQL_MODE", "TRADITIONAL") } config = {k: v for k, v in config.items() if v is not None} + if not all([config.get("user"), config.get("password"), config.get("database")]): - logger.error("Missing required database configuration: MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE") + logger.error("Missing required database configuration.") raise ValueError("Missing required database configuration") + return config -# --- MCP Server Setup app = Server("mysql_mcp_server") @app.list_resources() async def list_resources() -> list[Resource]: + """List MySQL tables as resources.""" config = get_db_config() try: + logger.info(f"Connecting to MySQL with charset: {config.get('charset')}, collation: {config.get('collation')}") with connect(**config) as conn: + logger.info(f"Successfully connected to MySQL server version: {conn.get_server_info()}") with conn.cursor() as cursor: cursor.execute("SHOW TABLES") tables = cursor.fetchall() - return [ - Resource( - uri=f"mysql://{table[0]}/data", - name=f"Table: {table[0]}", - mimeType="text/plain", - description=f"Data in table: {table[0]}" + logger.info(f"Found tables: {tables}") + + resources = [] + for table in tables: + resources.append( + Resource( + uri=f"mysql://{table[0]}/data", + name=f"Table: {table[0]}", + mimeType="text/plain", + description=f"Data in table: {table[0]}" + ) ) - for table in tables - ] + return resources except Error as e: - logger.error(f"Error listing resources: {e} | Code: {e.errno}, SQL state: {e.sqlstate}") + logger.error(f"Failed to list resources: {str(e)}") + logger.error(f"Error code: {e.errno}, SQL state: {e.sqlstate}") return [] @app.read_resource() async def read_resource(uri: AnyUrl) -> str: + """Read table contents.""" config = get_db_config() uri_str = str(uri) + logger.info(f"Reading resource: {uri_str}") if not uri_str.startswith("mysql://"): - raise ValueError(f"Invalid URI: {uri_str}") - table = uri_str[8:].split('/')[0] + raise ValueError(f"Invalid URI scheme: {uri_str}") + + parts = uri_str[8:].split('/') + table = parts[0] try: + logger.info(f"Connecting to MySQL with charset: {config.get('charset')}, collation: {config.get('collation')}") with connect(**config) as conn: + logger.info(f"Successfully connected to MySQL server version: {conn.get_server_info()}") with conn.cursor() as cursor: cursor.execute(f"SELECT * FROM {table} LIMIT 100") columns = [desc[0] for desc in cursor.description] rows = cursor.fetchall() result = [",".join(map(str, row)) for row in rows] return "\n".join([",".join(columns)] + result) + except Error as e: - logger.error(f"Error reading {uri_str}: {e} | Code: {e.errno}, SQL state: {e.sqlstate}") + logger.error(f"Database error reading resource {uri}: {str(e)}") + logger.error(f"Error code: {e.errno}, SQL state: {e.sqlstate}") raise RuntimeError(f"Database error: {str(e)}") @app.list_tools() async def list_tools() -> list[Tool]: + """List available MySQL tools.""" + logger.info("Listing tools...") return [ Tool( name="execute_sql", @@ -103,50 +119,56 @@ async def list_tools() -> list[Tool]: @app.call_tool() async def call_tool(name: str, arguments: dict) -> list[TextContent]: + """Execute SQL commands.""" config = get_db_config() + logger.info(f"Calling tool: {name} with arguments: {arguments}") if name != "execute_sql": raise ValueError(f"Unknown tool: {name}") query = arguments.get("query") if not query: - raise ValueError("Missing query") + raise ValueError("Query is required") try: + logger.info(f"Connecting to MySQL with charset: {config.get('charset')}, collation: {config.get('collation')}") with connect(**config) as conn: + logger.info(f"Successfully connected to MySQL server version: {conn.get_server_info()}") with conn.cursor() as cursor: cursor.execute(query) - # Result sets (SELECT, SHOW, etc.) - if cursor.description: + # Special handling for SHOW TABLES + if query.strip().upper().startswith("SHOW TABLES"): + tables = cursor.fetchall() + result = ["Tables_in_" + config["database"]] # Header + result.extend([table[0] for table in tables]) + return [TextContent(type="text", text="\n".join(result))] + + # Handle all other queries that return result sets (SELECT, SHOW, DESCRIBE etc.) + elif cursor.description is not None: columns = [desc[0] for desc in cursor.description] try: rows = cursor.fetchall() result = [",".join(map(str, row)) for row in rows] return [TextContent(type="text", text="\n".join([",".join(columns)] + result))] except Error as e: - logger.warning(f"Query executed but fetch failed: {e}") - return [TextContent(type="text", text=f"Fetch error: {e}")] + logger.warning(f"Error fetching results: {str(e)}") + return [TextContent(type="text", text=f"Query executed but error fetching results: {str(e)}")] + + # Non-SELECT queries + else: + conn.commit() + return [TextContent(type="text", text=f"Query executed successfully. Rows affected: {cursor.rowcount}")] - # Non-result queries - conn.commit() - return [TextContent(type="text", text=f"Query executed. Rows affected: {cursor.rowcount}")] except Error as e: - logger.error(f"SQL execution error: {e} | Code: {e.errno}, SQL state: {e.sqlstate}") + logger.error(f"Error executing SQL '{query}': {e}") + logger.error(f"Error code: {e.errno}, SQL state: {e.sqlstate}") return [TextContent(type="text", text=f"Error executing query: {str(e)}")] -# --- Entry point async def main(): - print("Starting MySQL MCP Server...", file=sys.stderr) - config = get_db_config() - print(f"DB Config -> Host: {config['host']} | User: {config['user']} | DB: {config['database']}", file=sys.stderr) - - try: - async with stdio_server() as (reader, writer): - await app.run(reader, writer, app.create_initialization_options()) - except Exception as e: - logger.error(f"Fatal server error: {e}", exc_info=True) - raise + logger.info("Starting MCP STDIO server...") + async with stdio_server() as (reader, writer): + await app.run(reader, writer, app.create_initialization_options()) if __name__ == "__main__": asyncio.run(main()) From 9657edf972c9e0e68e72dfb1e8685931b7986285 Mon Sep 17 00:00:00 2001 From: HuiVoonC <108331037+CHuiV123@users.noreply.github.com> Date: Fri, 1 Aug 2025 16:18:38 +0800 Subject: [PATCH 19/22] Update server.py --- src/mysql_mcp_server/server.py | 52 +++++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/src/mysql_mcp_server/server.py b/src/mysql_mcp_server/server.py index 4f6d9fc..5621e63 100644 --- a/src/mysql_mcp_server/server.py +++ b/src/mysql_mcp_server/server.py @@ -1,4 +1,3 @@ -# server.py (Langflow MCP STDIO-Compatible) import asyncio import logging import os @@ -7,8 +6,8 @@ from mcp.server import Server from mcp.types import Resource, Tool, TextContent from pydantic import AnyUrl -from mcp.server.stdio import stdio_server +# Configure logging # Log to stderr to not interfere with STDIO stream logging.basicConfig( level=logging.INFO, @@ -18,24 +17,34 @@ logger = logging.getLogger("mysql_mcp_server") def get_db_config(): + """Get database configuration from environment variables.""" config = { - "host": "localhost", - "port": 3306, - "user": "root", - "password": "admin", - "database": "langflow", + "host": os.getenv("MYSQL_HOST", "localhost"), + "port": int(os.getenv("MYSQL_PORT", "3306")), + "user": os.getenv("MYSQL_USER"), + "password": os.getenv("MYSQL_PASSWORD"), + "database": os.getenv("MYSQL_DATABASE"), + # Add charset and collation to avoid utf8mb4_0900_ai_ci issues with older MySQL versions + # These can be overridden via environment variables for specific MySQL versions "charset": os.getenv("MYSQL_CHARSET", "utf8mb4"), "collation": os.getenv("MYSQL_COLLATION", "utf8mb4_unicode_ci"), + # Disable autocommit for better transaction control + "autocommit": True, + # Set SQL mode for better compatibility - can be overridden "sql_mode": os.getenv("MYSQL_SQL_MODE", "TRADITIONAL") } + + # Remove None values to let MySQL connector use defaults if not specified config = {k: v for k, v in config.items() if v is not None} if not all([config.get("user"), config.get("password"), config.get("database")]): - logger.error("Missing required database configuration.") + logger.error("Missing required database configuration. Please check environment variables:") + logger.error("MYSQL_USER, MYSQL_PASSWORD, and MYSQL_DATABASE are required") raise ValueError("Missing required database configuration") return config +# Initialize server app = Server("mysql_mcp_server") @app.list_resources() @@ -166,9 +175,30 @@ async def call_tool(name: str, arguments: dict) -> list[TextContent]: return [TextContent(type="text", text=f"Error executing query: {str(e)}")] async def main(): - logger.info("Starting MCP STDIO server...") - async with stdio_server() as (reader, writer): - await app.run(reader, writer, app.create_initialization_options()) + """Main entry point to run the MCP server.""" + from mcp.server.stdio import stdio_server + + # Add additional debug output + print("Starting MySQL MCP server with config:", file=sys.stderr) + config = get_db_config() + print(f"Host: {config['host']}", file=sys.stderr) + print(f"Port: {config['port']}", file=sys.stderr) + print(f"User: {config['user']}", file=sys.stderr) + print(f"Database: {config['database']}", file=sys.stderr) + + logger.info("Starting MySQL MCP server...") + logger.info(f"Database config: {config['host']}/{config['database']} as {config['user']}") + + async with stdio_server() as (read_stream, write_stream): + try: + await app.run( + read_stream, + write_stream, + app.create_initialization_options() + ) + except Exception as e: + logger.error(f"Server error: {str(e)}", exc_info=True) + raise if __name__ == "__main__": asyncio.run(main()) From 2a9c5cd0ae101f0016117a1c0ae6462de09c0aee Mon Sep 17 00:00:00 2001 From: HuiVoonC <108331037+CHuiV123@users.noreply.github.com> Date: Sun, 3 Aug 2025 03:18:33 +0800 Subject: [PATCH 20/22] Update README.md --- README.md | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 95bf03e..9d5e621 100644 --- a/README.md +++ b/README.md @@ -58,17 +58,30 @@ Upon successful server start up, you shall see "mysql_mcp_server - INFO - Starti 1. Go to Langflow setting, look for MCP server. 2. Click **Add MCP Server** -3. Select **STDIO** +3. Select **JSON** 4. Server name= **MySQL_MCP** -5. Command= **python** -6. Arguments= **your directory path of server.py** -7. Add environment variable +5. Copy and paste this config under JSON section ``` -MYSQL_HOST= **YOUR_HOST** -MYSQL_PORT= **YOUR_PORT** -MYSQL_USER= **YOUR_USER_NAME** -MYSQL_PASSWORD= **YOUR_PASSWORD** -MYSQL_DATABASE= **YOUR_DATABASE_NAME** +{ + "mcpServers": { + "mysql": { + "command": "uv", + "args": [ + "--directory", + "path/to/mysql_mcp_server", + "run", + "mysql_mcp_server" + ], + "env": { + "MYSQL_HOST": "localhost", + "MYSQL_PORT": "3306", + "MYSQL_USER": "your_username", + "MYSQL_PASSWORD": "your_password", + "MYSQL_DATABASE": "your_database" + } + } + } +} ``` 8. Click **Add server** 9. Restart Langflow From 482c710a1f49bc68262113b94e74f135002fe13c Mon Sep 17 00:00:00 2001 From: HuiVoonC <108331037+CHuiV123@users.noreply.github.com> Date: Sun, 3 Aug 2025 03:19:06 +0800 Subject: [PATCH 21/22] Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 9d5e621..ef4f84a 100644 --- a/README.md +++ b/README.md @@ -59,8 +59,7 @@ Upon successful server start up, you shall see "mysql_mcp_server - INFO - Starti 1. Go to Langflow setting, look for MCP server. 2. Click **Add MCP Server** 3. Select **JSON** -4. Server name= **MySQL_MCP** -5. Copy and paste this config under JSON section +4. Copy and paste this config under JSON section ``` { "mcpServers": { From 774aecdbf66f205ed2c876b7fc14cd49565b4b34 Mon Sep 17 00:00:00 2001 From: HuiVoonC <108331037+CHuiV123@users.noreply.github.com> Date: Sun, 3 Aug 2025 03:22:57 +0800 Subject: [PATCH 22/22] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ef4f84a..f4b1b24 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ Upon successful server start up, you shall see "mysql_mcp_server - INFO - Starti } ``` 8. Click **Add server** -9. Restart Langflow +9. Restart Langflow (**If necessary) ### Debugging with MCP Inspector