From 28fcb8e68bddb7df45c8fb5658cb209382000e85 Mon Sep 17 00:00:00 2001 From: Kaushik Iska Date: Mon, 17 Feb 2025 18:48:09 -0600 Subject: [PATCH 1/3] Add comprehensive environment configuration handling - Add new ClickHouseConfig class to manage all connection settings - Implement secure defaults for HTTPS and SSL verification - Add detailed documentation for all environment variables - Improve connection error handling and logging - Add .envrc to gitignore --- .gitignore | 1 + README.md | 102 ++++++++++++++++++++++++- mcp_clickhouse/mcp_env.py | 141 +++++++++++++++++++++++++++++++++++ mcp_clickhouse/mcp_server.py | 28 ++++--- 4 files changed, 261 insertions(+), 11 deletions(-) create mode 100644 mcp_clickhouse/mcp_env.py diff --git a/.gitignore b/.gitignore index 61f9fa8..4d65088 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.envrc .ruff_cache/ # Byte-compiled / optimized / DLL files diff --git a/README.md b/README.md index e0f1778..fe4cdf3 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,11 @@ An MCP server for ClickHouse. "CLICKHOUSE_HOST": "", "CLICKHOUSE_PORT": "", "CLICKHOUSE_USER": "", - "CLICKHOUSE_PASSWORD": "" + "CLICKHOUSE_PASSWORD": "", + "CLICKHOUSE_SECURE": "true", + "CLICKHOUSE_VERIFY": "true", + "CLICKHOUSE_CONNECT_TIMEOUT": "30", + "CLICKHOUSE_SEND_RECEIVE_TIMEOUT": "30" } } } @@ -74,7 +78,11 @@ Or, if you'd like to try it out with the [ClickHouse SQL Playground](https://sql "CLICKHOUSE_HOST": "sql-clickhouse.clickhouse.com", "CLICKHOUSE_PORT": "8443", "CLICKHOUSE_USER": "demo", - "CLICKHOUSE_PASSWORD": "" + "CLICKHOUSE_PASSWORD": "", + "CLICKHOUSE_SECURE": "true", + "CLICKHOUSE_VERIFY": "true", + "CLICKHOUSE_CONNECT_TIMEOUT": "30", + "CLICKHOUSE_SEND_RECEIVE_TIMEOUT": "30" } } } @@ -102,3 +110,93 @@ CLICKHOUSE_PASSWORD=clickhouse 3. Run `uv sync` to install the dependencies. To install `uv` follow the instructions [here](https://docs.astral.sh/uv/). Then do `source .venv/bin/activate`. 4. For easy testing, you can run `fastmcp dev mcp_clickhouse/mcp_server.py` to start the MCP server. + +### Environment Variables + +The following environment variables are used to configure the ClickHouse connection: + +#### Required Variables +* `CLICKHOUSE_HOST`: The hostname of your ClickHouse server +* `CLICKHOUSE_USER`: The username for authentication +* `CLICKHOUSE_PASSWORD`: The password for authentication + +#### Optional Variables +* `CLICKHOUSE_PORT`: The port number of your ClickHouse server + - Default: `8443` if HTTPS is enabled, `8123` if disabled + - Usually doesn't need to be set unless using a non-standard port +* `CLICKHOUSE_SECURE`: Enable/disable HTTPS connection + - Default: `"true"` + - Set to `"false"` for non-secure connections +* `CLICKHOUSE_VERIFY`: Enable/disable SSL certificate verification + - Default: `"true"` + - Set to `"false"` to disable certificate verification (not recommended for production) +* `CLICKHOUSE_CONNECT_TIMEOUT`: Connection timeout in seconds + - Default: `"30"` + - Increase this value if you experience connection timeouts +* `CLICKHOUSE_SEND_RECEIVE_TIMEOUT`: Send/receive timeout in seconds + - Default: `"300"` + - Increase this value for long-running queries +* `CLICKHOUSE_DATABASE`: Default database to use + - Default: None (uses server default) + - Set this to automatically connect to a specific database + +#### Example Configurations + +For local development with Docker: +```env +# Required variables +CLICKHOUSE_HOST=localhost +CLICKHOUSE_USER=default +CLICKHOUSE_PASSWORD=clickhouse + +# Optional: Override defaults for local development +CLICKHOUSE_SECURE=false # Uses port 8123 automatically +CLICKHOUSE_VERIFY=false +``` + +For ClickHouse Cloud: +```env +# Required variables +CLICKHOUSE_HOST=your-instance.clickhouse.cloud +CLICKHOUSE_USER=default +CLICKHOUSE_PASSWORD=your-password + +# Optional: These use secure defaults +# CLICKHOUSE_SECURE=true # Uses port 8443 automatically +# CLICKHOUSE_DATABASE=your_database +``` + +For ClickHouse SQL Playground: +```env +CLICKHOUSE_HOST=sql-clickhouse.clickhouse.com +CLICKHOUSE_USER=demo +CLICKHOUSE_PASSWORD= +# Uses secure defaults (HTTPS on port 8443) +``` + +You can set these variables in your environment, in a `.env` file, or in the Claude Desktop configuration: + +```json +{ + "mcpServers": { + "mcp-clickhouse": { + "command": "uv", + "args": [ + "run", + "--with", + "mcp-clickhouse", + "--python", + "3.13", + "mcp-clickhouse" + ], + "env": { + "CLICKHOUSE_HOST": "", + "CLICKHOUSE_USER": "", + "CLICKHOUSE_PASSWORD": "", + "CLICKHOUSE_DATABASE": "" + } + } + } +} +``` + diff --git a/mcp_clickhouse/mcp_env.py b/mcp_clickhouse/mcp_env.py new file mode 100644 index 0000000..080b6b6 --- /dev/null +++ b/mcp_clickhouse/mcp_env.py @@ -0,0 +1,141 @@ +"""Environment configuration for the MCP ClickHouse server. + +This module handles all environment variable configuration with sensible defaults +and type conversion. +""" + +from dataclasses import dataclass +import os +from typing import Optional + + +@dataclass +class ClickHouseConfig: + """Configuration for ClickHouse connection settings. + + This class handles all environment variable configuration with sensible defaults + and type conversion. It provides typed methods for accessing each configuration value. + + Required environment variables: + CLICKHOUSE_HOST: The hostname of the ClickHouse server + CLICKHOUSE_USER: The username for authentication + CLICKHOUSE_PASSWORD: The password for authentication + + Optional environment variables (with defaults): + CLICKHOUSE_PORT: The port number (default: 8443 if secure=True, 8123 if secure=False) + CLICKHOUSE_SECURE: Enable HTTPS (default: true) + CLICKHOUSE_VERIFY: Verify SSL certificates (default: true) + CLICKHOUSE_CONNECT_TIMEOUT: Connection timeout in seconds (default: 30) + CLICKHOUSE_SEND_RECEIVE_TIMEOUT: Send/receive timeout in seconds (default: 300) + CLICKHOUSE_DATABASE: Default database to use (default: None) + """ + + def __init__(self): + """Initialize the configuration from environment variables.""" + self._validate_required_vars() + + @property + def host(self) -> str: + """Get the ClickHouse host.""" + return os.environ["CLICKHOUSE_HOST"] + + @property + def port(self) -> int: + """Get the ClickHouse port. + + Defaults to 8443 if secure=True, 8123 if secure=False. + Can be overridden by CLICKHOUSE_PORT environment variable. + """ + if "CLICKHOUSE_PORT" in os.environ: + return int(os.environ["CLICKHOUSE_PORT"]) + return 8443 if self.secure else 8123 + + @property + def username(self) -> str: + """Get the ClickHouse username.""" + return os.environ["CLICKHOUSE_USER"] + + @property + def password(self) -> str: + """Get the ClickHouse password.""" + return os.environ["CLICKHOUSE_PASSWORD"] + + @property + def database(self) -> Optional[str]: + """Get the default database name if set.""" + return os.getenv("CLICKHOUSE_DATABASE") + + @property + def secure(self) -> bool: + """Get whether HTTPS is enabled. + + Default: True + """ + return os.getenv("CLICKHOUSE_SECURE", "true").lower() == "true" + + @property + def verify(self) -> bool: + """Get whether SSL certificate verification is enabled. + + Default: True + """ + return os.getenv("CLICKHOUSE_VERIFY", "true").lower() == "true" + + @property + def connect_timeout(self) -> int: + """Get the connection timeout in seconds. + + Default: 30 + """ + return int(os.getenv("CLICKHOUSE_CONNECT_TIMEOUT", "30")) + + @property + def send_receive_timeout(self) -> int: + """Get the send/receive timeout in seconds. + + Default: 300 (ClickHouse default) + """ + return int(os.getenv("CLICKHOUSE_SEND_RECEIVE_TIMEOUT", "300")) + + def get_client_config(self) -> dict: + """Get the configuration dictionary for clickhouse_connect client. + + Returns: + dict: Configuration ready to be passed to clickhouse_connect.get_client() + """ + config = { + "host": self.host, + "port": self.port, + "username": self.username, + "password": self.password, + "secure": self.secure, + "verify": self.verify, + "connect_timeout": self.connect_timeout, + "send_receive_timeout": self.send_receive_timeout, + } + + # Add optional database if set + if self.database: + config["database"] = self.database + + return config + + def _validate_required_vars(self) -> None: + """Validate that all required environment variables are set. + + Raises: + ValueError: If any required environment variable is missing. + """ + missing_vars = [] + for var in ["CLICKHOUSE_HOST", "CLICKHOUSE_USER", "CLICKHOUSE_PASSWORD"]: + if var not in os.environ: + missing_vars.append(var) + + if missing_vars: + raise ValueError( + f"Missing required environment variables: {', '.join(missing_vars)}" + ) + + +# Global instance for easy access +config = ClickHouseConfig() \ No newline at end of file diff --git a/mcp_clickhouse/mcp_server.py b/mcp_clickhouse/mcp_server.py index 4cd25f5..c42be15 100644 --- a/mcp_clickhouse/mcp_server.py +++ b/mcp_clickhouse/mcp_server.py @@ -6,6 +6,8 @@ from dotenv import load_dotenv from fastmcp import FastMCP +from mcp_clickhouse.mcp_env import config + MCP_SERVER_NAME = "mcp-clickhouse" # Configure logging @@ -102,13 +104,21 @@ def run_select_query(query: str): def create_clickhouse_client(): - host = os.getenv("CLICKHOUSE_HOST") - port = os.getenv("CLICKHOUSE_PORT") - username = os.getenv("CLICKHOUSE_USER") - logger.info(f"Creating ClickHouse client connection to {host}:{port} as {username}") - return clickhouse_connect.get_client( - host=host, - port=port, - username=username, - password=os.getenv("CLICKHOUSE_PASSWORD"), + client_config = config.get_client_config() + logger.info( + f"Creating ClickHouse client connection to {client_config['host']}:{client_config['port']} " + f"as {client_config['username']} " + f"(secure={client_config['secure']}, verify={client_config['verify']}, " + f"connect_timeout={client_config['connect_timeout']}s, " + f"send_receive_timeout={client_config['send_receive_timeout']}s)" ) + + try: + client = clickhouse_connect.get_client(**client_config) + # Test the connection + version = client.server_version + logger.info(f"Successfully connected to ClickHouse server version {version}") + return client + except Exception as e: + logger.error(f"Failed to connect to ClickHouse: {str(e)}") + raise From 6543582e384c68cd76e4f2a64b46a4c3a28b92a6 Mon Sep 17 00:00:00 2001 From: Kaushik Iska Date: Mon, 17 Feb 2025 18:50:30 -0600 Subject: [PATCH 2/3] fix ci --- .github/workflows/ci.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 603ab8e..8240da8 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -36,6 +36,8 @@ jobs: CLICKHOUSE_PORT: "8123" CLICKHOUSE_USER: "default" CLICKHOUSE_PASSWORD: "" + CLICKHOUSE_SECURE: "false" + CLICKHOUSE_VERIFY: "false" run: | uv run pytest tests From 7aad8fea7fc69a0edb20b227e2d965a8a64fcf64 Mon Sep 17 00:00:00 2001 From: Kaushik Iska Date: Mon, 17 Feb 2025 18:52:31 -0600 Subject: [PATCH 3/3] fix lint --- mcp_clickhouse/mcp_server.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mcp_clickhouse/mcp_server.py b/mcp_clickhouse/mcp_server.py index c42be15..1b799a6 100644 --- a/mcp_clickhouse/mcp_server.py +++ b/mcp_clickhouse/mcp_server.py @@ -1,5 +1,4 @@ import logging -import os from typing import Sequence import clickhouse_connect