Skip to content

Commit 6cde490

Browse files
author
Jove Zhong
authored
port latest code/refinement from mcp-clickhouse (#3)
* update the env config * don't check env, since default values provided * Update README.md * Update test_tool.py * Update test_tool.py
1 parent 0b8a75d commit 6cde490

File tree

6 files changed

+210
-59
lines changed

6 files changed

+210
-59
lines changed

.github/workflows/ci.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ jobs:
3535
TIMEPLUS_PORT: "8123"
3636
TIMEPLUS_USER: "default"
3737
TIMEPLUS_PASSWORD: ""
38+
TIMEPLUS_SECURE: "false"
39+
TIMEPLUS_VERIFY: "false"
3840
run: |
3941
uv run pytest tests
4042

.gitignore

Lines changed: 1 addition & 0 deletions
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

Lines changed: 43 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
An MCP server for Timeplus.
55

6-
<a href="https://glama.ai/mcp/servers/yvjy4csvo1"><img width="380" height="200" src="https://glama.ai/mcp/servers/yvjy4csvo1/badge" alt="mcp-timeplus MCP server" /></a>
6+
<a href="https://glama.ai/mcp/servers/9aleefsq9s"><img width="380" height="200" src="https://glama.ai/mcp/servers/9aleefsq9s/badge" alt="mcp-timeplus MCP server" /></a>
77

88
## Features
99

@@ -25,8 +25,6 @@ An MCP server for Timeplus.
2525

2626
First, ensure you have the `uv` executable installed. If not, you can install it by following the instructions [here](https://docs.astral.sh/uv/).
2727

28-
This Python package is not published to PyPI yet. Please clone this repo and run `uv sync` to install the dependencies.
29-
3028
1. Open the Claude Desktop configuration file located at:
3129
- On macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
3230
- On Windows: `%APPDATA%/Claude/claude_desktop_config.json`
@@ -37,20 +35,17 @@ This Python package is not published to PyPI yet. Please clone this repo and run
3735
{
3836
"mcpServers": {
3937
"mcp-timeplus": {
40-
"command": "/path/to/uv",
41-
"args": [
42-
"run",
43-
"--project",
44-
"/path/to/repo/mcp-timeplus",
45-
"--python",
46-
"3.13",
47-
"mcp-timeplus"
48-
],
38+
"command": "uvx",
39+
"args": ["mcp-timeplus"],
4940
"env": {
5041
"TIMEPLUS_HOST": "<timeplus-host>",
5142
"TIMEPLUS_PORT": "<timeplus-port>",
5243
"TIMEPLUS_USER": "<timeplus-user>",
53-
"TIMEPLUS_PASSWORD": "<timeplus-password>"
44+
"TIMEPLUS_PASSWORD": "<timeplus-password>",
45+
"TIMEPLUS_SECURE": "false",
46+
"TIMEPLUS_VERIFY": "true",
47+
"TIMEPLUS_CONNECT_TIMEOUT": "30",
48+
"TIMEPLUS_SEND_RECEIVE_TIMEOUT": "30"
5449
}
5550
}
5651
}
@@ -59,33 +54,9 @@ This Python package is not published to PyPI yet. Please clone this repo and run
5954

6055
Update the environment variables to point to your own Timeplus service.
6156

62-
3. 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. Also point to the absolute path to the `mcp-timeplus` directory. A sample configuration:
57+
3. Restart Claude Desktop to apply the changes.
6358

64-
```json
65-
{
66-
"mcpServers": {
67-
"mcp-timeplus": {
68-
"command": "/opt/homebrew/bin/uv",
69-
"args": [
70-
"run",
71-
"--project",
72-
"/Users/jove/Dev/mcp-timeplus",
73-
"--python",
74-
"3.13",
75-
"mcp-timeplus"
76-
],
77-
"env": {
78-
"TIMEPLUS_HOST": "localhost",
79-
"TIMEPLUS_PORT": "8123",
80-
"TIMEPLUS_USER": "default",
81-
"TIMEPLUS_PASSWORD": ""
82-
}
83-
}
84-
}
85-
}
86-
```
87-
88-
4. Restart Claude Desktop to apply the changes.
59+
You can also try this MCP server with other MCP clients, such as [5ire](https://github.com/nanbingxyz/5ire).
8960

9061
## Development
9162

@@ -98,8 +69,41 @@ TIMEPLUS_HOST=localhost
9869
TIMEPLUS_PORT=8123
9970
TIMEPLUS_USER=default
10071
TIMEPLUS_PASSWORD=
72+
TIMEPLUS_SECURE=false
73+
TIMEPLUS_VERIFY=true
74+
TIMEPLUS_CONNECT_TIMEOUT=30
75+
TIMEPLUS_SEND_RECEIVE_TIMEOUT=30
10176
```
10277

10378
3. Run `uv sync` to install the dependencies. Then do `source .venv/bin/activate`.
10479

10580
4. For easy testing, you can run `fastmcp dev mcp_timeplus/mcp_server.py` to start the MCP server. Click the "Connect" button to connect the UI with the MCP server, then switch to the "Tools" tab to run the available tools: list_databases, list_tables, run_selected_query.
81+
82+
### Environment Variables
83+
84+
The following environment variables are used to configure the Timeplus connection:
85+
86+
#### Required Variables
87+
* `TIMEPLUS_HOST`: The hostname of your Timeplus server
88+
* `TIMEPLUS_USER`: The username for authentication
89+
* `TIMEPLUS_PASSWORD`: The password for authentication
90+
91+
#### Optional Variables
92+
* `TIMEPLUS_PORT`: The port number of your Timeplus server
93+
- Default: `8443` if HTTPS is enabled, `8123` if disabled
94+
- Usually doesn't need to be set unless using a non-standard port
95+
* `TIMEPLUS_SECURE`: Enable/disable HTTPS connection
96+
- Default: `"false"`
97+
- Set to `"true"` for secure connections
98+
* `TIMEPLUS_VERIFY`: Enable/disable SSL certificate verification
99+
- Default: `"true"`
100+
- Set to `"false"` to disable certificate verification (not recommended for production)
101+
* `TIMEPLUS_CONNECT_TIMEOUT`: Connection timeout in seconds
102+
- Default: `"30"`
103+
- Increase this value if you experience connection timeouts
104+
* `TIMEPLUS_SEND_RECEIVE_TIMEOUT`: Send/receive timeout in seconds
105+
- Default: `"300"`
106+
- Increase this value for long-running queries
107+
* `TIMEPLUS_DATABASE`: Default database to use
108+
- Default: None (uses server default)
109+
- Set this to automatically connect to a specific database

mcp_timeplus/mcp_env.py

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
"""Environment configuration for the MCP Timeplus 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 TimeplusConfig:
14+
"""Configuration for Timeplus 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+
TIMEPLUS_HOST: The hostname of the Timeplus server
21+
TIMEPLUS_USER: The username for authentication
22+
23+
Optional environment variables (with defaults):
24+
TIMEPLUS_PASSWORD: The password for authentication (default: empty string)
25+
TIMEPLUS_PORT: The port number (default: 8443 if secure=True, 8123 if secure=False)
26+
TIMEPLUS_SECURE: Enable HTTPS (default: false)
27+
TIMEPLUS_VERIFY: Verify SSL certificates (default: true)
28+
TIMEPLUS_CONNECT_TIMEOUT: Connection timeout in seconds (default: 30)
29+
TIMEPLUS_SEND_RECEIVE_TIMEOUT: Send/receive timeout in seconds (default: 300)
30+
TIMEPLUS_DATABASE: Default database to use (default: default)
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 Timeplus host."""
40+
return os.getenv("TIMEPLUS_HOST", "localhost")
41+
42+
@property
43+
def port(self) -> int:
44+
"""Get the Timeplus port.
45+
46+
Defaults to 8443 if secure=True, 8123 if secure=False.
47+
Can be overridden by TIMEPLUS_PORT environment variable.
48+
"""
49+
if "TIMEPLUS_PORT" in os.environ:
50+
return int(os.environ["TIMEPLUS_PORT"])
51+
return 8443 if self.secure else 8123
52+
53+
@property
54+
def username(self) -> str:
55+
"""Get the Timeplus username."""
56+
return os.getenv("TIMEPLUS_USER", "default")
57+
58+
@property
59+
def password(self) -> str:
60+
"""Get the Timeplus password."""
61+
return os.getenv("TIMEPLUS_PASSWORD", "")
62+
63+
@property
64+
def database(self) -> Optional[str]:
65+
"""Get the default database name if set."""
66+
return os.getenv("TIMEPLUS_DATABASE", "default")
67+
68+
@property
69+
def secure(self) -> bool:
70+
"""Get whether HTTPS is enabled.
71+
72+
Default: False
73+
"""
74+
return os.getenv("TIMEPLUS_SECURE", "false").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("TIMEPLUS_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("TIMEPLUS_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 (Timeplus default)
97+
"""
98+
return int(os.getenv("TIMEPLUS_SEND_RECEIVE_TIMEOUT", "300"))
99+
100+
def get_client_config(self) -> dict:
101+
"""Get the configuration dictionary for timeplus_connect client.
102+
103+
Returns:
104+
dict: Configuration ready to be passed to timeplus_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+
131+
if missing_vars:
132+
raise ValueError(
133+
f"Missing required environment variables: {', '.join(missing_vars)}"
134+
)
135+
136+
137+
# Global instance for easy access
138+
config = TimeplusConfig()

mcp_timeplus/mcp_server.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import logging
2-
import os
32
from typing import Sequence
43

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

98
MCP_SERVER_NAME = "mcp-timeplus"
9+
from mcp_timeplus.mcp_env import config
1010

1111
# Configure logging
1212
logging.basicConfig(
@@ -102,13 +102,20 @@ def run_select_query(query: str):
102102

103103

104104
def create_timeplus_client():
105-
host = os.getenv("TIMEPLUS_HOST")
106-
port = os.getenv("TIMEPLUS_PORT")
107-
username = os.getenv("TIMEPLUS_USER")
108-
logger.info(f"Creating Timeplus client connection to {host}:{port} as {username}")
109-
return timeplus_connect.get_client(
110-
host=host,
111-
port=port,
112-
username=username,
113-
password=os.getenv("TIMEPLUS_PASSWORD"),
114-
)
105+
client_config = config.get_client_config()
106+
logger.info(
107+
f"Creating Timeplus client connection to {client_config['host']}:{client_config['port']} "
108+
f"as {client_config['username']} "
109+
f"(secure={client_config['secure']}, verify={client_config['verify']}, "
110+
f"connect_timeout={client_config['connect_timeout']}s, "
111+
f"send_receive_timeout={client_config['send_receive_timeout']}s)")
112+
113+
try:
114+
client = timeplus_connect.get_client(**client_config)
115+
# Test the connection
116+
version = client.server_version
117+
logger.info(f"Successfully connected to Timeplus server version {version}")
118+
return client
119+
except Exception as e:
120+
logger.error(f"Failed to connect to Timeplus: {str(e)}")
121+
raise

tests/test_tool.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,26 @@
22

33
from dotenv import load_dotenv
44

5-
from mcp_clickhouse import create_clickhouse_client, list_databases, list_tables, run_select_query
5+
from mcp_timeplus import create_timeplus_client, list_databases, list_tables, run_select_query
66

77
load_dotenv()
88

99

10-
class TestClickhouseTools(unittest.TestCase):
10+
class TestTimeplusTools(unittest.TestCase):
1111
@classmethod
1212
def setUpClass(cls):
1313
"""Set up the environment before tests."""
14-
cls.client = create_clickhouse_client()
14+
cls.client = create_timeplus_client()
1515

1616
# Prepare test database and table
17-
cls.test_db = "test_tool_db"
17+
cls.test_db = "default"
1818
cls.test_table = "test_table"
1919
cls.client.command(f"CREATE DATABASE IF NOT EXISTS {cls.test_db}")
2020
cls.client.command(f"""
21-
CREATE TABLE IF NOT EXISTS {cls.test_db}.{cls.test_table} (
22-
id UInt32,
23-
name String
24-
) ENGINE = MergeTree()
25-
ORDER BY id
21+
CREATE STREAM IF NOT EXISTS {cls.test_db}.{cls.test_table} (
22+
id uint32,
23+
name string
24+
)
2625
""")
2726
cls.client.command(f"""
2827
INSERT INTO {cls.test_db}.{cls.test_table} (id, name) VALUES (1, 'Alice'), (2, 'Bob')

0 commit comments

Comments
 (0)