Skip to content
Open
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
30 changes: 30 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
.git/
.venv/
__pycache__/
*.pyc
*.pyo
*.pyd
.pytest_cache/
.mypy_cache/
.ruff_cache/

# Local skills/test data
#.skills/

# Build outputs
build/
dist/
*.egg-info/

# IDE
.vscode/
.idea/

# Docs/tests (not needed to run the server)
tests/
guide/
docs/
examples/

# OS
.DS_Store
90 changes: 90 additions & 0 deletions DOCKER_MCP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Docker: SkillPort MCP Server (HTTP)

This repo ships the MCP server as the `skillport-mcp` command.

The goal of this Docker setup is to run `skillport-mcp` in **Remote mode (HTTP)**:

```bash
SKILLPORT_SKILLS_DIR=.skills uv run skillport-mcp --http --port=3000 --host=0.0.0.0
```

Inside Docker, `SKILLPORT_SKILLS_DIR` should come from env, and typically points to a mounted volume.

---

## Files

- `Dockerfile.mcp`: Multi-stage build using `uv`.
- `scripts/build-mcp-docker.sh`: Convenience builder.

---

## Build

```bash
./scripts/build-mcp-docker.sh
# or: ./scripts/build-mcp-docker.sh v1
```

Manual build:

```bash
docker build -f Dockerfile.mcp -t skillport-mcp:latest .
```

---

## Run (HTTP / Remote mode)

### Default (0.0.0.0:3000)

```bash
docker run -p 3000:3000 \
-e SKILLPORT_SKILLS_DIR=/skills \
-v "$(pwd)/.skills:/skills" \
skillport-mcp:latest
```

The MCP endpoint will be:

- `http://localhost:3000/mcp`

### Custom host/port

```bash
docker run -p 8000:8000 \
-e SKILLPORT_SKILLS_DIR=/skills \
-v "$(pwd)/.skills:/skills" \
skillport-mcp:latest \
skillport-mcp --http --host 0.0.0.0 --port 8000
```

---

## Run (STDIO / Local mode)

If you want stdio transport (useful for local MCP client integration), run without `--http`:

```bash
docker run -i \
-e SKILLPORT_SKILLS_DIR=/skills \
-v "$(pwd)/.skills:/skills" \
skillport-mcp:latest \
skillport-mcp
```

---

## Notes / Tips

- If you want persistence for the index DB, mount the SkillPort home directory (defaults to `~/.skillport` inside the container user home). For example:

```bash
docker run -p 3000:3000 \
-e SKILLPORT_SKILLS_DIR=/skills \
-v "$(pwd)/.skills:/skills" \
-v "skillport-data:/home/app/.skillport" \
skillport-mcp:latest
```

- Embeddings default to `SKILLPORT_EMBEDDING_PROVIDER=none`. If you set it to `openai`, you must also pass `OPENAI_API_KEY`.
52 changes: 52 additions & 0 deletions Dockerfile.mcp
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# syntax=docker/dockerfile:1

# Stage 1: builder
FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim AS builder

ENV UV_COMPILE_BYTECODE=1 \
UV_LINK_MODE=copy

WORKDIR /app

# Copy dependency metadata first for better cache reuse
COPY pyproject.toml uv.lock ./

# Workspace members must exist before `uv sync` can resolve workspace sources.
COPY packages/skillport-core/pyproject.toml packages/skillport-core/pyproject.toml
COPY packages/skillport-mcp/pyproject.toml packages/skillport-mcp/pyproject.toml

# Install dependencies (without installing the workspace itself)
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --locked --no-install-workspace --no-editable --no-dev

# Copy the full workspace
COPY . /app

# Install the workspace (console scripts like `skillport-mcp` become available)
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --locked --no-editable --no-dev


# Stage 2: runtime
FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim AS runtime

# Non-root user
RUN groupadd --gid=1000 app \
&& useradd --uid=1000 --gid=app --shell=/bin/bash --create-home app

WORKDIR /app

# Copy built workspace + venv
COPY --from=builder --chown=app:app /app /app

ENV PATH="/app/.venv/bin:$PATH"

USER app

EXPOSE 3000

# Default: HTTP (Remote mode) on 0.0.0.0:3000
# Override by passing a different command, e.g.:
# docker run -i <image> skillport-mcp # stdio
# docker run -p 8000:8000 <image> skillport-mcp --http --host 0.0.0.0 --port 8000
CMD ["skillport-mcp", "--http", "--host", "0.0.0.0", "--port", "3000"]
9 changes: 9 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ dev = [
"tomli>=2.0.0",
]

mcp = [
"skillport-mcp==1.1.0",
]

[tool.pytest.ini_options]
asyncio_mode = "auto"
filterwarnings = [
Expand All @@ -69,12 +73,17 @@ Repository = "https://github.com/gotalab/skillport"
[project.scripts]
skillport = "skillport.interfaces.cli.app:run"

[tool.uv]
default-groups = ["dev", "mcp"]

[tool.uv.sources]
skillport-core = { workspace = true }
skillport-mcp = { workspace = true }

[tool.uv.workspace]
members = [
"packages/skillport-core",
"packages/skillport-mcp",
]

[tool.ruff]
Expand Down
40 changes: 40 additions & 0 deletions scripts/build-mcp-docker.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env bash
# Build script for SkillPort MCP Server Docker image

set -euo pipefail

IMAGE_NAME="skillport-mcp"
TAG="${1:-latest}"
FULL_IMAGE_NAME="${IMAGE_NAME}:${TAG}"

echo "🐳 Building Docker image: ${FULL_IMAGE_NAME}"

docker build -f Dockerfile.mcp -t "${FULL_IMAGE_NAME}" .

echo "✅ Built: ${FULL_IMAGE_NAME}"

# Show image info
echo ""
echo "📊 Image information:"
docker images "${IMAGE_NAME}" --format "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.Size}}\t{{.CreatedSince}}"

cat <<EOF

🚀 Run (HTTP mode, default):
docker run -p 3000:3000 \
-e SKILLPORT_SKILLS_DIR=/skills \
-v "$(pwd)/.skills:/skills" \
${FULL_IMAGE_NAME}

🔍 Run (HTTP mode, custom host/port):
docker run -p 8000:8000 \
-e SKILLPORT_SKILLS_DIR=/skills \
-v "$(pwd)/.skills:/skills" \
${FULL_IMAGE_NAME} skillport-mcp --http --host 0.0.0.0 --port 8000

🛠️ Run (STDIO mode):
docker run -i \
-e SKILLPORT_SKILLS_DIR=/skills \
-v "$(pwd)/.skills:/skills" \
${FULL_IMAGE_NAME} skillport-mcp
EOF
32 changes: 30 additions & 2 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.