mcp-hydrolix 0.1.3__py3-none-any.whl → 0.1.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
mcp_hydrolix/main.py CHANGED
@@ -1,8 +1,20 @@
1
1
  from .mcp_server import mcp
2
+ from .mcp_env import get_config, TransportType
2
3
 
3
4
 
4
5
  def main():
5
- mcp.run()
6
+ config = get_config()
7
+ transport = config.mcp_server_transport
8
+
9
+ # For HTTP and SSE transports, we need to specify host and port
10
+ http_transports = [TransportType.HTTP.value, TransportType.SSE.value]
11
+ if transport in http_transports:
12
+ # Use the configured bind host (defaults to 127.0.0.1, can be set to 0.0.0.0)
13
+ # and bind port (defaults to 8000)
14
+ mcp.run(transport=transport, host=config.mcp_bind_host, port=config.mcp_bind_port)
15
+ else:
16
+ # For stdio transport, no host or port is needed
17
+ mcp.run(transport=transport)
6
18
 
7
19
 
8
20
  if __name__ == "__main__":
mcp_hydrolix/mcp_env.py CHANGED
@@ -7,6 +7,20 @@ and type conversion.
7
7
  from dataclasses import dataclass
8
8
  import os
9
9
  from typing import Optional
10
+ from enum import Enum
11
+
12
+
13
+ class TransportType(str, Enum):
14
+ """Supported MCP server transport types."""
15
+
16
+ STDIO = "stdio"
17
+ HTTP = "http"
18
+ SSE = "sse"
19
+
20
+ @classmethod
21
+ def values(cls) -> list[str]:
22
+ """Get all valid transport values."""
23
+ return [transport.value for transport in cls]
10
24
 
11
25
 
12
26
  @dataclass
@@ -18,15 +32,20 @@ class HydrolixConfig:
18
32
 
19
33
  Required environment variables:
20
34
  HYDROLIX_HOST: The hostname of the Hydrolix server
21
- HYDROLIX_USER: The username for authentication
22
- HYDROLIX_PASSWORD: The password for authentication
23
35
 
24
36
  Optional environment variables (with defaults):
37
+ HYDROLIX_TOKEN: Service account token to the Hydrolix Server (this or user+password is required)
38
+ HYDROLIX_USER: The username for authentication (this or token is required)
39
+ HYDROLIX_PASSWORD: The password for authentication (this or token is required)
25
40
  HYDROLIX_PORT: The port number (default: 8088)
26
41
  HYDROLIX_VERIFY: Verify SSL certificates (default: true)
27
42
  HYDROLIX_CONNECT_TIMEOUT: Connection timeout in seconds (default: 30)
28
43
  HYDROLIX_SEND_RECEIVE_TIMEOUT: Send/receive timeout in seconds (default: 300)
29
44
  HYDROLIX_DATABASE: Default database to use (default: None)
45
+ HYDROLIX_PROXY_PATH: Path to be added to the host URL. For instance, for servers behind an HTTP proxy (default: None)
46
+ HYDROLIX_MCP_SERVER_TRANSPORT: MCP server transport method - "stdio", "http", or "sse" (default: stdio)
47
+ HYDROLIX_MCP_BIND_HOST: Host to bind the MCP server to when using HTTP or SSE transport (default: 127.0.0.1)
48
+ HYDROLIX_MCP_BIND_PORT: Port to bind the MCP server to when using HTTP or SSE transport (default: 8000)
30
49
  """
31
50
 
32
51
  def __init__(self):
@@ -49,15 +68,39 @@ class HydrolixConfig:
49
68
  return int(os.environ["HYDROLIX_PORT"])
50
69
  return 8088
51
70
 
71
+ @property
72
+ def service_account(self) -> bool:
73
+ """Determine if service account is enabled
74
+
75
+ Defaults to false.
76
+ Can be overridden if HYDROLIX_TOKEN environment variable.
77
+ """
78
+ return "HYDROLIX_TOKEN" in os.environ
79
+
80
+ @property
81
+ def service_account_token(self) -> str:
82
+ """Get the service account token
83
+
84
+ Defaults to None.
85
+ Can be overridden if HYDROLIX_TOKEN environment variable.
86
+ """
87
+ if "HYDROLIX_TOKEN" in os.environ:
88
+ return os.environ["HYDROLIX_TOKEN"]
89
+ return None
90
+
52
91
  @property
53
92
  def username(self) -> str:
54
93
  """Get the Hydrolix username."""
55
- return os.environ["HYDROLIX_USER"]
94
+ if "HYDROLIX_USER" in os.environ:
95
+ return os.environ["HYDROLIX_USER"]
96
+ return None
56
97
 
57
98
  @property
58
99
  def password(self) -> str:
59
100
  """Get the Hydrolix password."""
60
- return os.environ["HYDROLIX_PASSWORD"]
101
+ if "HYDROLIX_PASSWORD" in os.environ:
102
+ return os.environ["HYDROLIX_PASSWORD"]
103
+ return None
61
104
 
62
105
  @property
63
106
  def database(self) -> Optional[str]:
@@ -88,6 +131,43 @@ class HydrolixConfig:
88
131
  """
89
132
  return int(os.getenv("HYDROLIX_SEND_RECEIVE_TIMEOUT", "300"))
90
133
 
134
+ @property
135
+ def proxy_path(self) -> str:
136
+ return os.getenv("HYDROLIX_PROXY_PATH")
137
+
138
+ @property
139
+ def mcp_server_transport(self) -> str:
140
+ """Get the MCP server transport method.
141
+
142
+ Valid options: "stdio", "http", "sse"
143
+ Default: "stdio"
144
+ """
145
+ transport = os.getenv("HYDROLIX_MCP_SERVER_TRANSPORT", TransportType.STDIO.value).lower()
146
+
147
+ # Validate transport type
148
+ if transport not in TransportType.values():
149
+ valid_options = ", ".join(f'"{t}"' for t in TransportType.values())
150
+ raise ValueError(f"Invalid transport '{transport}'. Valid options: {valid_options}")
151
+ return transport
152
+
153
+ @property
154
+ def mcp_bind_host(self) -> str:
155
+ """Get the host to bind the MCP server to.
156
+
157
+ Only used when transport is "http" or "sse".
158
+ Default: "127.0.0.1"
159
+ """
160
+ return os.getenv("HYDROLIX_MCP_BIND_HOST", "127.0.0.1")
161
+
162
+ @property
163
+ def mcp_bind_port(self) -> int:
164
+ """Get the port to bind the MCP server to.
165
+
166
+ Only used when transport is "http" or "sse".
167
+ Default: 8000
168
+ """
169
+ return int(os.getenv("HYDROLIX_MCP_BIND_PORT", "8000"))
170
+
91
171
  def get_client_config(self) -> dict:
92
172
  """Get the configuration dictionary for clickhouse_connect client.
93
173
 
@@ -97,8 +177,6 @@ class HydrolixConfig:
97
177
  config = {
98
178
  "host": self.host,
99
179
  "port": self.port,
100
- "username": self.username,
101
- "password": self.password,
102
180
  "secure": True,
103
181
  "verify": self.verify,
104
182
  "connect_timeout": self.connect_timeout,
@@ -110,6 +188,15 @@ class HydrolixConfig:
110
188
  if self.database:
111
189
  config["database"] = self.database
112
190
 
191
+ if self.proxy_path:
192
+ config["proxy_path"] = self.proxy_path
193
+
194
+ if self.service_account:
195
+ config["access_token"] = self.service_account_token
196
+ else:
197
+ config["username"] = self.username
198
+ config["password"] = self.password
199
+
113
200
  return config
114
201
 
115
202
  def _validate_required_vars(self) -> None:
@@ -119,7 +206,11 @@ class HydrolixConfig:
119
206
  ValueError: If any required environment variable is missing.
120
207
  """
121
208
  missing_vars = []
122
- for var in ["HYDROLIX_HOST", "HYDROLIX_USER", "HYDROLIX_PASSWORD"]:
209
+ if self.service_account:
210
+ required_vars = ["HYDROLIX_HOST", "HYDROLIX_TOKEN"]
211
+ else:
212
+ required_vars = ["HYDROLIX_HOST", "HYDROLIX_USER", "HYDROLIX_PASSWORD"]
213
+ for var in required_vars:
123
214
  if var not in os.environ:
124
215
  missing_vars.append(var)
125
216
 
@@ -1,15 +1,53 @@
1
1
  import logging
2
- from typing import Sequence
2
+ import json
3
+ from typing import Optional, List, Any
3
4
  import concurrent.futures
4
5
  import atexit
5
6
 
6
7
  import clickhouse_connect
7
- from clickhouse_connect.driver.binding import quote_identifier, format_query_value
8
+ from clickhouse_connect.driver.binding import format_query_value
8
9
  from dotenv import load_dotenv
9
- from mcp.server.fastmcp import FastMCP
10
+ from fastmcp import FastMCP
11
+ from fastmcp.exceptions import ToolError
12
+ from dataclasses import dataclass, field, asdict, is_dataclass
13
+ from starlette.requests import Request
14
+ from starlette.responses import PlainTextResponse
10
15
 
11
16
  from mcp_hydrolix.mcp_env import get_config
12
17
 
18
+
19
+ @dataclass
20
+ class Column:
21
+ database: str
22
+ table: str
23
+ name: str
24
+ column_type: str
25
+ default_kind: Optional[str]
26
+ default_expression: Optional[str]
27
+ comment: Optional[str]
28
+
29
+
30
+ @dataclass
31
+ class Table:
32
+ database: str
33
+ name: str
34
+ engine: str
35
+ create_table_query: str
36
+ dependencies_database: str
37
+ dependencies_table: str
38
+ engine_full: str
39
+ sorting_key: str
40
+ primary_key: str
41
+ total_rows: int
42
+ total_bytes: int
43
+ total_bytes_uncompressed: int
44
+ parts: int
45
+ active_parts: int
46
+ total_marks: int
47
+ comment: Optional[str] = None
48
+ columns: List[Column] = field(default_factory=list)
49
+
50
+
13
51
  MCP_SERVER_NAME = "mcp-hydrolix"
14
52
 
15
53
  # Configure logging
@@ -24,14 +62,48 @@ SELECT_QUERY_TIMEOUT_SECS = 30
24
62
 
25
63
  load_dotenv()
26
64
 
27
- deps = [
28
- "clickhouse-connect",
29
- "python-dotenv",
30
- "uvicorn",
31
- "pip-system-certs",
32
- ]
65
+ mcp = FastMCP(
66
+ name=MCP_SERVER_NAME,
67
+ dependencies=[
68
+ "clickhouse-connect",
69
+ "python-dotenv",
70
+ "pip-system-certs",
71
+ ],
72
+ )
73
+
74
+
75
+ @mcp.custom_route("/health", methods=["GET"])
76
+ async def health_check(request: Request) -> PlainTextResponse:
77
+ """Health check endpoint for monitoring server status.
78
+
79
+ Returns OK if the server is running and can connect to Hydrolix.
80
+ """
81
+ try:
82
+ # Try to create a client connection to verify query-head connectivity
83
+ client = create_hydrolix_client()
84
+ version = client.server_version
85
+ return PlainTextResponse(f"OK - Connected to Hydrolix compatible with ClickHouse {version}")
86
+ except Exception as e:
87
+ # Return 503 Service Unavailable if we can't connect to Hydrolix
88
+ return PlainTextResponse(f"ERROR - Cannot connect to Hydrolix: {str(e)}", status_code=503)
89
+
90
+
91
+ def result_to_table(query_columns, result) -> List[Table]:
92
+ return [Table(**dict(zip(query_columns, row))) for row in result]
33
93
 
34
- mcp = FastMCP(MCP_SERVER_NAME, dependencies=deps)
94
+
95
+ def result_to_column(query_columns, result) -> List[Column]:
96
+ return [Column(**dict(zip(query_columns, row))) for row in result]
97
+
98
+
99
+ def to_json(obj: Any) -> str:
100
+ if is_dataclass(obj):
101
+ return json.dumps(asdict(obj), default=to_json)
102
+ elif isinstance(obj, list):
103
+ return [to_json(item) for item in obj]
104
+ elif isinstance(obj, dict):
105
+ return {key: to_json(value) for key, value in obj.items()}
106
+ return obj
35
107
 
36
108
 
37
109
  @mcp.tool()
@@ -40,81 +112,48 @@ def list_databases():
40
112
  logger.info("Listing all databases")
41
113
  client = create_hydrolix_client()
42
114
  result = client.command("SHOW DATABASES")
43
- logger.info(f"Found {len(result) if isinstance(result, list) else 1} databases")
44
- return result
115
+
116
+ # Convert newline-separated string to list and trim whitespace
117
+ if isinstance(result, str):
118
+ databases = [db.strip() for db in result.strip().split("\n")]
119
+ else:
120
+ databases = [result]
121
+
122
+ logger.info(f"Found {len(databases)} databases")
123
+ return json.dumps(databases)
45
124
 
46
125
 
47
126
  @mcp.tool()
48
- def list_tables(database: str, like: str = None):
49
- """List available Hydrolix tables in a database"""
127
+ def list_tables(database: str, like: Optional[str] = None, not_like: Optional[str] = None):
128
+ """List available Hydrolix tables in a database, including schema, comment,
129
+ row count, and column count."""
50
130
  logger.info(f"Listing tables in database '{database}'")
51
131
  client = create_hydrolix_client()
52
- query = f"SHOW TABLES FROM {quote_identifier(database)}"
132
+ query = f"SELECT database, name, engine, create_table_query, dependencies_database, dependencies_table, engine_full, sorting_key, primary_key, total_rows, total_bytes, total_bytes_uncompressed, parts, active_parts, total_marks, comment FROM system.tables WHERE database = {format_query_value(database)}"
53
133
  if like:
54
- query += f" LIKE {format_query_value(like)}"
55
- result = client.command(query)
134
+ query += f" AND name LIKE {format_query_value(like)}"
56
135
 
57
- # Get all table comments in one query
58
- table_comments_query = (
59
- f"SELECT name, comment, primary_key FROM system.tables WHERE database = {format_query_value(database)} and engine = 'TurbineStorage' and total_rows > 0"
60
- )
61
- table_comments_result = client.query(table_comments_query)
62
- table_comments = {row[0]: row[1] for row in table_comments_result.result_rows}
63
- primary_keys = {row[0]: row[2] for row in table_comments_result.result_rows}
64
-
65
- # Get all column comments in one query
66
- column_comments_query = f"SELECT table, name, comment FROM system.columns WHERE database = {format_query_value(database)}"
67
- column_comments_result = client.query(column_comments_query)
68
- column_comments = {}
69
- for row in column_comments_result.result_rows:
70
- table, col_name, comment = row
71
- if table not in column_comments:
72
- column_comments[table] = {}
73
- column_comments[table][col_name] = comment
74
-
75
- def get_table_info(table):
76
- logger.info(f"Getting schema info for table {database}.{table}")
77
- schema_query = f"DESCRIBE TABLE {quote_identifier(database)}.{quote_identifier(table)}"
78
- schema_result = client.query(schema_query)
79
-
80
- columns = []
81
- column_names = schema_result.column_names
82
- for row in schema_result.result_rows:
83
- column_dict = {}
84
- for i, col_name in enumerate(column_names):
85
- column_dict[col_name] = row[i]
86
- # Add comment from our pre-fetched comments
87
- if table in column_comments and column_dict["name"] in column_comments[table]:
88
- column_dict["comment"] = column_comments[table][column_dict["name"]]
89
- else:
90
- column_dict["comment"] = None
91
- columns.append(column_dict)
92
-
93
- create_table_query = f"SHOW CREATE TABLE {database}.`{table}`"
94
- create_table_result = client.command(create_table_query)
95
-
96
- return {
97
- "database": database,
98
- "name": table,
99
- "comment": table_comments.get(table),
100
- "columns": columns,
101
- "create_table_query": create_table_result,
102
- "primary_key": primary_keys.get(table)
103
- }
104
-
105
- tables = []
106
- if isinstance(result, str):
107
- # Single table result
108
- for table in (t.strip() for t in result.split()):
109
- if table:
110
- tables.append(get_table_info(table))
111
- elif isinstance(result, Sequence):
112
- # Multiple table results
113
- for table in result:
114
- tables.append(get_table_info(table))
136
+ if not_like:
137
+ query += f" AND name NOT LIKE {format_query_value(not_like)}"
138
+
139
+ result = client.query(query)
140
+
141
+ # Deserialize result as Table dataclass instances
142
+ tables = result_to_table(result.column_names, result.result_rows)
143
+
144
+ for table in tables:
145
+ column_data_query = f"SELECT database, table, name, type AS column_type, default_kind, default_expression, comment FROM system.columns WHERE database = {format_query_value(database)} AND table = {format_query_value(table.name)}"
146
+ column_data_query_result = client.query(column_data_query)
147
+ table.columns = [
148
+ c
149
+ for c in result_to_column(
150
+ column_data_query_result.column_names,
151
+ column_data_query_result.result_rows,
152
+ )
153
+ ]
115
154
 
116
155
  logger.info(f"Found {len(tables)} tables")
117
- return tables
156
+ return [asdict(table) for table in tables]
118
157
 
119
158
 
120
159
  def execute_query(query: str):
@@ -131,20 +170,11 @@ def execute_query(query: str):
131
170
  "hdx_query_admin_comment": f"User: {MCP_SERVER_NAME}",
132
171
  },
133
172
  )
134
- column_names = res.column_names
135
- rows = []
136
- for row in res.result_rows:
137
- row_dict = {}
138
- for i, col_name in enumerate(column_names):
139
- row_dict[col_name] = row[i]
140
- rows.append(row_dict)
141
- logger.info(f"Query returned {len(rows)} rows")
142
- return rows
173
+ logger.info(f"Query returned {len(res.result_rows)} rows")
174
+ return {"columns": res.column_names, "rows": res.result_rows}
143
175
  except Exception as err:
144
176
  logger.error(f"Error executing query: {err}")
145
- # Return a structured dictionary rather than a string to ensure proper serialization
146
- # by the MCP protocol. String responses for errors can cause BrokenResourceError.
147
- return {"error": str(err)}
177
+ raise ToolError(f"Query execution failed: {str(err)}")
148
178
 
149
179
 
150
180
  @mcp.tool()
@@ -196,28 +226,32 @@ def run_select_query(query: str):
196
226
  logger.warning(f"Query failed: {result['error']}")
197
227
  # MCP requires structured responses; string error messages can cause
198
228
  # serialization issues leading to BrokenResourceError
199
- return {"status": "error", "message": f"Query failed: {result['error']}"}
229
+ return {
230
+ "status": "error",
231
+ "message": f"Query failed: {result['error']}",
232
+ }
200
233
  return result
201
234
  except concurrent.futures.TimeoutError:
202
235
  logger.warning(f"Query timed out after {SELECT_QUERY_TIMEOUT_SECS} seconds: {query}")
203
236
  future.cancel()
204
- # Return a properly structured response for timeout errors
205
- return {
206
- "status": "error",
207
- "message": f"Query timed out after {SELECT_QUERY_TIMEOUT_SECS} seconds",
208
- }
237
+ raise ToolError(f"Query timed out after {SELECT_QUERY_TIMEOUT_SECS} seconds")
238
+ except ToolError:
239
+ raise
209
240
  except Exception as e:
210
241
  logger.error(f"Unexpected error in run_select_query: {str(e)}")
211
- # Catch all other exceptions and return them in a structured format
212
- # to prevent MCP serialization failures
213
- return {"status": "error", "message": f"Unexpected error: {str(e)}"}
242
+ raise RuntimeError(f"Unexpected error during query execution: {str(e)}")
214
243
 
215
244
 
216
245
  def create_hydrolix_client():
217
246
  client_config = get_config().get_client_config()
247
+ auth_info = (
248
+ f"as {client_config['username']}"
249
+ if "username" in client_config
250
+ else "using service account token"
251
+ )
218
252
  logger.info(
219
253
  f"Creating Hydrolix client connection to {client_config['host']}:{client_config['port']} "
220
- f"as {client_config['username']} "
254
+ f"{auth_info} "
221
255
  f"(secure={client_config['secure']}, verify={client_config['verify']}, "
222
256
  f"connect_timeout={client_config['connect_timeout']}s, "
223
257
  f"send_receive_timeout={client_config['send_receive_timeout']}s)"
@@ -227,7 +261,7 @@ def create_hydrolix_client():
227
261
  client = clickhouse_connect.get_client(**client_config)
228
262
  # Test the connection
229
263
  version = client.server_version
230
- logger.info(f"Successfully connected to Hydrolix server version {version}")
264
+ logger.info(f"Successfully connected to Hydrolix compatible with ClickHouse {version}")
231
265
  return client
232
266
  except Exception as e:
233
267
  logger.error(f"Failed to connect to Hydrolix: {str(e)}")
@@ -1,22 +1,24 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-hydrolix
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: An MCP server for Hydrolix.
5
5
  Project-URL: Home, https://github.com/hydrolix/mcp-hydrolix
6
6
  License-Expression: Apache-2.0
7
7
  License-File: LICENSE
8
8
  Requires-Python: >=3.13
9
9
  Requires-Dist: clickhouse-connect>=0.8.16
10
- Requires-Dist: mcp[cli]>=1.3.0
10
+ Requires-Dist: fastmcp>=2.0.0
11
11
  Requires-Dist: pip-system-certs>=4.0
12
12
  Requires-Dist: python-dotenv>=1.0.1
13
- Requires-Dist: uvicorn>=0.34.0
14
13
  Provides-Extra: dev
14
+ Requires-Dist: pre-commit; extra == 'dev'
15
15
  Requires-Dist: pytest; extra == 'dev'
16
+ Requires-Dist: pytest-asyncio; extra == 'dev'
16
17
  Requires-Dist: ruff; extra == 'dev'
17
18
  Description-Content-Type: text/markdown
18
19
 
19
20
  # Hydrolix MCP Server
21
+
20
22
  [![PyPI - Version](https://img.shields.io/pypi/v/mcp-hydrolix)](https://pypi.org/project/mcp-hydrolix)
21
23
 
22
24
  An MCP server for Hydrolix.
@@ -24,16 +26,16 @@ An MCP server for Hydrolix.
24
26
  ## Tools
25
27
 
26
28
  * `run_select_query`
27
- - Execute SQL queries on your Hydrolix cluster.
28
- - Input: `sql` (string): The SQL query to execute.
29
- - All Hydrolix queries are run with `readonly = 1` to ensure they are safe.
29
+ * Execute SQL queries on your Hydrolix cluster.
30
+ * Input: `sql` (string): The SQL query to execute.
31
+ * All Hydrolix queries are run with `readonly = 1` to ensure they are safe.
30
32
 
31
33
  * `list_databases`
32
- - List all databases on your Hydrolix cluster.
34
+ * List all databases on your Hydrolix cluster.
33
35
 
34
36
  * `list_tables`
35
- - List all tables in a database.
36
- - Input: `database` (string): The name of the database.
37
+ * List all tables in a database.
38
+ * Input: `database` (string): The name of the database.
37
39
 
38
40
  ## Effective Usage
39
41
 
@@ -44,13 +46,25 @@ Due to the wide variety in LLM architectures, not all models will proactively us
44
46
  * Include time ranges in your prompts (e.g., "Between December 5 2023 and January 18 2024, ...") and specifically request that the output be ordered by timestamp.
45
47
  - This prompts the model to write more efficient queries that take advantage of [primary key optimizations](https://hydrolix.io/blog/optimizing-latest-n-row-queries/)
46
48
 
49
+ ### Health Check Endpoint
50
+
51
+ When running with HTTP or SSE transport, a health check endpoint is available at `/health`. This endpoint:
52
+ - Returns `200 OK` with the Hydrolix query-head's Clickhouse version if the server is healthy and can connect to Hydrolix
53
+ - Returns `503 Service Unavailable` if the server cannot connect to the Hydrolix query-head
54
+
55
+ Example:
56
+ ```bash
57
+ curl http://localhost:8000/health
58
+ # Response: OK - Connected to Hydrolix compatible with ClickHouse 24.3.1
59
+ ```
60
+
47
61
  ## Configuration
48
62
 
49
63
  The Hydrolix MCP server is configured using a standard MCP server entry. Consult your client's documentation for specific instructions on where to find or declare MCP servers. An example setup using Claude Desktop is documented below.
50
64
 
51
65
  The recommended way to launch the Hydrolix MCP server is via the [`uv` project manager](https://github.com/astral-sh/uv), which will manage installing all other dependencies in an isolated environment.
52
66
 
53
- MCP Server definition (JSON):
67
+ MCP Server definition using username and password (JSON):
54
68
 
55
69
  ```json
56
70
  {
@@ -71,7 +85,27 @@ MCP Server definition (JSON):
71
85
  }
72
86
  ```
73
87
 
74
- MCP Server definition (YAML):
88
+ MCP Server definition using service account token (JSON):
89
+
90
+ ```json
91
+ {
92
+ "command": "uv",
93
+ "args": [
94
+ "run",
95
+ "--with",
96
+ "mcp-hydrolix",
97
+ "--python",
98
+ "3.13",
99
+ "mcp-hydrolix"
100
+ ],
101
+ "env": {
102
+ "HYDROLIX_HOST": "<hydrolix-host>",
103
+ "HYDROLIX_TOKEN": "<hydrolix-service-account-token>"
104
+ }
105
+ }
106
+ ```
107
+
108
+ MCP Server definition using username and password (YAML):
75
109
 
76
110
  ```yaml
77
111
  command: uv
@@ -88,13 +122,29 @@ env:
88
122
  HYDROLIX_PASSWORD: <hydrolix-password>
89
123
  ```
90
124
 
125
+ MCP Server definition using service account token (YAML):
126
+
127
+ ```yaml
128
+ command: uv
129
+ args:
130
+ - run
131
+ - --with
132
+ - mcp-hydrolix
133
+ - --python
134
+ - "3.13"
135
+ - mcp-hydrolix
136
+ env:
137
+ HYDROLIX_HOST: <hydrolix-host>
138
+ HYDROLIX_TOKEN: <hydrolix-service-account-token>
139
+ ```
140
+
91
141
  ### Configuration Example (Claude Desktop)
92
142
 
93
143
  1. Open the Claude Desktop configuration file located at:
94
144
  - On macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
95
145
  - On Windows: `%APPDATA%/Claude/claude_desktop_config.json`
96
146
 
97
- 2. Add a `mcp-hydrolix` server entry to the `mcpServers` config block:
147
+ 2. Add a `mcp-hydrolix` server entry to the `mcpServers` config block to use username and password:
98
148
 
99
149
  ```json
100
150
  {
@@ -119,6 +169,30 @@ env:
119
169
  }
120
170
  ```
121
171
 
172
+ To leverage service account use the following config block:
173
+
174
+ ```json
175
+ {
176
+ "mcpServers": {
177
+ "mcp-hydrolix": {
178
+ "command": "uv",
179
+ "args": [
180
+ "run",
181
+ "--with",
182
+ "mcp-hydrolix",
183
+ "--python",
184
+ "3.13",
185
+ "mcp-hydrolix"
186
+ ],
187
+ "env": {
188
+ "HYDROLIX_HOST": "<hydrolix-host>",
189
+ "HYDROLIX_TOKEN": "<hydrolix-service-account-token>"
190
+ }
191
+ }
192
+ }
193
+ }
194
+ ```
195
+
122
196
  3. Update the environment variable definitions to point to your Hydrolix cluster.
123
197
 
124
198
  4. (Recommended) Locate the command entry for `uv` and replace it with the absolute path to the `uv` executable. This ensures that the correct version of `uv` is used when starting the server. You can find this path using `which uv` or `where.exe uv`.
@@ -131,16 +205,47 @@ The following variables are used to configure the Hydrolix connection. These var
131
205
 
132
206
  #### Required Variables
133
207
  * `HYDROLIX_HOST`: The hostname of your Hydrolix server
134
- * `HYDROLIX_USER`: The username for authentication
135
- * `HYDROLIX_PASSWORD`: The password for authentication
208
+ * `HYDROLIX_TOKEN`: The Hydrolix service account token (omit if using username/password)
209
+ * `HYDROLIX_USER`: The username for authentication (omit if using service account)
210
+ * `HYDROLIX_PASSWORD`: The password for authentication (omit if using service account)
211
+
212
+ **Authentication precedence:** If both `HYDROLIX_TOKEN` and `HYDROLIX_USER`/`HYDROLIX_PASSWORD` are provided, the service account token takes precedence and username/password authentication will be ignored.
136
213
 
137
214
  #### Optional Variables
138
215
  * `HYDROLIX_PORT`: The port number of your Hydrolix server
139
- - Default: `8088`
140
- - Usually doesn't need to be set unless using a non-standard port
216
+ * Default: `8088`
217
+ * Usually doesn't need to be set unless using a non-standard port
141
218
  * `HYDROLIX_VERIFY`: Enable/disable SSL certificate verification
142
- - Default: `"true"`
143
- - Set to `"false"` to disable certificate verification (not recommended for production)
219
+ * Default: `"true"`
220
+ * Set to `"false"` to disable certificate verification (not recommended for production)
144
221
  * `HYDROLIX_DATABASE`: Default database to use
145
- - Default: None (uses server default)
146
- - Set this to automatically connect to a specific database
222
+ *Default: None (uses server default)
223
+ * Set this to automatically connect to a specific database
224
+ * `HYDROLIX_MCP_SERVER_TRANSPORT`: Sets the transport method for the MCP server.
225
+ * Default: `"stdio"`
226
+ * Valid options: `"stdio"`, `"http"`, `"sse"`. This is useful for local development with tools like MCP Inspector.
227
+ * `HYDROLIX_MCP_BIND_HOST`: Host to bind the MCP server to when using HTTP or SSE transport
228
+ * Default: `"127.0.0.1"`
229
+ * Set to `"0.0.0.0"` to bind to all network interfaces (useful for Docker or remote access)
230
+ * Only used when transport is `"http"` or `"sse"`
231
+ * `HYDROLIX_MCP_BIND_PORT`: Port to bind the MCP server to when using HTTP or SSE transport
232
+ * Default: `"8000"`
233
+ * Only used when transport is `"http"` or `"sse"`
234
+
235
+
236
+ For MCP Inspector or remote access with HTTP transport:
237
+
238
+ ```env
239
+ HYDROLIX_HOST=localhost
240
+ HYDROLIX_USER=default
241
+ HYDROLIX_PASSWORD=myPassword
242
+ HYDROLIX_MCP_SERVER_TRANSPORT=http
243
+ HYDROLIX_MCP_BIND_HOST=0.0.0.0 # Bind to all interfaces
244
+ HYDROLIX_MCP_BIND_PORT=4200 # Custom port (default: 8000)
245
+ ```
246
+
247
+ When using HTTP transport, the server will run on the configured port (default 8000). For example, with the above configuration:
248
+ - MCP endpoint: `http://localhost:4200/mcp`
249
+ - Health check: `http://localhost:4200/health`
250
+
251
+ Note: The bind host and port settings are only used when transport is set to "http" or "sse".
@@ -0,0 +1,9 @@
1
+ mcp_hydrolix/__init__.py,sha256=DnAQkvoFf_QhrDNFLOmn-nHlldPUgtdN33k3xJWthgc,225
2
+ mcp_hydrolix/main.py,sha256=9cNwIVNE0fCbPuwde-VjcwyZwNb07i9zFRxzEE1yLus,703
3
+ mcp_hydrolix/mcp_env.py,sha256=3NyjHiMyhodB9D7QpPxtFtXFzHbw-FzrqlsmGPElqwc,7651
4
+ mcp_hydrolix/mcp_server.py,sha256=i0qPwXv-s6-kxMh1FeuPNAItk5u17sSjY8GGu1SiyB8,10537
5
+ mcp_hydrolix-0.1.4.dist-info/METADATA,sha256=13PY5b6HeuurxPMYOysJ29FwJ8wc2xPnKUNvGSWuqxA,8376
6
+ mcp_hydrolix-0.1.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
7
+ mcp_hydrolix-0.1.4.dist-info/entry_points.txt,sha256=vHa7F2rOCVu8lpsqR8BYbE1w8ugJSOYwX95w802Y5qE,56
8
+ mcp_hydrolix-0.1.4.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
9
+ mcp_hydrolix-0.1.4.dist-info/RECORD,,
@@ -198,4 +198,4 @@
198
198
  distributed under the License is distributed on an "AS IS" BASIS,
199
199
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
200
  See the License for the specific language governing permissions and
201
- limitations under the License.
201
+ limitations under the License.
@@ -1,9 +0,0 @@
1
- mcp_hydrolix/__init__.py,sha256=DnAQkvoFf_QhrDNFLOmn-nHlldPUgtdN33k3xJWthgc,225
2
- mcp_hydrolix/main.py,sha256=WTZcvpIas2mIjNd_em7KWlXI_6IXZAdtdlQ7czmX1cs,96
3
- mcp_hydrolix/mcp_env.py,sha256=HPOPRdPm-YAxU3ygdZPoNT8Oc4CE-VWXF6mmton5mnU,4454
4
- mcp_hydrolix/mcp_server.py,sha256=xzEILclKF88KE8OZljiaAHwPcfiyt5DE_amPnqVyMFQ,10017
5
- mcp_hydrolix-0.1.3.dist-info/METADATA,sha256=EEc9-0SGJc6AGmSQHvrbki6unzxV7c1m2IoLJhMWBuo,5152
6
- mcp_hydrolix-0.1.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
7
- mcp_hydrolix-0.1.3.dist-info/entry_points.txt,sha256=vHa7F2rOCVu8lpsqR8BYbE1w8ugJSOYwX95w802Y5qE,56
8
- mcp_hydrolix-0.1.3.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
9
- mcp_hydrolix-0.1.3.dist-info/RECORD,,