From 5c7703966133001e29ce405ead8ae448a46ebbbb Mon Sep 17 00:00:00 2001 From: Aleksandr Date: Tue, 1 Sep 2026 15:58:00 +0500 Subject: [PATCH 1/9] =?UTF-8?q?feat:=20self-contained=20Docker-=D0=BE?= =?UTF-8?q?=D0=B1=D1=80=D0=B0=D0=B7=20=D0=B4=D0=BB=D1=8F=20=D0=BB=D0=BE?= =?UTF-8?q?=D0=BA=D0=B0=D0=BB=D1=8C=D0=BD=D0=BE=D0=B3=D0=BE=20MCP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - docker-compose/docker/Dockerfile.mcp: slim-образ без zensical, индекс генерируется на этапе сборки, ENTRYPOINT run_v8std_mcp.sh - requirements-mcp.txt: явная зависимость Pillow (ранее приходила транзитивно через zensical, без него generate_ai_artifacts падает) - docker-compose: сервис v8std-mcp использует готовый образ malikovpro/v8std-mcp, монтирование репозитория больше не нужно - scripts/build_mcp_image.sh: сборка, smoke-тест, мультиархитектурный push - .github/workflows/docker-publish.yml: публикация образа при изменениях docs/scripts (имя образа через vars.DOCKERHUB_IMAGE, секреты DOCKERHUB_USERNAME/DOCKERHUB_TOKEN) - документация: запуск MCP без клонирования репозитория --- .github/workflows/docker-publish.yml | 56 ++++++++ README.md | 10 +- docker-compose/docker-compose.yml | 7 +- docker-compose/docker/Dockerfile.mcp | 46 ++++++ .../docker/Dockerfile.mcp.dockerignore | 8 ++ docs/support.md | 12 ++ requirements-mcp.txt | 1 + scripts/build_mcp_image.sh | 132 ++++++++++++++++++ 8 files changed, 265 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/docker-publish.yml create mode 100644 docker-compose/docker/Dockerfile.mcp create mode 100644 docker-compose/docker/Dockerfile.mcp.dockerignore create mode 100755 scripts/build_mcp_image.sh diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 0000000..a2f2190 --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,56 @@ +name: docker-publish + +on: + push: + branches: + - main + paths: + - "docs/**" + - "scripts/**" + - "requirements-mcp.txt" + - "retrieval-rules.yml" + - "zensical.toml" + - "docker-compose/docker/Dockerfile.mcp" + - ".github/workflows/docker-publish.yml" + workflow_dispatch: + +env: + REGISTRY_IMAGE: ${{ vars.DOCKERHUB_IMAGE || 'malikovpro/v8std-mcp' }} + +jobs: + publish: + if: ${{ vars.DOCKERHUB_IMAGE != '' }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v5 + - name: Set up 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 + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY_IMAGE }} + tags: | + type=raw,value=latest + type=raw,value={{date 'YYYY-MM-DD'}} + type=sha,format=long + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: . + file: docker-compose/docker/Dockerfile.mcp + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + REVISION=${{ github.sha }} + CREATED=${{ github.event.head_commit.timestamp }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/README.md b/README.md index 2815a7f..9877df3 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,14 @@ https://v8std.ru docker compose -f docker-compose/docker-compose.yml up -d v8std-mcp ``` -MCP-сервер будет доступен на `http://127.0.0.1:8765/mcp` и читает локальный индекс -`docs/ai/pages.jsonl` из смонтированного репозитория. +MCP-сервер будет доступен на `http://127.0.0.1:8765/mcp`. Если образа +`malikovpro/v8std-mcp` нет локально, он будет собран из репозитория. + +Запуск без клонирования репозитория: + +```bash +docker run -d --name v8std-mcp -p 127.0.0.1:8765:8765 malikovpro/v8std-mcp:latest +``` ```bash codex mcp add v8std-local --url http://127.0.0.1:8765/mcp diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index 39a0a96..6e91fb9 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -14,9 +14,10 @@ services: v8std-mcp: container_name: v8std-mcp + image: malikovpro/v8std-mcp:latest build: context: .. - dockerfile: docker-compose/docker/Dockerfile + dockerfile: docker-compose/docker/Dockerfile.mcp restart: always ports: - "127.0.0.1:8765:8765" @@ -24,11 +25,7 @@ services: - ALL security_opt: - no-new-privileges:true - volumes: - - ../:/docs - working_dir: /docs environment: V8STD_MCP_GENERATE_INDEX: auto V8STD_MCP_HOST: 0.0.0.0 V8STD_MCP_PORT: "8765" - entrypoint: ["/opt/v8std/scripts/run_v8std_mcp.sh"] diff --git a/docker-compose/docker/Dockerfile.mcp b/docker-compose/docker/Dockerfile.mcp new file mode 100644 index 0000000..f527420 --- /dev/null +++ b/docker-compose/docker/Dockerfile.mcp @@ -0,0 +1,46 @@ +FROM python:3.12-slim + +ARG PIP_INDEX_URL=https://pypi.org/simple +ARG REVISION=unknown +ARG CREATED=unknown + +LABEL org.opencontainers.image.title="v8std MCP" \ + org.opencontainers.image.description="Локальный MCP-сервер стандартов разработки 1С (v8std.ru)" \ + org.opencontainers.image.source="https://github.com/zeegin/v8std" \ + org.opencontainers.image.revision="${REVISION}" \ + org.opencontainers.image.created="${CREATED}" \ + org.opencontainers.image.licenses="CC0-1.0" + +ENV V8STD_REPO_ROOT=/opt/v8std \ + V8STD_MCP_GENERATE_INDEX=auto \ + V8STD_MCP_HOST=0.0.0.0 \ + V8STD_MCP_PORT=8765 \ + PYTHONUNBUFFERED=1 + +WORKDIR /opt/v8std + +RUN apt-get update \ + && apt-get install -y --no-install-recommends fonts-dejavu-core \ + && rm -rf /var/lib/apt/lists/* + +COPY requirements-mcp.txt /opt/v8std/requirements-mcp.txt +COPY scripts /opt/v8std/scripts +COPY docs /opt/v8std/docs +COPY zensical.toml retrieval-rules.yml /opt/v8std/ + +RUN chmod +x /opt/v8std/scripts/run_v8std_mcp.sh \ + /opt/v8std/scripts/v8std_mcp_server.py \ + && PIP_INDEX_URL="${PIP_INDEX_URL}" python -m pip install --no-cache-dir -r /opt/v8std/requirements-mcp.txt \ + && python scripts/generate_ai_artifacts.py \ + && python scripts/generate_search_vectors.py \ + --pages docs/ai/pages.jsonl \ + --output docs/ai/search-vectors.jsonl + +RUN useradd --system --create-home v8std \ + && chown -R v8std:v8std /opt/v8std + +USER v8std + +EXPOSE 8765 + +ENTRYPOINT ["/opt/v8std/scripts/run_v8std_mcp.sh"] diff --git a/docker-compose/docker/Dockerfile.mcp.dockerignore b/docker-compose/docker/Dockerfile.mcp.dockerignore new file mode 100644 index 0000000..bac1e76 --- /dev/null +++ b/docker-compose/docker/Dockerfile.mcp.dockerignore @@ -0,0 +1,8 @@ +** +!requirements-mcp.txt +!scripts/ +!scripts/** +!docs/ +!docs/** +!zensical.toml +!retrieval-rules.yml diff --git a/docs/support.md b/docs/support.md index daf888c..5a2069e 100644 --- a/docs/support.md +++ b/docs/support.md @@ -100,6 +100,18 @@ cd v8std docker compose -f docker-compose/docker-compose.yml up -d v8std-mcp ``` +Можно обойтись без клонирования репозитория. Готовый образ содержит базу +стандартов и индекс поиска: + +```bash +docker run -d --name v8std-mcp -p 127.0.0.1:8765:8765 malikovpro/v8std-mcp:latest +``` + +Образ обновляется вместе с базой стандартов. Тег `latest` указывает на свежую +сборку, датированные теги вида `2026-09-01` и теги вида `sha-…` позволяют +зафиксировать конкретную версию. Актуальность базы в запущенном контейнере +зависит от даты сборки образа. + Локальный адрес MCP: ```text diff --git a/requirements-mcp.txt b/requirements-mcp.txt index ce0e5f7..f7171dd 100644 --- a/requirements-mcp.txt +++ b/requirements-mcp.txt @@ -1,2 +1,3 @@ mcp==1.27.0 PyYAML +Pillow diff --git a/scripts/build_mcp_image.sh b/scripts/build_mcp_image.sh new file mode 100755 index 0000000..d94fb6a --- /dev/null +++ b/scripts/build_mcp_image.sh @@ -0,0 +1,132 @@ +#!/usr/bin/env bash + +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)" + +IMAGE="malikovpro/v8std-mcp" +LOCAL_TAG="local" +PUSH=0 +SMOKE=0 +PIP_INDEX_URL="${V8STD_PIP_INDEX_URL:-https://pypi.org/simple}" +PLATFORMS="" + +usage() { + cat <&2; usage >&2; exit 2 ;; + esac +done + +if [ "${PUSH}" -eq 1 ] && [ "${SMOKE}" -eq 1 ]; then + printf -- '--smoke работает только с локальной сборкой (без --push)\n' >&2 + exit 2 +fi + +REVISION="$(git -C "${REPO_ROOT}" rev-parse HEAD)" +CREATED="$(date -u +%Y-%m-%dT%H:%M:%SZ)" +DATE_TAG="$(date -u +%Y-%m-%d)" +SHA_TAG="sha-$(git -C "${REPO_ROOT}" rev-parse --short=12 HEAD)" +DOCKERFILE="docker-compose/docker/Dockerfile.mcp" + +BUILDER="" +SMOKE_NAME="" + +cleanup() { + if [ -n "${BUILDER}" ]; then + docker buildx rm -f "${BUILDER}" >/dev/null 2>&1 || true + fi + if [ -n "${SMOKE_NAME}" ]; then + docker rm -f "${SMOKE_NAME}" >/dev/null 2>&1 || true + fi +} +trap cleanup EXIT + +BUILD_ARGS=( + --build-arg "REVISION=${REVISION}" + --build-arg "CREATED=${CREATED}" + --build-arg "PIP_INDEX_URL=${PIP_INDEX_URL}" +) + +cd "${REPO_ROOT}" + +if [ "${PUSH}" -eq 1 ]; then + BUILDER="v8std-mcp-builder-$$" + docker buildx create --name "${BUILDER}" --driver docker-container --use >/dev/null + docker buildx build \ + --file "${DOCKERFILE}" \ + --platform "linux/amd64,linux/arm64" \ + --tag "${IMAGE}:latest" \ + --tag "${IMAGE}:${DATE_TAG}" \ + --tag "${IMAGE}:${SHA_TAG}" \ + --push \ + "${BUILD_ARGS[@]}" \ + . + printf 'запушено: %s:latest, %s:%s, %s:%s\n' "${IMAGE}" "${IMAGE}" "${DATE_TAG}" "${IMAGE}" "${SHA_TAG}" +else + PLATFORM_ARGS=() + if [ -n "${PLATFORMS}" ]; then + PLATFORM_ARGS=(--platform "${PLATFORMS}") + fi + docker build \ + --file "${DOCKERFILE}" \ + "${PLATFORM_ARGS[@]}" \ + --tag "${IMAGE}:${LOCAL_TAG}" \ + "${BUILD_ARGS[@]}" \ + . + printf 'собрано: %s:%s\n' "${IMAGE}" "${LOCAL_TAG}" +fi + +if [ "${SMOKE}" -eq 1 ]; then + SMOKE_NAME="v8std-mcp-smoke-$$" + docker run -d --rm --name "${SMOKE_NAME}" \ + -p 127.0.0.1:8765:8765 \ + "${IMAGE}:${LOCAL_TAG}" >/dev/null + ok=0 + for _ in $(seq 1 30); do + code="$(curl --noproxy '*' -s -o /dev/null -w '%{http_code}' --max-time 2 http://127.0.0.1:8765/mcp || true)" + if [ "${code}" != "000" ]; then + ok=1 + printf 'smoke: MCP ответил HTTP %s на http://127.0.0.1:8765/mcp\n' "${code}" + break + fi + sleep 1 + done + if [ "${ok}" -ne 1 ]; then + printf 'smoke: нет ответа на http://127.0.0.1:8765/mcp, логи контейнера:\n' >&2 + docker logs "${SMOKE_NAME}" >&2 || true + exit 1 + fi + docker rm -f "${SMOKE_NAME}" >/dev/null + SMOKE_NAME="" +fi From 90b15ea16f3b72db507242160e2ccf64def95518 Mon Sep 17 00:00:00 2001 From: Aleksandr Date: Tue, 1 Sep 2026 16:01:23 +0500 Subject: [PATCH 2/9] =?UTF-8?q?feat:=20=D0=BF=D0=B5=D1=80=D0=B5=D1=85?= =?UTF-8?q?=D0=BE=D0=B4=20MCP-=D0=BE=D0=B1=D1=80=D0=B0=D0=B7=D0=B0=20?= =?UTF-8?q?=D0=BD=D0=B0=20Alpine=20=D0=B8=20=D0=BF=D0=B8=D0=BD=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=B2=D0=B8=D1=81=D0=B8=D0=BC=D0=BE=D1=81=D1=82=D0=B5?= =?UTF-8?q?=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - база python:3.12-alpine вместо python:3.12-slim (~40% легче) - font-dejavu через apk + симлинк в Debian-путь для find_font - requirements-mcp.txt: пины PyYAML==6.0.3, Pillow==12.3.0 — обе имеют musllinux-колёса cp312 (x86_64/aarch64), компилятор не нужен - busybox adduser вместо useradd --- docker-compose/docker/Dockerfile.mcp | 10 +++++----- requirements-mcp.txt | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docker-compose/docker/Dockerfile.mcp b/docker-compose/docker/Dockerfile.mcp index f527420..19a1863 100644 --- a/docker-compose/docker/Dockerfile.mcp +++ b/docker-compose/docker/Dockerfile.mcp @@ -1,4 +1,4 @@ -FROM python:3.12-slim +FROM python:3.12-alpine ARG PIP_INDEX_URL=https://pypi.org/simple ARG REVISION=unknown @@ -19,9 +19,9 @@ ENV V8STD_REPO_ROOT=/opt/v8std \ WORKDIR /opt/v8std -RUN apt-get update \ - && apt-get install -y --no-install-recommends fonts-dejavu-core \ - && rm -rf /var/lib/apt/lists/* +RUN apk add --no-cache font-dejavu \ + && mkdir -p /usr/share/fonts/truetype \ + && ln -s /usr/share/fonts/dejavu /usr/share/fonts/truetype/dejavu COPY requirements-mcp.txt /opt/v8std/requirements-mcp.txt COPY scripts /opt/v8std/scripts @@ -36,7 +36,7 @@ RUN chmod +x /opt/v8std/scripts/run_v8std_mcp.sh \ --pages docs/ai/pages.jsonl \ --output docs/ai/search-vectors.jsonl -RUN useradd --system --create-home v8std \ +RUN adduser -S -D -H v8std \ && chown -R v8std:v8std /opt/v8std USER v8std diff --git a/requirements-mcp.txt b/requirements-mcp.txt index f7171dd..6003d39 100644 --- a/requirements-mcp.txt +++ b/requirements-mcp.txt @@ -1,3 +1,3 @@ mcp==1.27.0 -PyYAML -Pillow +PyYAML==6.0.3 +Pillow==12.3.0 From bc0b60b7a4d0182d20b220825366222c302024fc Mon Sep 17 00:00:00 2001 From: Aleksandr Date: Tue, 1 Sep 2026 16:03:07 +0500 Subject: [PATCH 3/9] =?UTF-8?q?refactor:=20=D1=83=D0=B1=D1=80=D0=B0=D1=82?= =?UTF-8?q?=D1=8C=20=D1=88=D1=80=D0=B8=D1=84=D1=82=D1=8B=20=D0=B8=D0=B7=20?= =?UTF-8?q?MCP-=D0=BE=D0=B1=D1=80=D0=B0=D0=B7=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Соцкарточки в self-contained образе не рендерятся: индекс запечён на этапе сборки, режим always не поддерживается. Pillow остаётся — нужен генератору индекса на импорте. --- docker-compose/docker/Dockerfile.mcp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docker-compose/docker/Dockerfile.mcp b/docker-compose/docker/Dockerfile.mcp index 19a1863..19e320a 100644 --- a/docker-compose/docker/Dockerfile.mcp +++ b/docker-compose/docker/Dockerfile.mcp @@ -19,10 +19,6 @@ ENV V8STD_REPO_ROOT=/opt/v8std \ WORKDIR /opt/v8std -RUN apk add --no-cache font-dejavu \ - && mkdir -p /usr/share/fonts/truetype \ - && ln -s /usr/share/fonts/dejavu /usr/share/fonts/truetype/dejavu - COPY requirements-mcp.txt /opt/v8std/requirements-mcp.txt COPY scripts /opt/v8std/scripts COPY docs /opt/v8std/docs From bc175b1643b83bf3f12ac96b1eeec83e279c44a7 Mon Sep 17 00:00:00 2001 From: Aleksandr Date: Tue, 1 Sep 2026 16:04:51 +0500 Subject: [PATCH 4/9] =?UTF-8?q?fix:=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D1=82?= =?UTF-8?q?=D1=8C=20=D0=B3=D1=80=D1=83=D0=BF=D0=BF=D1=83=20v8std=20=D0=BF?= =?UTF-8?q?=D0=B5=D1=80=D0=B5=D0=B4=20=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D1=82=D0=B5=D0=BB=D0=B5=D0=BC=20(busybox=20adduser?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose/docker/Dockerfile.mcp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker-compose/docker/Dockerfile.mcp b/docker-compose/docker/Dockerfile.mcp index 19e320a..19f6315 100644 --- a/docker-compose/docker/Dockerfile.mcp +++ b/docker-compose/docker/Dockerfile.mcp @@ -32,7 +32,8 @@ RUN chmod +x /opt/v8std/scripts/run_v8std_mcp.sh \ --pages docs/ai/pages.jsonl \ --output docs/ai/search-vectors.jsonl -RUN adduser -S -D -H v8std \ +RUN addgroup -S v8std \ + && adduser -S -D -H -G v8std v8std \ && chown -R v8std:v8std /opt/v8std USER v8std From 8c888b99f9292a29a3a6621338f145bf0bfba241 Mon Sep 17 00:00:00 2001 From: Aleksandr Date: Tue, 1 Sep 2026 16:07:41 +0500 Subject: [PATCH 5/9] =?UTF-8?q?fix:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20bash=20=D0=B2=20MCP-=D0=BE=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D0=B7=20=D0=B8=20=D1=81=D0=BE=D1=85=D1=80=D0=B0=D0=BD=D1=8F?= =?UTF-8?q?=D1=82=D1=8C=20=D0=BB=D0=BE=D0=B3=D0=B8=20=D1=83=D0=BF=D0=B0?= =?UTF-8?q?=D0=B2=D1=88=D0=B5=D0=B3=D0=BE=20smoke-=D0=BA=D0=BE=D0=BD=D1=82?= =?UTF-8?q?=D0=B5=D0=B9=D0=BD=D0=B5=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - run_v8std_mcp.sh — bash-скрипт, в python:3.12-alpine bash отсутствует - smoke: без --rm, чтобы docker logs был доступен после падения (уборка контейнера осталась в trap EXIT) --- docker-compose/docker/Dockerfile.mcp | 2 ++ scripts/build_mcp_image.sh | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docker-compose/docker/Dockerfile.mcp b/docker-compose/docker/Dockerfile.mcp index 19f6315..a0dad4a 100644 --- a/docker-compose/docker/Dockerfile.mcp +++ b/docker-compose/docker/Dockerfile.mcp @@ -19,6 +19,8 @@ ENV V8STD_REPO_ROOT=/opt/v8std \ WORKDIR /opt/v8std +RUN apk add --no-cache bash + COPY requirements-mcp.txt /opt/v8std/requirements-mcp.txt COPY scripts /opt/v8std/scripts COPY docs /opt/v8std/docs diff --git a/scripts/build_mcp_image.sh b/scripts/build_mcp_image.sh index d94fb6a..34c07de 100755 --- a/scripts/build_mcp_image.sh +++ b/scripts/build_mcp_image.sh @@ -109,7 +109,7 @@ fi if [ "${SMOKE}" -eq 1 ]; then SMOKE_NAME="v8std-mcp-smoke-$$" - docker run -d --rm --name "${SMOKE_NAME}" \ + docker run -d --name "${SMOKE_NAME}" \ -p 127.0.0.1:8765:8765 \ "${IMAGE}:${LOCAL_TAG}" >/dev/null ok=0 From 913ae6e1de51c46f7041ff76e6f2544e9641d822 Mon Sep 17 00:00:00 2001 From: Aleksandr Date: Tue, 1 Sep 2026 16:25:01 +0500 Subject: [PATCH 6/9] =?UTF-8?q?fix:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20setup-qemu-action=20=D0=B2=20docker-publish=20wo?= =?UTF-8?q?rkflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/docker-publish.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index a2f2190..0d17b14 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -24,6 +24,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v5 + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 - name: Set up Buildx uses: docker/setup-buildx-action@v3 - name: Log in to Docker Hub From 73ea2128d2e56f9d809a251a241c0200f64854e2 Mon Sep 17 00:00:00 2001 From: Aleksandr Date: Tue, 1 Sep 2026 16:29:18 +0500 Subject: [PATCH 7/9] =?UTF-8?q?refactor:=20=D0=B2=D1=8B=D0=BD=D0=B5=D1=81?= =?UTF-8?q?=D1=82=D0=B8=20MCP-=D0=BE=D0=B1=D1=80=D0=B0=D0=B7=20=D0=B2=20do?= =?UTF-8?q?cker-compose-mcp-only,=20=D0=B2=D0=BE=D1=81=D1=81=D1=82=D0=B0?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2=D0=B8=D1=82=D1=8C=20docker-compose?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - docker-compose/ возвращён к исходному состоянию (стек docs+MCP с монтированием репозитория не меняется) - docker-compose-mcp-only/: отдельный compose, Dockerfile.mcp и .dockerignore для самодостаточного образа из Docker Hub - пути обновлены в scripts/build_mcp_image.sh, workflow и документации --- .github/workflows/docker-publish.yml | 4 ++-- README.md | 11 +++++++++-- docker-compose-mcp-only/docker-compose.yml | 18 ++++++++++++++++++ .../docker/Dockerfile.mcp | 0 .../docker/Dockerfile.mcp.dockerignore | 0 docker-compose/docker-compose.yml | 7 +++++-- docs/support.md | 6 ++++++ scripts/build_mcp_image.sh | 2 +- 8 files changed, 41 insertions(+), 7 deletions(-) create mode 100644 docker-compose-mcp-only/docker-compose.yml rename {docker-compose => docker-compose-mcp-only}/docker/Dockerfile.mcp (100%) rename {docker-compose => docker-compose-mcp-only}/docker/Dockerfile.mcp.dockerignore (100%) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 0d17b14..96d2704 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -10,7 +10,7 @@ on: - "requirements-mcp.txt" - "retrieval-rules.yml" - "zensical.toml" - - "docker-compose/docker/Dockerfile.mcp" + - "docker-compose-mcp-only/**" - ".github/workflows/docker-publish.yml" workflow_dispatch: @@ -46,7 +46,7 @@ jobs: uses: docker/build-push-action@v6 with: context: . - file: docker-compose/docker/Dockerfile.mcp + file: docker-compose-mcp-only/docker/Dockerfile.mcp platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} diff --git a/README.md b/README.md index 9877df3..77eeb22 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,15 @@ https://v8std.ru docker compose -f docker-compose/docker-compose.yml up -d v8std-mcp ``` -MCP-сервер будет доступен на `http://127.0.0.1:8765/mcp`. Если образа -`malikovpro/v8std-mcp` нет локально, он будет собран из репозитория. +MCP-сервер будет доступен на `http://127.0.0.1:8765/mcp` и читает локальный индекс +`docs/ai/pages.jsonl` из смонтированного репозитория. + +Только MCP, без сервера документации — из готового образа Docker Hub (индекс +запечён на этапе сборки, монтирование репозитория не нужно): + +```bash +docker compose -f docker-compose-mcp-only/docker-compose.yml up -d v8std-mcp +``` Запуск без клонирования репозитория: diff --git a/docker-compose-mcp-only/docker-compose.yml b/docker-compose-mcp-only/docker-compose.yml new file mode 100644 index 0000000..e554608 --- /dev/null +++ b/docker-compose-mcp-only/docker-compose.yml @@ -0,0 +1,18 @@ +services: + v8std-mcp: + container_name: v8std-mcp + image: malikovpro/v8std-mcp:latest + build: + context: .. + dockerfile: docker-compose-mcp-only/docker/Dockerfile.mcp + restart: always + ports: + - "127.0.0.1:8765:8765" + cap_drop: + - ALL + security_opt: + - no-new-privileges:true + environment: + V8STD_MCP_GENERATE_INDEX: auto + V8STD_MCP_HOST: 0.0.0.0 + V8STD_MCP_PORT: "8765" diff --git a/docker-compose/docker/Dockerfile.mcp b/docker-compose-mcp-only/docker/Dockerfile.mcp similarity index 100% rename from docker-compose/docker/Dockerfile.mcp rename to docker-compose-mcp-only/docker/Dockerfile.mcp diff --git a/docker-compose/docker/Dockerfile.mcp.dockerignore b/docker-compose-mcp-only/docker/Dockerfile.mcp.dockerignore similarity index 100% rename from docker-compose/docker/Dockerfile.mcp.dockerignore rename to docker-compose-mcp-only/docker/Dockerfile.mcp.dockerignore diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index 6e91fb9..39a0a96 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -14,10 +14,9 @@ services: v8std-mcp: container_name: v8std-mcp - image: malikovpro/v8std-mcp:latest build: context: .. - dockerfile: docker-compose/docker/Dockerfile.mcp + dockerfile: docker-compose/docker/Dockerfile restart: always ports: - "127.0.0.1:8765:8765" @@ -25,7 +24,11 @@ services: - ALL security_opt: - no-new-privileges:true + volumes: + - ../:/docs + working_dir: /docs environment: V8STD_MCP_GENERATE_INDEX: auto V8STD_MCP_HOST: 0.0.0.0 V8STD_MCP_PORT: "8765" + entrypoint: ["/opt/v8std/scripts/run_v8std_mcp.sh"] diff --git a/docs/support.md b/docs/support.md index 5a2069e..8f780b5 100644 --- a/docs/support.md +++ b/docs/support.md @@ -100,6 +100,12 @@ cd v8std docker compose -f docker-compose/docker-compose.yml up -d v8std-mcp ``` +Только MCP, без сервера документации — из готового образа Docker Hub: + +```bash +docker compose -f docker-compose-mcp-only/docker-compose.yml up -d v8std-mcp +``` + Можно обойтись без клонирования репозитория. Готовый образ содержит базу стандартов и индекс поиска: diff --git a/scripts/build_mcp_image.sh b/scripts/build_mcp_image.sh index 34c07de..c6363f6 100755 --- a/scripts/build_mcp_image.sh +++ b/scripts/build_mcp_image.sh @@ -57,7 +57,7 @@ REVISION="$(git -C "${REPO_ROOT}" rev-parse HEAD)" CREATED="$(date -u +%Y-%m-%dT%H:%M:%SZ)" DATE_TAG="$(date -u +%Y-%m-%d)" SHA_TAG="sha-$(git -C "${REPO_ROOT}" rev-parse --short=12 HEAD)" -DOCKERFILE="docker-compose/docker/Dockerfile.mcp" +DOCKERFILE="docker-compose-mcp-only/docker/Dockerfile.mcp" BUILDER="" SMOKE_NAME="" From 95827b8a92f1f392700298f1ab1b6da098d5bd95 Mon Sep 17 00:00:00 2001 From: Aleksandr Date: Tue, 1 Sep 2026 16:43:04 +0500 Subject: [PATCH 8/9] =?UTF-8?q?feat:=20=D0=BD=D0=B0=D1=81=D1=82=D1=80?= =?UTF-8?q?=D0=BE=D0=B9=D0=BA=D0=B0=20=D0=B4=D0=BE=D1=81=D1=82=D1=83=D0=BF?= =?UTF-8?q?=D0=B0=20=D0=BA=20MCP=20=D0=BD=D0=B5=20=D1=82=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D0=BA=D0=BE=20=D1=81=20localhost?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - run_v8std_mcp.sh: env V8STD_MCP_ALLOWED_HOSTS/V8STD_MCP_ALLOWED_ORIGINS дополняют дефолтные списки allowed-host/allowed-origin (защита от DNS-rebinding остается включенной) - compose: публикация порта через V8STD_MCP_BIND (по умолчанию 127.0.0.1) - документация: раздел про доступ из локальной сети --- docker-compose-mcp-only/docker-compose.yml | 4 +++- docs/support.md | 18 ++++++++++++++++++ scripts/run_v8std_mcp.sh | 15 ++++++++++++++- 3 files changed, 35 insertions(+), 2 deletions(-) mode change 100644 => 100755 scripts/run_v8std_mcp.sh diff --git a/docker-compose-mcp-only/docker-compose.yml b/docker-compose-mcp-only/docker-compose.yml index e554608..98f2d91 100644 --- a/docker-compose-mcp-only/docker-compose.yml +++ b/docker-compose-mcp-only/docker-compose.yml @@ -7,7 +7,7 @@ services: dockerfile: docker-compose-mcp-only/docker/Dockerfile.mcp restart: always ports: - - "127.0.0.1:8765:8765" + - "${V8STD_MCP_BIND:-127.0.0.1}:8765:8765" cap_drop: - ALL security_opt: @@ -16,3 +16,5 @@ services: V8STD_MCP_GENERATE_INDEX: auto V8STD_MCP_HOST: 0.0.0.0 V8STD_MCP_PORT: "8765" + V8STD_MCP_ALLOWED_HOSTS: "${V8STD_MCP_ALLOWED_HOSTS:-}" + V8STD_MCP_ALLOWED_ORIGINS: "${V8STD_MCP_ALLOWED_ORIGINS:-}" diff --git a/docs/support.md b/docs/support.md index 8f780b5..2061048 100644 --- a/docs/support.md +++ b/docs/support.md @@ -118,6 +118,24 @@ docker run -d --name v8std-mcp -p 127.0.0.1:8765:8765 malikovpro/v8std-mcp:lates зафиксировать конкретную версию. Актуальность базы в запущенном контейнере зависит от даты сборки образа. +По умолчанию сервер принимает запросы только с `localhost` — это защита от +DNS-rebinding: без нее страница злоумышленника в браузере могла бы обращаться +к вашему локальному MCP и читать ответы, включая отправленные фрагменты кода. + +Доступ из локальной сети — добавьте в окружение адреса, по которым к серверу +будут обращаться, и опубликуйте порт на нужном интерфейсе: + +```bash +V8STD_MCP_ALLOWED_HOSTS="192.168.1.55:*, v8std.local:*" \ +V8STD_MCP_BIND=0.0.0.0 \ +docker compose -f docker-compose-mcp-only/docker-compose.yml up -d v8std-mcp +``` + +Паттерны поддерживают точный адрес (`192.168.1.55:*`) и суффикс `:*` +(любой порт). Вайлдкарды в середине адреса (`192.168.1.*`) не поддерживаются — +перечисляйте адреса явно. Подключение: `http://192.168.1.55:8765/mcp` или +`http://v8std.local:8765/mcp`. + Локальный адрес MCP: ```text diff --git a/scripts/run_v8std_mcp.sh b/scripts/run_v8std_mcp.sh old mode 100644 new mode 100755 index 4a2b01e..cf18e59 --- a/scripts/run_v8std_mcp.sh +++ b/scripts/run_v8std_mcp.sh @@ -31,6 +31,18 @@ if [ ! -s "${PAGES_PATH}" ]; then exit 1 fi +extra_transport_args=() +if [ -n "${V8STD_MCP_ALLOWED_HOSTS:-}" ]; then + for pattern in ${V8STD_MCP_ALLOWED_HOSTS//,/ }; do + extra_transport_args+=(--allowed-host "${pattern}") + done +fi +if [ -n "${V8STD_MCP_ALLOWED_ORIGINS:-}" ]; then + for pattern in ${V8STD_MCP_ALLOWED_ORIGINS//,/ }; do + extra_transport_args+=(--allowed-origin "${pattern}") + done +fi + exec python "${SCRIPT_DIR}/v8std_mcp_server.py" \ --pages "${PAGES_PATH}" \ --vectors "${VECTORS_PATH}" \ @@ -42,4 +54,5 @@ exec python "${SCRIPT_DIR}/v8std_mcp_server.py" \ --allowed-host "localhost:*" \ --allowed-host "v8std-mcp:*" \ --allowed-origin "http://127.0.0.1:*" \ - --allowed-origin "http://localhost:*" + --allowed-origin "http://localhost:*" \ + "${extra_transport_args[@]}" From 0fa292f654a3ca014a2040bf81aeff8ec7d1227d Mon Sep 17 00:00:00 2001 From: Aleksandr Date: Tue, 1 Sep 2026 16:50:34 +0500 Subject: [PATCH 9/9] =?UTF-8?q?docs:=20=D0=BF=D1=80=D0=B8=D0=BC=D0=B5?= =?UTF-8?q?=D1=80=20.env=20=D0=B4=D0=BB=D1=8F=20docker-compose-mcp-only=20?= =?UTF-8?q?=D0=B8=20=D0=B8=D0=B3=D0=BD=D0=BE=D1=80=20.env?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose-mcp-only/.env.example | 15 +++++++++++++++ docker-compose-mcp-only/.gitignore | 1 + 2 files changed, 16 insertions(+) create mode 100644 docker-compose-mcp-only/.env.example create mode 100644 docker-compose-mcp-only/.gitignore diff --git a/docker-compose-mcp-only/.env.example b/docker-compose-mcp-only/.env.example new file mode 100644 index 0000000..c3b9d92 --- /dev/null +++ b/docker-compose-mcp-only/.env.example @@ -0,0 +1,15 @@ +# Скопируйте в .env рядом с docker-compose.yml: cp .env.example .env +# Значения из .env подставляются в docker-compose-mcp-only/docker-compose.yml. + +# Адреса, по которым к серверу обращаются из сети (проверяется Host-заголовок). +# Формат: точный адрес или хост с любым портом (хост:*), разделитель — запятая +# или пробел. Вайлдкарды в середине адреса (192.168.1.*) не поддерживаются. +# V8STD_MCP_ALLOWED_HOSTS=192.168.1.55:*, v8std.local:* + +# Origin-заголовки браузерных клиентов; редакторам (codex, claude, cursor) +# обычно не требуется — Origin у них отсутствует и разрешен. +# V8STD_MCP_ALLOWED_ORIGINS=http://192.168.1.55:8765 + +# Интерфейс публикации порта 8765: 127.0.0.1 (по умолчанию — только локально), +# 0.0.0.0 (все интерфейсы) или конкретный IP сервера. +# V8STD_MCP_BIND=0.0.0.0 diff --git a/docker-compose-mcp-only/.gitignore b/docker-compose-mcp-only/.gitignore new file mode 100644 index 0000000..4c49bd7 --- /dev/null +++ b/docker-compose-mcp-only/.gitignore @@ -0,0 +1 @@ +.env