Skip to content

Commit e407f52

Browse files
authored
Merge branch 'main' into add-chdb-mcp
2 parents 2402f02 + f9b5177 commit e407f52

File tree

6 files changed

+148
-14
lines changed

6 files changed

+148
-14
lines changed

.dockerignore

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Git
2+
.git/
3+
.gitignore
4+
.github/
5+
6+
# Documentation
7+
LICENSE
8+
# README.md is needed for package build
9+
# *.md files may be needed for package metadata
10+
11+
# Development environment
12+
.venv/
13+
.env
14+
.envrc
15+
.python-version
16+
17+
# IDE and editor files
18+
.vscode/
19+
.idea/
20+
*.swp
21+
*.swo
22+
*~
23+
24+
# Cache directories
25+
.ruff_cache/
26+
.pytest_cache/
27+
__pycache__/
28+
*.pyc
29+
*.pyo
30+
*.pyd
31+
.mypy_cache/
32+
.dmypy.json
33+
dmypy.json
34+
35+
# Test files and directories
36+
tests/
37+
test-services/
38+
.coverage
39+
.coverage.*
40+
htmlcov/
41+
.tox/
42+
.nox/
43+
coverage.xml
44+
*.cover
45+
*.py,cover
46+
.hypothesis/
47+
cover/
48+
49+
# Build artifacts
50+
build/
51+
dist/
52+
*.egg-info/
53+
.eggs/
54+
*.egg
55+
MANIFEST
56+
57+
# Temporary files
58+
*.tmp
59+
*.temp
60+
*.log
61+
.DS_Store
62+
Thumbs.db
63+
64+
# Docker files (avoid copying Dockerfile into itself)
65+
Dockerfile
66+
.dockerignore
67+
68+
# Editor config (not needed at runtime)
69+
.editorconfig
70+
71+
# Claude AI files
72+
.claude/

.github/workflows/ci.yaml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,31 @@ jobs:
4545
4646
- name: Lint with Ruff
4747
run: uv run ruff check .
48+
49+
docker-build:
50+
runs-on: ubuntu-latest
51+
52+
steps:
53+
- name: Checkout repository
54+
uses: actions/checkout@v4
55+
56+
- name: Set up Docker Buildx
57+
uses: docker/setup-buildx-action@v3
58+
59+
- name: Build Docker image
60+
uses: docker/build-push-action@v5
61+
with:
62+
context: .
63+
push: false
64+
load: true
65+
tags: mcp-clickhouse:test
66+
cache-from: type=gha
67+
cache-to: type=gha,mode=max
68+
69+
- name: Test Docker image import
70+
run: |
71+
docker run --rm mcp-clickhouse:test python -c "import mcp_clickhouse; print('✅ MCP ClickHouse Docker image works!')"
72+
73+
- name: Test Docker image default command
74+
run: |
75+
timeout 10s docker run --rm mcp-clickhouse:test || [ $? = 124 ] && echo "✅ Docker container starts successfully"

Dockerfile

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Build stage - Use a Python image with uv pre-installed
2+
FROM ghcr.io/astral-sh/uv:python3.13-alpine AS builder
3+
4+
# Install the project into `/app`
5+
WORKDIR /app
6+
7+
# Enable bytecode compilation
8+
ENV UV_COMPILE_BYTECODE=1
9+
10+
# Copy from the cache instead of linking since it's a mounted volume
11+
ENV UV_LINK_MODE=copy
12+
13+
# Install git and build dependencies for ClickHouse client
14+
RUN --mount=type=cache,target=/var/cache/apk \
15+
apk add git build-base
16+
17+
# Install the project's dependencies using the lockfile and settings
18+
RUN --mount=type=cache,target=/root/.cache/uv \
19+
--mount=type=bind,source=uv.lock,target=uv.lock \
20+
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
21+
--mount=type=bind,source=README.md,target=README.md \
22+
uv sync --locked --no-install-project --no-dev
23+
24+
# Then, add the rest of the project source code and install it
25+
# Installing separately from its dependencies allows optimal layer caching
26+
COPY . /app
27+
RUN --mount=type=cache,target=/root/.cache/uv \
28+
uv sync --locked --no-dev --no-editable
29+
30+
# Production stage - Use minimal Python image
31+
FROM python:3.13-alpine
32+
33+
# Set the working directory
34+
WORKDIR /app
35+
36+
# Copy the virtual environment from the builder stage
37+
COPY --from=builder /app/.venv /app/.venv
38+
39+
# Place executables in the environment at the front of the path
40+
ENV PATH="/app/.venv/bin:$PATH"
41+
42+
# Run the MCP ClickHouse server by default
43+
CMD ["python", "-m", "mcp_clickhouse.main"]

mcp_clickhouse/mcp_server.py

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -139,15 +139,8 @@ def execute_query(query: str):
139139
try:
140140
read_only = get_readonly_setting(client)
141141
res = client.query(query, settings={"readonly": read_only})
142-
column_names = res.column_names
143-
rows = []
144-
for row in res.result_rows:
145-
row_dict = {}
146-
for i, col_name in enumerate(column_names):
147-
row_dict[col_name] = row[i]
148-
rows.append(row_dict)
149-
logger.info(f"Query returned {len(rows)} rows")
150-
return rows
142+
logger.info(f"Query returned {len(res.result_rows)} rows")
143+
return {"columns": res.column_names, "rows": res.result_rows}
151144
except Exception as err:
152145
logger.error(f"Error executing query: {err}")
153146
# Return a structured dictionary rather than a string to ensure proper serialization

tests/test_tool.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,10 @@ def test_run_select_query_success(self):
6262
"""Test running a SELECT query successfully."""
6363
query = f"SELECT * FROM {self.test_db}.{self.test_table}"
6464
result = run_select_query(query)
65-
self.assertIsInstance(result, list)
65+
self.assertIsInstance(result, dict)
6666
self.assertEqual(len(result), 2)
67-
self.assertEqual(result[0]["id"], 1)
68-
self.assertEqual(result[0]["name"], "Alice")
67+
self.assertEqual(result["rows"][0][0], 1)
68+
self.assertEqual(result["rows"][0][1], "Alice")
6969

7070
def test_run_select_query_failure(self):
7171
"""Test running a SELECT query with an error."""

uv.lock

Lines changed: 0 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)