Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 14 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1 +1,14 @@
OPENAI_API_KEY="<YOUR-OPENAI_API_KEY>"
# Required: API key for LLM access
OPENAI_API_KEY="<YOUR_OPENAI_API_KEY>"

# Optional: Kaizen Backend Configuration
# KAIZEN_BACKEND=filesystem
# KAIZEN_NAMESPACE_ID=kaizen

# Optional: LLM Configuration
# KAIZEN_MODEL_NAME=gpt-4o
# KAIZEN_CUSTOM_LLM_PROVIDER=openai
# OPENAI_BASE_URL=https://api.openai.com/v1

# Optional: Advanced Settings
# KAIZEN_CLUSTERING_THRESHOLD=0.80
62 changes: 62 additions & 0 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: Build and Publish Docker Images

on:
release:
types: [published]

permissions:
contents: read
packages: write

jobs:
docker-publish:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- dockerfile: Dockerfile.core
image-suffix: core

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ secrets.DOCKERHUB_USERNAME }}/kaizen-mcp-${{ matrix.image-suffix }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value=latest

- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: ${{ matrix.dockerfile }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64,linux/arm64
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Update Docker Hub description
uses: peter-evans/dockerhub-description@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
repository: ${{ secrets.DOCKERHUB_USERNAME }}/kaizen-mcp-${{ matrix.image-suffix }}
readme-filepath: ./README.md
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ demo/workdir/.claude/
.claude
dist
.coverage
/.kaizen
.secrets
event.json
11 changes: 11 additions & 0 deletions .roomodes
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"customModes": [
{
"slug": "kaizen",
"name": "Kaizen",
"roleDefinition": "You are the Kaizen agent — a self-improving AI assistant deeply familiar with the Kaizen project. Your purpose is to help the project continuously improve by extracting lessons from work done, storing them as searchable guidelines in the Kaizen vector database, and surfacing those learnings to improve project documentation. You have full knowledge of the Kaizen CLI (`uv run kaizen`) and the project's architecture as described in AGENTS.md.",
"customInstructions": "See .roo/rules-kaizen/instructions.md for detailed behavior for each slash command.",
"groups": ["read", "edit", "command"]
}
]
}
58 changes: 58 additions & 0 deletions DOCKER_TESTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Docker MCP Server Testing Guide

This guide explains how to manually build and test the Kaizen MCP server Docker images.

## Prerequisites

- Docker or Podman installed and running
- OpenAI API key (or compatible LLM API key)

## Building the Image

```bash
docker build -f Dockerfile.core -t kaizen-mcp:test .
```
## Testing the MCP Server

```bash
cp .env.example .env
# Populate the .env file with your API keys and any other configs
docker run -i --rm \
-e KAIZEN_BACKEND=filesystem \
-v $(pwd)/kaizen-data:/app/.kaizen \
--env-file .env kaizen-mcp:test
```

You should see output like:
```
╭──────────────────────────────────────────────────────────────────────────────╮
│ ▄▀▀ ▄▀█ █▀▀ ▀█▀ █▀▄▀█ █▀▀ █▀█ │
│ █▀ █▀█ ▄▄█ █ █ ▀ █ █▄▄ █▀▀ │
│ FastMCP 3.1.0 │
│ 🖥 Server: entities, 3.1.0 │
╰──────────────────────────────────────────────────────────────────────────────╯

INFO Starting MCP server 'entities' with transport 'stdio'
```
### 3. Test with MCP Client

To test the server with an actual MCP client, add it to your MCP configuration:

```json
{
"mcpServers": {
"kaizen": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-e", "OPENAI_API_KEY",
"kaizen-mcp:test"
]
}
}
}
```

Then you can send MCP protocol messages via stdin.
38 changes: 38 additions & 0 deletions Dockerfile.core
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
FROM registry.access.redhat.com/ubi9/ubi-minimal:latest

# Install system dependencies
RUN microdnf install -y \
python3.12 \
python3.12-pip \
git \
tar \
gzip \
&& microdnf clean all

# Install uv
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
ENV PATH="/root/.local/bin:${PATH}"

# Set working directory
WORKDIR /app

# Copy project files
COPY pyproject.toml uv.lock ./
COPY kaizen ./kaizen
COPY README.md LICENSE MANIFEST.in ./

# Install dependencies using uv (no extras needed for milvus as it's in base dependencies)
RUN uv sync --frozen --no-dev

# Set environment variables for filesystem backend
ENV KAIZEN_BACKEND=filesystem
ENV KAIZEN_NAMESPACE_ID=kaizen

# Expose MCP server (stdio-based, no port needed)
# The MCP server runs via stdio, not HTTP

# Activate the virtual environment
ENV PATH="/app/.venv/bin:${PATH}"

# Set the entrypoint to run the MCP server directly from venv
ENTRYPOINT ["python", "-m", "kaizen.frontend.mcp"]
11 changes: 10 additions & 1 deletion kaizen/frontend/mcp/__main__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
import logging
import sys

from kaizen.frontend.mcp.mcp_server import mcp

logger = logging.getLogger("kaizen-mcp")


def main():
"""
Main entry point for the server.
"""
mcp.run()
try:
mcp.run()
except KeyboardInterrupt:
logger.info("MCP server stopped by user (KeyboardInterrupt)")
sys.exit(0)


if __name__ == "__main__":
Expand Down
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ dependencies = [
"jinja2",
"litellm",
"pydantic",
"pymilvus[milvus-lite]",
"milvus-lite>=2.5.2rc1", # TODO: remove once 2.5.2+ is released (pkg_resources deprecation)
"pymilvus[milvus-lite]; platform_machine != 'aarch64'",
"pymilvus; platform_machine == 'aarch64'",
"milvus-lite>=2.5.2rc1; platform_machine != 'aarch64'", # TODO: remove once 2.5.2+ is released (pkg_resources deprecation)
"sentence-transformers",
"typer>=0.9.0",
]
Expand Down
18 changes: 12 additions & 6 deletions tests/e2e/test_mcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
load_dotenv()


@pytest.fixture(params=["milvus", "filesystem"])
@pytest.fixture(params=["filesystem"])
def mcp(request, tmp_path):
backend_type = request.param
os.environ["KAIZEN_NAMESPACE_ID"] = "test"
Expand All @@ -26,12 +26,18 @@ def mcp(request, tmp_path):
original_milvus_uri = None

if backend_type == "milvus":
# Use a unique DB file inside tmp_path for each test to avoid socket/locking issues and ensure cleanup
milvus_db_file = str(tmp_path / f"test_{uuid.uuid4().hex[:8]}.db")
original_milvus_uri = milvus_client_settings.uri
milvus_client_settings.uri = milvus_db_file
# Note: currently milvus-lite creates a file, not a dir for the uri
# We set KAIZEN_URI just in case, though settings init should pick it up if we did it before
_env_uri = os.getenv("KAIZEN_URI", "")
if _env_uri.startswith("http"):
# Running against a real Milvus server (e.g. in CI via Docker sidecar).
# Use the server URI directly; skip milvus-lite local file creation.
milvus_db_file = None
milvus_client_settings.uri = _env_uri
else:
# Local dev: use a unique milvus-lite DB file per test run.
milvus_db_file = str(tmp_path / f"test_{uuid.uuid4().hex[:8]}.db")
milvus_client_settings.uri = milvus_db_file
# Note: currently milvus-lite creates a file, not a dir for the uri
elif backend_type == "filesystem":
os.environ["KAIZEN_DATA_DIR"] = str(tmp_path)

Expand Down
Loading
Loading