Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
4fdac14
feat: embed MCP server in ibis-server Docker image
goldmedal Feb 27, 2026
cdd4f94
feat: add wren-agent module and MCP mdl_tools integration
goldmedal Mar 1, 2026
297d64d
refactor(mcp-server): replace nested MDL agent with flat MCP tools
goldmedal Mar 1, 2026
a561f0b
feat(mcp-server): add generate_mdl prompt and next-step hints to MDL …
goldmedal Mar 1, 2026
73e045c
fix(mcp-server): fix Manifest dto causing deploy to fail silently
goldmedal Mar 1, 2026
09ccdc3
feat(mcp-server): add mdl_save_project and mdl_load_project tools
goldmedal Mar 1, 2026
9ff417e
feat(mcp-server): parameterize project_dir and deploy in generate_mdl…
goldmedal Mar 1, 2026
5e9a954
chore(mcp-server): set deploy default to False, ask user at end of wo…
goldmedal Mar 1, 2026
4e1a743
fix(mcp-server): restore mdl_save_project and mdl_load_project tools
goldmedal Mar 1, 2026
b4f892e
chore: remove wren-agent
goldmedal Mar 5, 2026
73b65f2
feat(mcp-server): enrich MDL schema, add build_mdl_project tool, depl…
goldmedal Mar 5, 2026
f0f4aee
feat(mcp-server): replace mdl_tools with skills, remove DB driver dep…
goldmedal Mar 5, 2026
c117661
feat(skills): add wren-sql skill with dialect references
goldmedal Mar 5, 2026
e4300ee
perf(ibis-server): optimize Docker build from 66 min to ~5-8 min
goldmedal Mar 5, 2026
c9fbb9e
fix(mcp-server): use camelCase manifestStr for ibis-server dry-plan API
goldmedal Mar 5, 2026
fd906c3
add .gitkeep for mcp workspace
goldmedal Mar 5, 2026
e2b9b5c
remove unused etc
goldmedal Mar 5, 2026
d0902ab
enhance health check
goldmedal Mar 5, 2026
42d5a37
add requrie env for docker compose
goldmedal Mar 5, 2026
1ced13a
feat(skills): add wren-quickstart skill for Docker MCP setup
goldmedal Mar 5, 2026
9e05269
docs(skills): update mdl-project skill with connection.yml support
goldmedal Mar 5, 2026
f111cbf
docs(skills): add install script and update README with installation …
goldmedal Mar 6, 2026
e50acf9
docs(skills): add troubleshooting section for MCP server health issues
goldmedal Mar 6, 2026
b4e2f6b
chore(mcp-server): regenerate uv.lock after rebase onto main
goldmedal Mar 6, 2026
bf55aef
update clickhouse test
goldmedal Mar 6, 2026
2682518
update tag
goldmedal Mar 6, 2026
58d1428
address commants and enhance doc
goldmedal Mar 6, 2026
422277e
fix(docker): handle Linux ARM64 aarch64 in docker-build arch detection
goldmedal Mar 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/build-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ jobs:
wren-core-py=./wren-core-py
wren-core=./wren-core
wren-core-base=./wren-core-base
mcp-server=./mcp-server
outputs: type=image,name=${{ env.IBIS_IMAGE }},push-by-digest=true,name-canonical=true,push=true
- name: Export digest
run: |
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/stable-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ jobs:
wren-core-py=./wren-core-py
wren-core=./wren-core
wren-core-base=./wren-core-base
mcp-server=./mcp-server
outputs: type=image,name=${{ env.IBIS_IMAGE }},push-by-digest=true,name-canonical=true,push=true
- name: Export digest
run: |
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ target/
.mvn/timing.properties
.mvn/maven.config
wren-server/etc
mcp-server/workspace/*
!mcp-server/workspace/.gitkeep
/**/var/
__pycache__/
venv/
Expand Down
99 changes: 62 additions & 37 deletions ibis-server/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,20 +1,49 @@
FROM python:3.11-bookworm AS builder
# WHEEL_SOURCE=docker (default): build Rust inside Docker with BuildKit cargo cache
# WHEEL_SOURCE=local: use pre-built Linux wheel from host (maturin + zig required)
ARG WHEEL_SOURCE=docker

ARG ENV
ENV ENV=$ENV
# ── Option A: Build Rust wheel inside Docker ──────────────────────────────────
FROM python:3.11-bookworm AS wheel-docker

RUN apt-get update && apt-get -y install libpq-dev && rm -rf /var/lib/apt/lists/*

# libpq-dev is required for psycopg2
RUN apt-get update && apt-get -y install libpq-dev

# Install rust
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | bash -s -- -y
ENV PATH="/root/.cargo/bin:$PATH"

# Install justfile
RUN curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/bin
RUN pip install maturin

COPY --from=wren-core-py . /wren-core-py
COPY --from=wren-core . /wren-core
COPY --from=wren-core-base . /wren-core-base

WORKDIR /wren-core-py

# Cache cargo registry and target/ between builds for incremental recompilation
RUN --mount=type=cache,target=/root/.cargo/registry \
--mount=type=cache,target=/root/.cargo/git \
--mount=type=cache,target=/wren-core-py/target \
maturin build --release && \
mkdir /wheel && cp target/wheels/wren_core_py-*.whl /wheel/


# ── Option B: Use locally cross-compiled Linux wheel ─────────────────────────
FROM python:3.11-bookworm AS wheel-local
COPY --from=wren-core-py target/wheels/ /tmp/wheels/
RUN mkdir /wheel && cp /tmp/wheels/wren_core_py-*linux*.whl /wheel/


# ── Select wheel source ───────────────────────────────────────────────────────
FROM wheel-${WHEEL_SOURCE} AS wheel-final


# ── Python builder ────────────────────────────────────────────────────────────
FROM python:3.11-bookworm AS builder

ARG ENV
ENV ENV=$ENV

RUN apt-get update && apt-get -y install libpq-dev && rm -rf /var/lib/apt/lists/*

# python
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
# pip
Expand All @@ -28,32 +57,33 @@ ENV PYTHONUNBUFFERED=1 \

RUN pip install poetry==1.8.3

COPY --from=wren-core-py . /wren-core-py
COPY --from=wren-core . /wren-core
COPY --from=wren-core-base . /wren-core-base

WORKDIR /app

# Copy dependency files first so this layer is cached when only source changes
COPY pyproject.toml poetry.lock ./
RUN poetry install --without dev --with jupyter --no-root

# Install wheel (either from host or from wheel-docker stage)
COPY --from=wheel-final /wheel/ /tmp/wheels/
RUN poetry run pip install /tmp/wheels/wren_core_py-*.whl

# Install MCP server dependencies
RUN poetry run pip install "mcp[cli]>=1.19.0,<2"

COPY . .
RUN just install --without dev


# ── Runtime ───────────────────────────────────────────────────────────────────
FROM python:3.11-slim-bookworm AS runtime

# Add microsoft package list
# Install all system dependencies in a single layer
RUN apt-get update \
&& apt-get install -y curl gnupg \
&& curl -sSL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o /usr/share/keyrings/microsoft.gpg \
&& echo "deb [arch=amd64,arm64,armhf signed-by=/usr/share/keyrings/microsoft.gpg] https://packages.microsoft.com/debian/12/prod bookworm main" | tee /etc/apt/sources.list.d/mssql-release.list \
&& apt-get update

# Install msodbcsql 18 driver for mssql
RUN ACCEPT_EULA=Y apt-get -y install unixodbc-dev msodbcsql18

# Install libmysqlclient-dev for mysql
RUN apt-get install -y default-libmysqlclient-dev

# libpq-dev is required for psycopg2
RUN apt-get -y install libpq-dev \
&& apt-get update \
&& ACCEPT_EULA=Y apt-get install -y unixodbc-dev msodbcsql18 \
&& apt-get install -y default-libmysqlclient-dev libpq-dev \
&& rm -rf /var/lib/apt/lists/*

ENV VIRTUAL_ENV=/app/.venv \
Expand All @@ -62,25 +92,20 @@ ENV VIRTUAL_ENV=/app/.venv \
REMOTE_FUNCTION_LIST_PATH=/app/resources/function_list \
REMOTE_WHITE_FUNCTION_LIST_PATH=/app/resources/white_function_list

# Set working directory before copying files
WORKDIR /app

COPY --from=builder ${VIRTUAL_ENV} ${VIRTUAL_ENV}
COPY app ./app/
COPY resources ./resources/
COPY notebooks ./notebooks/
COPY wren/ ./wren/

# Copy pyproject.toml and poetry.lock for poetry to work
COPY pyproject.toml poetry.lock ./

# Install poetry in runtime stage
RUN pip install poetry==1.8.3
# Copy MCP server
COPY --from=mcp-server ./app /mcp-server/app/
COPY --from=mcp-server ./mdl.schema.json /mcp-server/

# Install jupyter dependencies
RUN poetry install --without dev --with jupyter
COPY pyproject.toml poetry.lock ./

# runtime
# Copy venv from builder (includes all deps + jupyter)
COPY --from=builder /app/.venv ${VIRTUAL_ENV}

# Install Opentelemetry zero-instrumentation python
Expand All @@ -93,6 +118,6 @@ RUN jupyter lab --generate-config --allow-root
COPY entrypoint.sh ./
RUN chmod +x ./entrypoint.sh

EXPOSE 8000 8888
EXPOSE 8000 8888 9000

ENTRYPOINT ["./entrypoint.sh"]
28 changes: 28 additions & 0 deletions ibis-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,34 @@ This module is the API server of Wren Engine. It's built on top of [FastAPI](htt

## Quick Start

### Building the Docker Image

```bash
just docker-build # current platform (fast: Rust built locally)
just docker-build linux/amd64 # single platform
just docker-build linux/amd64,linux/arm64 --push # multi-arch (requires --push)
```

#### Build strategies

| Scenario | Rust compilation | Speed |
|---|---|---|
| Target matches host platform | Built locally via `maturin + zig` | Fast (reuses host cargo cache) |
| Cross-platform or multi-arch | Built inside Docker via BuildKit cache mounts | Slow on first build, incremental after |

**Local build prerequisites** (single-platform matching your host):
```bash
brew install zig
rustup target add aarch64-unknown-linux-gnu # Apple Silicon
rustup target add x86_64-unknown-linux-gnu # Intel Mac
```

Once set up, only the first build is slow. Subsequent builds reuse the host cargo cache and take a few minutes.

> **Note**: Multi-arch builds (`linux/amd64,linux/arm64`) always build Rust inside Docker and require `--push` to export the image (Docker cannot load multi-arch images locally).

---

### Running Ibis Server on Docker
You can follow the steps below to run the Java engine and ibis.
> Wren Engine is migrating to [wren-core](../wren-core/). However, we still recommend starting [the Java engine](../wren-core-legacy/) to enable the query fallback mechanism.
Expand Down
36 changes: 35 additions & 1 deletion ibis-server/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,45 @@ else
CMD_PREFIX=""
fi

# Start MCP server in background if enabled
MCP_PID=""
if [[ "${ENABLE_MCP_SERVER}" == "true" ]]; then
export MCP_TRANSPORT="${MCP_TRANSPORT:-streamable-http}"
export MCP_HOST="${MCP_HOST:-0.0.0.0}"
export MCP_PORT="${MCP_PORT:-9000}"
export WREN_URL="${WREN_URL:-localhost:8000}"

echo "Starting MCP Server (transport=${MCP_TRANSPORT}, port=${MCP_PORT})..."
cd /mcp-server && python -m app.wren &
MCP_PID=$!
cd /app
echo "MCP Server started (PID: ${MCP_PID})"
fi

# Signal handler to clean up all processes
cleanup() {
echo "Shutting down..."
if [[ -n "${MCP_PID}" ]]; then
kill -TERM "${MCP_PID}" 2>/dev/null
wait "${MCP_PID}" 2>/dev/null
fi
if [[ -n "${GUNICORN_PID}" ]]; then
kill -TERM "${GUNICORN_PID}" 2>/dev/null
wait "${GUNICORN_PID}" 2>/dev/null
fi
exit 0
}
trap cleanup SIGTERM SIGINT

# Start the WrenUvicornWorker with the specified configuration
${CMD_PREFIX} gunicorn app.main:app --bind 0.0.0.0:8000 \
-k app.worker.WrenUvicornWorker \
--workers ${WREN_NUM_WORKERS} \
--max-requests 1000 \
--max-requests-jitter 100 \
--timeout 300 \
--graceful-timeout 60
--graceful-timeout 60 &
GUNICORN_PID=$!

# Wait for all background processes
wait
40 changes: 37 additions & 3 deletions ibis-server/justfile
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,46 @@ test-verbose MARKER:

image-name := "ghcr.io/canner/wren-engine-ibis:latest"

docker-build:
docker buildx build -t {{ image-name }} -f Dockerfile \
# Build image. platform: ""=current, linux/amd64, linux/arm64, linux/amd64,linux/arm64 (multi-arch needs --push)
# Single-platform matching current host: Rust built locally via maturin+zig (requires: brew install zig)
# Multi-arch or cross-platform: Rust built inside Docker with BuildKit cargo cache
docker-build platform="" *args="":
#!/usr/bin/env bash
set -euo pipefail

host_arch=$(uname -m)
host_platform=$([[ "$host_arch" = "arm64" || "$host_arch" = "aarch64" ]] && echo "linux/arm64" || echo "linux/amd64")
rust_target=$([[ "$host_arch" = "arm64" || "$host_arch" = "aarch64" ]] && echo "aarch64-unknown-linux-gnu" || echo "x86_64-unknown-linux-gnu")

build_platform="{{ platform }}"
[ -z "$build_platform" ] && build_platform="$host_platform"

platform_arg=""
[ -n "{{ platform }}" ] && platform_arg="--platform {{ platform }}"

wheel_source="docker"
if [ "$build_platform" = "$host_platform" ]; then
echo "→ Building Rust wheel locally ($rust_target via maturin+zig)"
cd ../wren-core-py
just install
python_ver=$(poetry run python -c "import sys; print(f'python{sys.version_info.major}.{sys.version_info.minor}')")
poetry run maturin build --release --target "$rust_target" --zig -i "$python_ver" --out target/wheels/
cd -
wheel_source="local"
else
echo "→ Building Rust wheel inside Docker (target: $build_platform)"
fi

docker buildx build \
$platform_arg \
--build-arg WHEEL_SOURCE="$wheel_source" \
-t {{ image-name }} \
-f Dockerfile \
--build-context wren-core-py=../wren-core-py \
--build-context wren-core=../wren-core \
--build-context wren-core-base=../wren-core-base \
.
--build-context mcp-server=../mcp-server \
{{ args }} .

docker-run:
docker run -it --rm -p 8000:8000 --env-file .env {{ image-name }}
Expand Down
7 changes: 3 additions & 4 deletions ibis-server/tests/routers/v2/connector/test_clickhouse.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from testcontainers.clickhouse import ClickHouseContainer

from app.model.data_source import X_WREN_DB_STATEMENT_TIMEOUT
from app.model.error import ErrorCode
from tests.conftest import file_path

pytestmark = pytest.mark.clickhouse
Expand Down Expand Up @@ -317,7 +316,8 @@ async def test_query_to_many_relationship(

async def test_query_alias_join(client, manifest_str, clickhouse: ClickHouseContainer):
connection_info = _to_connection_info(clickhouse)
# ClickHouse does not support alias join
# ClickHouse does support alias join since 2026.03
# refSql models rewrite alias join into a CTE, which ClickHouse supports
response = await client.post(
url=f"{base_url}/query",
json={
Expand All @@ -327,8 +327,7 @@ async def test_query_alias_join(client, manifest_str, clickhouse: ClickHouseCont
},
)

assert response.status_code == 422
assert response.json()["errorCode"] == ErrorCode.INVALID_SQL.name
assert response.status_code == 200


async def test_query_without_manifest(client, clickhouse: ClickHouseContainer):
Expand Down
38 changes: 38 additions & 0 deletions mcp-server/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# =============================================================================
# Wren MCP Server — Environment Variables
# Copy this file to .env and fill in your values.
# =============================================================================

# -----------------------------------------------------------------------------
# Core (required)
# -----------------------------------------------------------------------------

# URL of the Wren ibis-server (host:port, no scheme)
WREN_URL=localhost:8000

# Absolute path to the connection info JSON file for your data source
# See README for the required fields per data source type.
# In not set, the connection info must be set at runtime via `setup_connection` tool.
# CONNECTION_INFO_FILE=/path/to/connection_info.json

# Absolute path to the MDL JSON file to pre-load on startup (optional)
# If not set, MDL must be deployed at runtime via the 'deploy' tool.
# MDL_PATH=/path/to/mdl.json

# MCP transport: "stdio" (default, for desktop clients), "sse" (for HTTP) or "streamable-http" (for HTTP)
# Standalone (wren.py): defaults to "stdio"
# Docker with ENABLE_MCP_SERVER=true (entrypoint.sh): defaults to "streamable-http"
# MCP_TRANSPORT=stdio

# Host and port used only when MCP_TRANSPORT is "sse" or "streamable-http"
# MCP_HOST=0.0.0.0
# MCP_PORT=9000

# -----------------------------------------------------------------------------
# MDL Tools (optional — requires `just install-mdl`)
# -----------------------------------------------------------------------------

# ibis-server endpoint for MDL dry-plan validation.
# When set, mdl_validate_manifest will also run a dry-plan after JSON Schema check.
# Usually the same host as WREN_URL but with http:// scheme.
# WREN_ENGINE_ENDPOINT=http://localhost:8000
Loading
Loading