Skip to content

Commit fecf57d

Browse files
authored
Add comprehensive environment configuration handling (#11)
- 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
1 parent 4b144c9 commit fecf57d

File tree

5 files changed

+263
-12
lines changed

5 files changed

+263
-12
lines changed

.github/workflows/ci.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ jobs:
3636
CLICKHOUSE_PORT: "8123"
3737
CLICKHOUSE_USER: "default"
3838
CLICKHOUSE_PASSWORD: ""
39+
CLICKHOUSE_SECURE: "false"
40+
CLICKHOUSE_VERIFY: "false"
3941
run: |
4042
uv run pytest tests
4143

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
.envrc
12
.ruff_cache/
23

34
# Byte-compiled / optimized / DLL files

README.md

+100-2
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,11 @@ An MCP server for ClickHouse.
4646
"CLICKHOUSE_HOST": "<clickhouse-host>",
4747
"CLICKHOUSE_PORT": "<clickhouse-port>",
4848
"CLICKHOUSE_USER": "<clickhouse-user>",
49-
"CLICKHOUSE_PASSWORD": "<clickhouse-password>"
49+
"CLICKHOUSE_PASSWORD": "<clickhouse-password>",
50+
"CLICKHOUSE_SECURE": "true",
51+
"CLICKHOUSE_VERIFY": "true",
52+
"CLICKHOUSE_CONNECT_TIMEOUT": "30",
53+
"CLICKHOUSE_SEND_RECEIVE_TIMEOUT": "30"
5054
}
5155
}
5256
}
@@ -74,7 +78,11 @@ Or, if you'd like to try it out with the [ClickHouse SQL Playground](https://sql
7478
"CLICKHOUSE_HOST": "sql-clickhouse.clickhouse.com",
7579
"CLICKHOUSE_PORT": "8443",
7680
"CLICKHOUSE_USER": "demo",
77-
"CLICKHOUSE_PASSWORD": ""
81+
"CLICKHOUSE_PASSWORD": "",
82+
"CLICKHOUSE_SECURE": "true",
83+
"CLICKHOUSE_VERIFY": "true",
84+
"CLICKHOUSE_CONNECT_TIMEOUT": "30",
85+
"CLICKHOUSE_SEND_RECEIVE_TIMEOUT": "30"
7886
}
7987
}
8088
}
@@ -102,3 +110,93 @@ CLICKHOUSE_PASSWORD=clickhouse
102110
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`.
103111

104112
4. For easy testing, you can run `fastmcp dev mcp_clickhouse/mcp_server.py` to start the MCP server.
113+
114+
### Environment Variables
115+
116+
The following environment variables are used to configure the ClickHouse connection:
117+
118+
#### Required Variables
119+
* `CLICKHOUSE_HOST`: The hostname of your ClickHouse server
120+
* `CLICKHOUSE_USER`: The username for authentication
121+
* `CLICKHOUSE_PASSWORD`: The password for authentication
122+
123+
#### Optional Variables
124+
* `CLICKHOUSE_PORT`: The port number of your ClickHouse server
125+
- Default: `8443` if HTTPS is enabled, `8123` if disabled
126+
- Usually doesn't need to be set unless using a non-standard port
127+
* `CLICKHOUSE_SECURE`: Enable/disable HTTPS connection
128+
- Default: `"true"`
129+
- Set to `"false"` for non-secure connections
130+
* `CLICKHOUSE_VERIFY`: Enable/disable SSL certificate verification
131+
- Default: `"true"`
132+
- Set to `"false"` to disable certificate verification (not recommended for production)
133+
* `CLICKHOUSE_CONNECT_TIMEOUT`: Connection timeout in seconds
134+
- Default: `"30"`
135+
- Increase this value if you experience connection timeouts
136+
* `CLICKHOUSE_SEND_RECEIVE_TIMEOUT`: Send/receive timeout in seconds
137+
- Default: `"300"`
138+
- Increase this value for long-running queries
139+
* `CLICKHOUSE_DATABASE`: Default database to use
140+
- Default: None (uses server default)
141+
- Set this to automatically connect to a specific database
142+
143+
#### Example Configurations
144+
145+
For local development with Docker:
146+
```env
147+
# Required variables
148+
CLICKHOUSE_HOST=localhost
149+
CLICKHOUSE_USER=default
150+
CLICKHOUSE_PASSWORD=clickhouse
151+
152+
# Optional: Override defaults for local development
153+
CLICKHOUSE_SECURE=false # Uses port 8123 automatically
154+
CLICKHOUSE_VERIFY=false
155+
```
156+
157+
For ClickHouse Cloud:
158+
```env
159+
# Required variables
160+
CLICKHOUSE_HOST=your-instance.clickhouse.cloud
161+
CLICKHOUSE_USER=default
162+
CLICKHOUSE_PASSWORD=your-password
163+
164+
# Optional: These use secure defaults
165+
# CLICKHOUSE_SECURE=true # Uses port 8443 automatically
166+
# CLICKHOUSE_DATABASE=your_database
167+
```
168+
169+
For ClickHouse SQL Playground:
170+
```env
171+
CLICKHOUSE_HOST=sql-clickhouse.clickhouse.com
172+
CLICKHOUSE_USER=demo
173+
CLICKHOUSE_PASSWORD=
174+
# Uses secure defaults (HTTPS on port 8443)
175+
```
176+
177+
You can set these variables in your environment, in a `.env` file, or in the Claude Desktop configuration:
178+
179+
```json
180+
{
181+
"mcpServers": {
182+
"mcp-clickhouse": {
183+
"command": "uv",
184+
"args": [
185+
"run",
186+
"--with",
187+
"mcp-clickhouse",
188+
"--python",
189+
"3.13",
190+
"mcp-clickhouse"
191+
],
192+
"env": {
193+
"CLICKHOUSE_HOST": "<clickhouse-host>",
194+
"CLICKHOUSE_USER": "<clickhouse-user>",
195+
"CLICKHOUSE_PASSWORD": "<clickhouse-password>",
196+
"CLICKHOUSE_DATABASE": "<optional-database>"
197+
}
198+
}
199+
}
200+
}
201+
```
202+

mcp_clickhouse/mcp_env.py

+141
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
"""Environment configuration for the MCP ClickHouse server.
2+
3+
This module handles all environment variable configuration with sensible defaults
4+
and type conversion.
5+
"""
6+
7+
from dataclasses import dataclass
8+
import os
9+
from typing import Optional
10+
11+
12+
@dataclass
13+
class ClickHouseConfig:
14+
"""Configuration for ClickHouse connection settings.
15+
16+
This class handles all environment variable configuration with sensible defaults
17+
and type conversion. It provides typed methods for accessing each configuration value.
18+
19+
Required environment variables:
20+
CLICKHOUSE_HOST: The hostname of the ClickHouse server
21+
CLICKHOUSE_USER: The username for authentication
22+
CLICKHOUSE_PASSWORD: The password for authentication
23+
24+
Optional environment variables (with defaults):
25+
CLICKHOUSE_PORT: The port number (default: 8443 if secure=True, 8123 if secure=False)
26+
CLICKHOUSE_SECURE: Enable HTTPS (default: true)
27+
CLICKHOUSE_VERIFY: Verify SSL certificates (default: true)
28+
CLICKHOUSE_CONNECT_TIMEOUT: Connection timeout in seconds (default: 30)
29+
CLICKHOUSE_SEND_RECEIVE_TIMEOUT: Send/receive timeout in seconds (default: 300)
30+
CLICKHOUSE_DATABASE: Default database to use (default: None)
31+
"""
32+
33+
def __init__(self):
34+
"""Initialize the configuration from environment variables."""
35+
self._validate_required_vars()
36+
37+
@property
38+
def host(self) -> str:
39+
"""Get the ClickHouse host."""
40+
return os.environ["CLICKHOUSE_HOST"]
41+
42+
@property
43+
def port(self) -> int:
44+
"""Get the ClickHouse port.
45+
46+
Defaults to 8443 if secure=True, 8123 if secure=False.
47+
Can be overridden by CLICKHOUSE_PORT environment variable.
48+
"""
49+
if "CLICKHOUSE_PORT" in os.environ:
50+
return int(os.environ["CLICKHOUSE_PORT"])
51+
return 8443 if self.secure else 8123
52+
53+
@property
54+
def username(self) -> str:
55+
"""Get the ClickHouse username."""
56+
return os.environ["CLICKHOUSE_USER"]
57+
58+
@property
59+
def password(self) -> str:
60+
"""Get the ClickHouse password."""
61+
return os.environ["CLICKHOUSE_PASSWORD"]
62+
63+
@property
64+
def database(self) -> Optional[str]:
65+
"""Get the default database name if set."""
66+
return os.getenv("CLICKHOUSE_DATABASE")
67+
68+
@property
69+
def secure(self) -> bool:
70+
"""Get whether HTTPS is enabled.
71+
72+
Default: True
73+
"""
74+
return os.getenv("CLICKHOUSE_SECURE", "true").lower() == "true"
75+
76+
@property
77+
def verify(self) -> bool:
78+
"""Get whether SSL certificate verification is enabled.
79+
80+
Default: True
81+
"""
82+
return os.getenv("CLICKHOUSE_VERIFY", "true").lower() == "true"
83+
84+
@property
85+
def connect_timeout(self) -> int:
86+
"""Get the connection timeout in seconds.
87+
88+
Default: 30
89+
"""
90+
return int(os.getenv("CLICKHOUSE_CONNECT_TIMEOUT", "30"))
91+
92+
@property
93+
def send_receive_timeout(self) -> int:
94+
"""Get the send/receive timeout in seconds.
95+
96+
Default: 300 (ClickHouse default)
97+
"""
98+
return int(os.getenv("CLICKHOUSE_SEND_RECEIVE_TIMEOUT", "300"))
99+
100+
def get_client_config(self) -> dict:
101+
"""Get the configuration dictionary for clickhouse_connect client.
102+
103+
Returns:
104+
dict: Configuration ready to be passed to clickhouse_connect.get_client()
105+
"""
106+
config = {
107+
"host": self.host,
108+
"port": self.port,
109+
"username": self.username,
110+
"password": self.password,
111+
"secure": self.secure,
112+
"verify": self.verify,
113+
"connect_timeout": self.connect_timeout,
114+
"send_receive_timeout": self.send_receive_timeout,
115+
}
116+
117+
# Add optional database if set
118+
if self.database:
119+
config["database"] = self.database
120+
121+
return config
122+
123+
def _validate_required_vars(self) -> None:
124+
"""Validate that all required environment variables are set.
125+
126+
Raises:
127+
ValueError: If any required environment variable is missing.
128+
"""
129+
missing_vars = []
130+
for var in ["CLICKHOUSE_HOST", "CLICKHOUSE_USER", "CLICKHOUSE_PASSWORD"]:
131+
if var not in os.environ:
132+
missing_vars.append(var)
133+
134+
if missing_vars:
135+
raise ValueError(
136+
f"Missing required environment variables: {', '.join(missing_vars)}"
137+
)
138+
139+
140+
# Global instance for easy access
141+
config = ClickHouseConfig()

mcp_clickhouse/mcp_server.py

+19-10
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import logging
2-
import os
32
from typing import Sequence
43

54
import clickhouse_connect
65
from dotenv import load_dotenv
76
from fastmcp import FastMCP
87

8+
from mcp_clickhouse.mcp_env import config
9+
910
MCP_SERVER_NAME = "mcp-clickhouse"
1011

1112
# Configure logging
@@ -102,13 +103,21 @@ def run_select_query(query: str):
102103

103104

104105
def create_clickhouse_client():
105-
host = os.getenv("CLICKHOUSE_HOST")
106-
port = os.getenv("CLICKHOUSE_PORT")
107-
username = os.getenv("CLICKHOUSE_USER")
108-
logger.info(f"Creating ClickHouse client connection to {host}:{port} as {username}")
109-
return clickhouse_connect.get_client(
110-
host=host,
111-
port=port,
112-
username=username,
113-
password=os.getenv("CLICKHOUSE_PASSWORD"),
106+
client_config = config.get_client_config()
107+
logger.info(
108+
f"Creating ClickHouse client connection to {client_config['host']}:{client_config['port']} "
109+
f"as {client_config['username']} "
110+
f"(secure={client_config['secure']}, verify={client_config['verify']}, "
111+
f"connect_timeout={client_config['connect_timeout']}s, "
112+
f"send_receive_timeout={client_config['send_receive_timeout']}s)"
114113
)
114+
115+
try:
116+
client = clickhouse_connect.get_client(**client_config)
117+
# Test the connection
118+
version = client.server_version
119+
logger.info(f"Successfully connected to ClickHouse server version {version}")
120+
return client
121+
except Exception as e:
122+
logger.error(f"Failed to connect to ClickHouse: {str(e)}")
123+
raise

0 commit comments

Comments
 (0)