diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..07262b8 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,70 @@ +# Git +.git +.gitignore + +# Python +__pycache__ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# Virtual environments +.venv +venv/ +ENV/ +env/ + +# IDE +.idea/ +.vscode/ +.cursor/ +*.swp +*.swo +*~ + +# Testing +.pytest_cache/ +.coverage +htmlcov/ +.tox/ + +# Documentation +docs/_build/ +*.md +!README.md + +# CI/CD +.github/ +.circleci/ + +# OS +.DS_Store +Thumbs.db + +# Environment +.env +.env.local + +# Examples and tests +examples/ +tests/ + +# Helm charts +helm/ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..704e5f3 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,138 @@ +name: CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + lint: + runs-on: ubuntu-latest + name: Lint with Ruff + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.13' + + - name: Install uv + uses: astral-sh/setup-uv@v4 + with: + enable-cache: true + + - name: Install dependencies + run: uv sync --all-groups + + - name: Run ruff check + run: uv run ruff check . + + - name: Run ruff format check + run: uv run ruff format --check . + + test: + runs-on: ubuntu-latest + name: Run Tests + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.13' + + - name: Install uv + uses: astral-sh/setup-uv@v4 + with: + enable-cache: true + + - name: Install dependencies + run: uv sync --all-groups + + - name: Run tests + run: uv run pytest tests/ + + helm-validate: + runs-on: ubuntu-latest + name: Validate Helm Chart + if: github.event_name == 'push' + + steps: + - uses: actions/checkout@v4 + + - name: Set up Helm + uses: azure/setup-helm@v4 + with: + version: latest + + - name: Validate default values + run: | + echo "Validating helm/mcp-server/values.yaml..." + helm template mcp-server ./helm/mcp-server \ + --values ./helm/mcp-server/values.yaml + + - name: Validate production values + run: | + echo "Validating helm/mcp-server/examples/values-production.yaml..." + helm template mcp-server ./helm/mcp-server \ + --values ./helm/mcp-server/examples/values-production.yaml + + - name: Validate secops values + run: | + echo "Validating helm/mcp-server/examples/values-secops.yaml..." + helm template mcp-server ./helm/mcp-server \ + --values ./helm/mcp-server/examples/values-secops.yaml + + - name: Lint Helm Chart + run: | + echo "Linting Helm chart..." + helm lint ./helm/mcp-server \ + --strict + + helm-unittest: + runs-on: ubuntu-latest + name: Run Helm Unit Tests + if: github.event_name == 'push' + + steps: + - uses: actions/checkout@v4 + + - name: Set up Helm + uses: azure/setup-helm@v4 + with: + version: latest + + - name: Install Helm Unittest Plugin + run: | + helm plugin install https://github.com/helm-unittest/helm-unittest.git + + - name: Run Helm unit tests + run: | + echo "Running Helm unit tests..." + helm unittest ./helm/mcp-server + + docker-build: + runs-on: ubuntu-latest + name: Build Docker Image + if: github.event_name == 'pull_request' + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build Docker image + uses: docker/build-push-action@v6 + with: + context: . + file: ./Dockerfile + push: false + platforms: linux/amd64,linux/arm64 + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index d5094a0..0000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,206 +0,0 @@ ---- -name: Publish to PyPI and MCP Registry - -on: - push: - tags: - - 'v*' - workflow_dispatch: - inputs: - publish_pypi: - description: 'Publish to PyPI' - type: boolean - default: true - publish_mcp_registry: - description: 'Publish to MCP Registry' - type: boolean - default: true - -jobs: - publish-pypi: - name: Publish to PyPI - if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.publish_pypi) - runs-on: ubuntu-latest - timeout-minutes: 10 - permissions: - id-token: write # Required for trusted publishing - contents: read - environment: - name: pypi - url: https://test.pypi.org/project/ggmcp/ - concurrency: - group: publish-pypi-${{ github.ref_name }} - cancel-in-progress: false - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.13' - - - name: Install uv - uses: astral-sh/setup-uv@v5 - with: - enable-cache: true - - - name: Build package - run: | - echo "Building ggmcp package..." - uv build - echo "Build artifacts:" - ls -lh dist/ - - - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - repository-url: https://test.pypi.org/legacy/ - packages-dir: dist/ - print-hash: true - - - name: Create summary - run: | - echo "## ✅ Test PyPI Publication Successful" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Package published to: https://test.pypi.org/project/ggmcp/" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Install with:" >> $GITHUB_STEP_SUMMARY - echo '```bash' >> $GITHUB_STEP_SUMMARY - echo "pip install --index-url https://test.pypi.org/simple/ ggmcp" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - - publish-mcp-registry: - name: Publish to MCP Registry - needs: publish-pypi - if: | - always() && - (needs.publish-pypi.result == 'success' || needs.publish-pypi.result == 'skipped') && - (github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.publish_mcp_registry)) - runs-on: ubuntu-latest - timeout-minutes: 10 - permissions: - contents: read - concurrency: - group: publish-mcp-registry-${{ github.ref_name }} - cancel-in-progress: false - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Verify server.json - run: | - echo "Validating server.json..." - if [ ! -f "server.json" ]; then - echo "❌ Error: server.json not found" - exit 1 - fi - - # Basic JSON validation - if ! python3 -m json.tool server.json > /dev/null; then - echo "❌ Error: server.json is not valid JSON" - exit 1 - fi - - echo "✅ server.json is valid" - echo "" - echo "Configuration:" - python3 -c " - import json - with open('server.json') as f: - config = json.load(f) - print(f\" Name: {config['name']}\") - print(f\" Version: {config['version']}\") - print(f\" Description: {config['description']}\") - if config.get('packages'): - pkg = config['packages'][0] - print(f\" Package: {pkg['identifier']} (via {pkg['registryType']})\") - " - - - name: Install mcp-publisher - run: | - echo "Installing mcp-publisher..." - - # Fetch the latest release version from GitHub API - echo "Fetching latest mcp-publisher version..." - PUBLISHER_VERSION=$(curl -sL https://api.github.com/repos/modelcontextprotocol/mcp-publisher/releases/latest | jq -r '.tag_name' | sed 's/^v//') - - if [ -z "$PUBLISHER_VERSION" ] || [ "$PUBLISHER_VERSION" = "null" ]; then - echo "❌ Error: Failed to fetch latest mcp-publisher version" - exit 1 - fi - - echo "Latest version: v${PUBLISHER_VERSION}" - - # Download mcp-publisher CLI - DOWNLOAD_URL="https://github.com/modelcontextprotocol/mcp-publisher/releases/download/v${PUBLISHER_VERSION}/mcp-publisher-linux-amd64" - echo "Downloading from: ${DOWNLOAD_URL}" - - if ! wget -q "$DOWNLOAD_URL" -O mcp-publisher; then - echo "❌ Error: Failed to download mcp-publisher" - exit 1 - fi - - # Verify download succeeded and file is not empty - if [ ! -s mcp-publisher ]; then - echo "❌ Error: Downloaded file is empty or does not exist" - exit 1 - fi - - chmod +x mcp-publisher - - # Verify the binary works - if ! ./mcp-publisher --version; then - echo "❌ Error: mcp-publisher binary is not working" - exit 1 - fi - - echo "✅ Successfully installed mcp-publisher v${PUBLISHER_VERSION}" - - - name: Authenticate with GitHub - run: | - echo "Authenticating with GitHub using OIDC..." - # mcp-publisher should support GitHub Actions OIDC - # The GITHUB_TOKEN is automatically available - echo "GITHUB_TOKEN is available: ${{ secrets.GITHUB_TOKEN != '' }}" - - - name: Publish to MCP Registry - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - echo "Publishing to MCP Registry..." - - # Use GitHub Actions OIDC for authentication - # The mcp-publisher should detect we're in GitHub Actions - ./mcp-publisher publish --non-interactive - - echo "✅ Successfully published to MCP Registry" - - - name: Create summary - run: | - echo "## ✅ MCP Registry Publication Successful" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Server registered in: https://github.com/modelcontextprotocol/registry" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Users can install with:" >> $GITHUB_STEP_SUMMARY - echo '```bash' >> $GITHUB_STEP_SUMMARY - echo "uvx ggmcp" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - - create-release: - name: Create GitHub Release - needs: [publish-pypi, publish-mcp-registry] - if: github.event_name == 'push' && github.ref_type == 'tag' - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Create Release - uses: softprops/action-gh-release@v2 - with: - draft: false - generate_release_notes: true - make_latest: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..c60e424 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,324 @@ +name: Release + +on: + push: + branches: + - main + tags: + - 'v*.*.*' + workflow_dispatch: + +env: + IMAGE_NAME: secops-mcp-server + REGISTRY: ghcr.io + +jobs: + detect-changes: + runs-on: ubuntu-latest + name: Detect Changes + outputs: + chart-changed: ${{ steps.detect.outputs.chart-changed }} + version-bump: ${{ steps.detect.outputs.version-bump }} + release-type: ${{ steps.detect.outputs.release-type }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Detect changes + id: detect + run: | + # Check if chart files have changed + if git diff HEAD~1 HEAD --name-only | grep -q "^helm/"; then + echo "chart-changed=true" >> $GITHUB_OUTPUT + else + echo "chart-changed=false" >> $GITHUB_OUTPUT + fi + + # Determine release type based on commit message or tag + if [[ "${{ github.ref }}" == refs/tags/* ]]; then + echo "release-type=tag" >> $GITHUB_OUTPUT + TAG="${{ github.ref_name }}" + if [[ $TAG =~ ^v([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then + echo "version-bump=${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.${BASH_REMATCH[3]}" >> $GITHUB_OUTPUT + fi + else + # Check commit message for semver directives + COMMIT_MSG=$(git log -1 --pretty=%B) + if echo "$COMMIT_MSG" | grep -qi "BREAKING\|major"; then + echo "release-type=major" >> $GITHUB_OUTPUT + elif echo "$COMMIT_MSG" | grep -qi "feat\|minor"; then + echo "release-type=minor" >> $GITHUB_OUTPUT + elif echo "$COMMIT_MSG" | grep -qi "fix\|patch"; then + echo "release-type=patch" >> $GITHUB_OUTPUT + else + echo "release-type=patch" >> $GITHUB_OUTPUT + fi + fi + + build-and-push-docker: + runs-on: ubuntu-latest + name: Build and Push Docker Image + if: github.event_name == 'push' && (github.ref_type == 'tag' || github.ref == 'refs/heads/main') + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=raw,value=latest,enable={{is_default_branch}} + type=sha,prefix={{branch}}- + + - name: Build and push Docker image + uses: docker/build-push-action@v6 + with: + context: . + file: ./Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + platforms: linux/amd64,linux/arm64 + + publish-helm-chart: + runs-on: ubuntu-latest + name: Publish Helm Chart + needs: [detect-changes, build-and-push-docker] + if: | + always() && + (github.event_name == 'push' && (github.ref_type == 'tag' || github.ref == 'refs/heads/main')) && + (needs.detect-changes.outputs.chart-changed == 'true' || github.ref_type == 'tag') + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Helm + uses: azure/setup-helm@v4 + with: + version: latest + + - name: Update Chart.yaml with image info + if: github.event_name == 'push' && github.ref_type == 'tag' + run: | + TAG="${{ github.ref_name }}" + # Update appVersion in Chart.yaml + sed -i "s/appVersion:.*/appVersion: \"${TAG#v}\"/" ./helm/mcp-server/Chart.yaml + sed -i "s/version:.*/version: \"${TAG#v}\"/" ./helm/mcp-server/Chart.yaml + echo "Updated Chart.yaml with version ${TAG#v}" + cat ./helm/mcp-server/Chart.yaml + + - name: Validate Helm Chart + run: | + helm lint ./helm/mcp-server --strict + helm template mcp-server ./helm/mcp-server --values ./helm/mcp-server/values.yaml + + - name: Package Helm Chart + id: package + run: | + helm package ./helm/mcp-server --destination /tmp/helm-charts + CHART_FILE=$(ls /tmp/helm-charts/*.tgz | head -1) + echo "chart-file=$(basename $CHART_FILE)" >> $GITHUB_OUTPUT + echo "chart-path=$CHART_FILE" >> $GITHUB_OUTPUT + echo "Chart packaged: $(basename $CHART_FILE)" + + - name: Push Helm Chart to OCI Registry + run: | + helm registry login ${{ env.REGISTRY }} \ + -u ${{ github.actor }} \ + -p ${{ secrets.GITHUB_TOKEN }} + + CHART_VERSION=$(grep "^version:" ./helm/mcp-server/Chart.yaml | cut -d' ' -f2 | tr -d '"') + + helm push "${{ steps.package.outputs.chart-path }}" \ + oci://${{ env.REGISTRY }}/${{ github.repository_owner }}/helm-charts + + echo "✅ Chart published to: ${{ env.REGISTRY }}/${{ github.repository_owner }}/helm-charts:${CHART_VERSION}" + + - name: Create summary + run: | + CHART_VERSION=$(grep "^version:" ./helm/mcp-server/Chart.yaml | cut -d' ' -f2 | tr -d '"') + echo "## ✅ Helm Chart Released" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Chart: gitguardian-secops-mcp-server" >> $GITHUB_STEP_SUMMARY + echo "Version: ${CHART_VERSION}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Published to: \`oci://${{ env.REGISTRY }}/${{ github.repository_owner }}/helm-charts\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Install with:" >> $GITHUB_STEP_SUMMARY + echo '```bash' >> $GITHUB_STEP_SUMMARY + echo "helm pull oci://${{ env.REGISTRY }}/${{ github.repository_owner }}/helm-charts/gitguardian-secops-mcp-server --version ${CHART_VERSION}" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + + publish-to-pypi: + runs-on: ubuntu-latest + name: Publish to PyPI + if: false && github.event_name == 'push' && github.ref_type == 'tag' + timeout-minutes: 10 + permissions: + id-token: write + contents: read + environment: + name: pypi + url: https://test.pypi.org/project/ggmcp/ + concurrency: + group: publish-pypi-${{ github.ref_name }} + cancel-in-progress: false + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.13' + + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + enable-cache: true + + - name: Build package + run: | + echo "Building ggmcp package..." + uv build + echo "Build artifacts:" + ls -lh dist/ + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ + packages-dir: dist/ + print-hash: true + + - name: Create summary + run: | + echo "## ✅ PyPI Publication Successful" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Package published to: https://test.pypi.org/project/ggmcp/" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Install with:" >> $GITHUB_STEP_SUMMARY + echo '```bash' >> $GITHUB_STEP_SUMMARY + echo "pip install --index-url https://test.pypi.org/simple/ ggmcp" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + + publish-to-mcp-registry: + runs-on: ubuntu-latest + name: Publish to MCP Registry + needs: [publish-to-pypi] + if: | + false && + always() && + github.event_name == 'push' && + github.ref_type == 'tag' && + (needs.publish-to-pypi.result == 'success' || needs.publish-to-pypi.result == 'skipped') + timeout-minutes: 10 + permissions: + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Verify server.json + run: | + echo "Validating server.json..." + if [ ! -f "server.json" ]; then + echo "❌ Error: server.json not found" + exit 1 + fi + + if ! python3 -m json.tool server.json > /dev/null; then + echo "❌ Error: server.json is not valid JSON" + exit 1 + fi + + echo "✅ server.json is valid" + + - name: Install mcp-publisher + run: | + echo "Installing mcp-publisher..." + PUBLISHER_VERSION=$(curl -sL https://api.github.com/repos/modelcontextprotocol/mcp-publisher/releases/latest | jq -r '.tag_name' | sed 's/^v//') + + if [ -z "$PUBLISHER_VERSION" ] || [ "$PUBLISHER_VERSION" = "null" ]; then + echo "❌ Error: Failed to fetch latest mcp-publisher version" + exit 1 + fi + + DOWNLOAD_URL="https://github.com/modelcontextprotocol/mcp-publisher/releases/download/v${PUBLISHER_VERSION}/mcp-publisher-linux-amd64" + if ! wget -q "$DOWNLOAD_URL" -O mcp-publisher; then + echo "❌ Error: Failed to download mcp-publisher" + exit 1 + fi + + chmod +x mcp-publisher + ./mcp-publisher --version + echo "✅ Successfully installed mcp-publisher v${PUBLISHER_VERSION}" + + - name: Publish to MCP Registry + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + echo "Publishing to MCP Registry..." + ./mcp-publisher publish --non-interactive + echo "✅ Successfully published to MCP Registry" + + - name: Create summary + run: | + echo "## ✅ MCP Registry Publication Successful" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Server registered in: https://github.com/modelcontextprotocol/registry" >> $GITHUB_STEP_SUMMARY + + create-github-release: + runs-on: ubuntu-latest + name: Create GitHub Release + needs: [publish-helm-chart, publish-to-pypi] + if: | + always() && + github.event_name == 'push' && + github.ref_type == 'tag' && + (needs.publish-helm-chart.result == 'success' || needs.publish-helm-chart.result == 'skipped') && + (needs.publish-to-pypi.result == 'success' || needs.publish-to-pypi.result == 'skipped') + permissions: + contents: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Create Release + uses: softprops/action-gh-release@v2 + with: + draft: false + generate_release_notes: true + make_latest: true diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 3e28796..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: Tests - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - lint: - runs-on: ubuntu-latest - name: Lint with Ruff - - steps: - - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.13' - - - name: Install uv - uses: astral-sh/setup-uv@v4 - with: - enable-cache: true - - - name: Install dependencies - run: uv sync --all-groups - - - name: Run ruff check - run: uv run ruff check . - - - name: Run ruff format check - run: uv run ruff format --check . - - test: - runs-on: ubuntu-latest - name: Run Tests - - steps: - - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.13' - - - name: Install uv - uses: astral-sh/setup-uv@v4 - with: - enable-cache: true - - - name: Install dependencies - run: uv sync --all-groups - - - name: Run tests - run: uv run pytest tests/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d310f13 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,68 @@ +# Multi-stage Dockerfile for GitGuardian MCP Server +# This Dockerfile creates a production-ready container image for the MCP server +# +# Build approach: Builds Python wheels from source, then installs them in production stage. +# This ensures parity between Docker builds and PyPI package distribution. + +FROM python:3.13-slim AS builder + +# Install uv for fast package management +COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv + +# Set working directory +WORKDIR /app + +# Copy project files needed for building +COPY pyproject.toml uv.lock ./ +COPY packages ./packages + +# Build wheels for all workspace packages +# This creates distributable .whl files that can be installed anywhere +RUN uv build --package gg-api-core --out-dir /dist && \ + uv build --package developer-mcp-server --out-dir /dist && \ + uv build --package secops-mcp-server --out-dir /dist + +# Production stage +FROM python:3.13-slim + +# Install runtime dependencies +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# Copy uv from builder +COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv + +# Create non-root user +RUN useradd -m -u 1000 -s /bin/bash mcpserver + +# Set working directory +WORKDIR /app + +# Copy built wheels from builder stage +COPY --from=builder /dist/*.whl /tmp/wheels/ + +# Install all packages from wheels +# Using --system to install globally (not in a venv) since this is a container +RUN uv pip install --system /tmp/wheels/*.whl && \ + rm -rf /tmp/wheels + +# Switch to non-root user +USER mcpserver + +# Expose MCP server port +EXPOSE 8000 + +# Set environment variables +ENV PYTHONUNBUFFERED=1 \ + MCP_PORT=8000 \ + MCP_HOST=0.0.0.0 \ + ENABLE_LOCAL_OAUTH=false + +# Health check +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD python -c "import httpx; httpx.get('http://localhost:8000/health', timeout=5.0)" || exit 1 + +# Default to secops server, can be overridden +CMD ["secops-mcp-server"] diff --git a/helm/mcp-server/.helmignore b/helm/mcp-server/.helmignore new file mode 100644 index 0000000..81a2ada --- /dev/null +++ b/helm/mcp-server/.helmignore @@ -0,0 +1,28 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ +# Testing and CI +.circleci/ +.github/ +*.test +*.md diff --git a/helm/mcp-server/CONTRIBUTING.md b/helm/mcp-server/CONTRIBUTING.md new file mode 100644 index 0000000..91a6b55 --- /dev/null +++ b/helm/mcp-server/CONTRIBUTING.md @@ -0,0 +1,351 @@ +# Contributing to the GitGuardian MCP Server Helm Chart + +Thank you for your interest in contributing to the GitGuardian MCP Server Helm chart! + +## Development Setup + +### Prerequisites + +- Kubernetes cluster (kind, minikube, or similar for local development) +- Helm 3.2.0+ +- kubectl configured to access your cluster +- Docker (for building images) + +### Local Development Cluster + +#### Using kind + +```bash +# Create a kind cluster +kind create cluster --name mcp-dev + +# Verify cluster is running +kubectl cluster-info --context kind-mcp-dev +``` + +#### Using minikube + +```bash +# Start minikube +minikube start + +# Enable ingress addon (if testing ingress) +minikube addons enable ingress +``` + +## Testing the Chart + +### Validate Chart Syntax + +```bash +# Lint the chart +helm lint helm/mcp-server + +# Validate templates render correctly +helm template test helm/mcp-server --debug + +# Render with specific values +helm template test helm/mcp-server -f helm/mcp-server/examples/values-production.yaml +``` + +### Dry Run Installation + +```bash +# Perform a dry run +helm install mcp-server helm/mcp-server \ + --dry-run --debug \ + --set gitguardian.personalAccessToken=test-token +``` + +### Install in Development + +```bash +# Create test secret +kubectl create secret generic gitguardian-pat-test \ + --from-literal=personal-access-token='test-token' + +# Install the chart +helm install mcp-server-test helm/mcp-server \ + --set gitguardian.existingSecret=gitguardian-pat-test \ + --set gitguardian.url=https://dashboard.gitguardian.com + +# Watch the deployment +kubectl get pods -w + +# Check for issues +kubectl describe pod -l app.kubernetes.io/name=gitguardian-mcp-server +``` + +### Testing Upgrades + +```bash +# Make changes to the chart +# Then upgrade +helm upgrade mcp-server-test helm/mcp-server \ + --set gitguardian.existingSecret=gitguardian-pat-test + +# Check rollout status +kubectl rollout status deployment/mcp-server-test + +# View revision history +helm history mcp-server-test +``` + +### Cleanup + +```bash +# Uninstall the release +helm uninstall mcp-server-test + +# Delete the secret +kubectl delete secret gitguardian-pat-test +``` + +## Chart Structure Guidelines + +### File Organization + +``` +helm/mcp-server/ +├── Chart.yaml # Chart metadata +├── values.yaml # Default values +├── templates/ +│ ├── _helpers.tpl # Template helpers +│ ├── deployment.yaml # Main deployment +│ ├── service.yaml # Service definition +│ ├── serviceaccount.yaml +│ ├── secret.yaml # Optional secret +│ ├── ingress.yaml # Optional ingress +│ ├── hpa.yaml # Optional HPA +│ └── NOTES.txt # Post-install notes +├── examples/ # Example configurations +├── README.md # User documentation +├── INSTALL.md # Installation guide +└── DOCKER.md # Docker guide +``` + +### Template Best Practices + +1. **Use helpers for repeated values** + ```yaml + {{- include "mcp-server.fullname" . }} + ``` + +2. **Make resources conditional** + ```yaml + {{- if .Values.ingress.enabled }} + # ingress resource + {{- end }} + ``` + +3. **Validate required values** + ```yaml + {{- if not .Values.gitguardian.existingSecret }} + {{- fail "gitguardian.existingSecret is required" }} + {{- end }} + ``` + +4. **Add annotations for checksums** + ```yaml + checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + ``` + +5. **Use toYaml for complex structures** + ```yaml + {{- toYaml .Values.resources | nindent 12 }} + ``` + +### values.yaml Guidelines + +1. **Organize logically** + - Group related settings + - Use consistent indentation + - Add comments for clarity + +2. **Provide sensible defaults** + ```yaml + resources: + limits: + cpu: 1000m + memory: 512Mi + requests: + cpu: 100m + memory: 128Mi + ``` + +3. **Document all options** + ```yaml + # GitGuardian instance URL + # Examples: + # - US SaaS: https://dashboard.gitguardian.com + url: "https://dashboard.gitguardian.com" + ``` + +## Making Changes + +### Adding a New Template + +1. Create the template file in `templates/` +2. Add corresponding values in `values.yaml` +3. Test the template renders correctly +4. Update documentation + +Example: Adding a PodDisruptionBudget + +```yaml +# templates/pdb.yaml +{{- if .Values.podDisruptionBudget.enabled }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "mcp-server.fullname" . }} +spec: + minAvailable: {{ .Values.podDisruptionBudget.minAvailable }} + selector: + matchLabels: + {{- include "mcp-server.selectorLabels" . | nindent 6 }} +{{- end }} +``` + +```yaml +# values.yaml +podDisruptionBudget: + enabled: false + minAvailable: 1 +``` + +### Modifying Existing Templates + +1. Review the template and understand current behavior +2. Make minimal, focused changes +3. Test with various value combinations +4. Update examples if needed +5. Document breaking changes + +### Version Bumping + +When making changes: + +- **Patch version** (0.1.0 -> 0.1.1): Bug fixes, no breaking changes +- **Minor version** (0.1.0 -> 0.2.0): New features, no breaking changes +- **Major version** (0.1.0 -> 1.0.0): Breaking changes + +Update `Chart.yaml`: +```yaml +version: 0.2.0 # Chart version +appVersion: "0.1.0" # App version +``` + +## Testing Checklist + +Before submitting a PR, verify: + +- [ ] `helm lint` passes without errors +- [ ] `helm template` renders all templates correctly +- [ ] Chart installs successfully in a test cluster +- [ ] All example values files work +- [ ] Documentation is updated +- [ ] NOTES.txt provides helpful information +- [ ] Security context is properly configured +- [ ] Resource limits are reasonable +- [ ] Secrets are handled securely + +## Common Scenarios to Test + +### Different Server Types + +```bash +# Test developer server +helm install test-dev helm/mcp-server \ + --set serverType=developer \ + --set gitguardian.existingSecret=pat + +# Test secops server +helm install test-secops helm/mcp-server \ + --set serverType=secops \ + --set gitguardian.existingSecret=pat +``` + +### With and Without Ingress + +```bash +# Without ingress +helm install test-no-ingress helm/mcp-server \ + --set gitguardian.existingSecret=pat + +# With ingress +helm install test-ingress helm/mcp-server \ + --set ingress.enabled=true \ + --set ingress.hosts[0].host=test.local \ + --set gitguardian.existingSecret=pat +``` + +### Different Image Modes + +```bash +# Git-based installation +helm install test-git helm/mcp-server \ + --set image.useDockerImage=false \ + --set gitguardian.existingSecret=pat + +# Docker image +helm install test-docker helm/mcp-server \ + --set image.useDockerImage=true \ + --set image.repository=your-registry/mcp-server \ + --set gitguardian.existingSecret=pat +``` + +## Documentation + +When adding features, update: + +1. **README.md**: User-facing documentation +2. **INSTALL.md**: Installation instructions +3. **values.yaml**: Inline comments +4. **examples/**: Example configurations +5. **NOTES.txt**: Post-install instructions + +## Submitting Changes + +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Test thoroughly +5. Update documentation +6. Submit a pull request + +### PR Description Template + +```markdown +## Description +Brief description of the changes + +## Type of Change +- [ ] Bug fix +- [ ] New feature +- [ ] Breaking change +- [ ] Documentation update + +## Testing +- [ ] Lint passes +- [ ] Templates render correctly +- [ ] Tested in local cluster +- [ ] Documentation updated + +## Additional Notes +Any additional information +``` + +## Getting Help + +- Open an issue on GitHub +- Check existing issues and PRs +- Review Helm documentation: https://helm.sh/docs/ + +## Code of Conduct + +Please be respectful and constructive in all interactions with the community. + +## License + +By contributing, you agree that your contributions will be licensed under the same license as the project (MIT). diff --git a/helm/mcp-server/Chart.yaml b/helm/mcp-server/Chart.yaml new file mode 100644 index 0000000..76408fd --- /dev/null +++ b/helm/mcp-server/Chart.yaml @@ -0,0 +1,18 @@ +apiVersion: v2 +name: gitguardian-secops-mcp-server +description: A Helm chart for GitGuardian SecOps MCP Server in HTTP mode +type: application +version: 0.1.0 +appVersion: "0.1.0" +keywords: + - gitguardian + - mcp + - security + - secrets-detection + - secops +home: https://github.com/GitGuardian/ggmcp +sources: + - https://github.com/GitGuardian/ggmcp +maintainers: + - name: GitGuardian + email: support@gitguardian.com diff --git a/helm/mcp-server/DOCKER.md b/helm/mcp-server/DOCKER.md new file mode 100644 index 0000000..8cfe81e --- /dev/null +++ b/helm/mcp-server/DOCKER.md @@ -0,0 +1,342 @@ +# Docker Image Guide + +This guide explains how to build and use Docker images for the GitGuardian MCP Server. + +## Overview + +The Helm chart supports two deployment modes: + +1. **Git-based installation** (Default): Uses `uvx` to install the MCP server directly from the Git repository at startup +2. **Pre-built Docker image**: Uses a pre-built container image with the server already installed + +## Building the Docker Image + +### Prerequisites + +- Docker 20.10+ or compatible container runtime +- Access to the GitGuardian MCP repository + +### Build Commands + +#### For Developer MCP Server + +```bash +# Build the image +docker build -t gitguardian-mcp-server:developer . + +# Build with a specific tag +docker build -t gitguardian-mcp-server:0.1.0-developer . + +# For multi-platform builds (ARM64 and AMD64) +docker buildx build --platform linux/amd64,linux/arm64 \ + -t gitguardian-mcp-server:developer . +``` + +#### For SecOps MCP Server + +The default Dockerfile uses the developer server. To use the SecOps server, override the CMD: + +```bash +# Build and override the command +docker build -t gitguardian-mcp-server:secops . + +# Or run with a different command +docker run --env-file .env gitguardian-mcp-server:developer secops-mcp-server +``` + +### Image Structure + +The Dockerfile creates a multi-stage build: + +1. **Builder stage**: Installs dependencies using `uv` +2. **Production stage**: Creates a minimal runtime image with: + - Python 3.13 slim base + - Non-root user (uid: 1000) + - Pre-installed dependencies + - Security hardening (read-only root filesystem compatible) + +### Testing the Image Locally + +```bash +# Create a .env file with your configuration +cat > .env.docker << EOF +MCP_PORT=8000 +MCP_HOST=0.0.0.0 +ENABLE_LOCAL_OAUTH=false +GITGUARDIAN_URL=https://dashboard.gitguardian.com +GITGUARDIAN_PERSONAL_ACCESS_TOKEN=your_pat_here +EOF + +# Run the container +docker run --rm \ + --env-file .env.docker \ + -p 8000:8000 \ + gitguardian-mcp-server:developer + +# Test the server +curl -X POST http://localhost:8000/tools/list \ + -H "Authorization: Bearer your_pat_here" \ + -H "Content-Type: application/json" \ + -d '{}' +``` + +## Pushing to a Container Registry + +### Docker Hub + +```bash +# Tag the image +docker tag gitguardian-mcp-server:developer your-username/gitguardian-mcp-server:developer + +# Login to Docker Hub +docker login + +# Push the image +docker push your-username/gitguardian-mcp-server:developer +``` + +### GitHub Container Registry (GHCR) + +```bash +# Tag for GHCR +docker tag gitguardian-mcp-server:developer ghcr.io/your-org/gitguardian-mcp-server:developer + +# Login to GHCR +echo $GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdin + +# Push the image +docker push ghcr.io/your-org/gitguardian-mcp-server:developer +``` + +### Amazon ECR + +```bash +# Authenticate with ECR +aws ecr get-login-password --region region | docker login --username AWS --password-stdin aws_account_id.dkr.ecr.region.amazonaws.com + +# Tag for ECR +docker tag gitguardian-mcp-server:developer aws_account_id.dkr.ecr.region.amazonaws.com/gitguardian-mcp-server:developer + +# Push to ECR +docker push aws_account_id.dkr.ecr.region.amazonaws.com/gitguardian-mcp-server:developer +``` + +### Google Container Registry (GCR) + +```bash +# Tag for GCR +docker tag gitguardian-mcp-server:developer gcr.io/your-project/gitguardian-mcp-server:developer + +# Configure Docker for GCR +gcloud auth configure-docker + +# Push to GCR +docker push gcr.io/your-project/gitguardian-mcp-server:developer +``` + +## Using the Docker Image with Helm + +### Update values.yaml + +```yaml +image: + registry: ghcr.io # or your registry + repository: your-org/gitguardian-mcp-server + tag: "developer" + pullPolicy: IfNotPresent + useDockerImage: true # Important: enables Docker image mode + +# If using a private registry, add pull secrets +imagePullSecrets: + - name: regcred +``` + +### Create Image Pull Secret (for private registries) + +```bash +kubectl create secret docker-registry regcred \ + --docker-server=ghcr.io \ + --docker-username=your-username \ + --docker-password=your-password \ + --docker-email=your-email \ + --namespace gitguardian +``` + +### Install with Docker Image + +```bash +helm install mcp-server ./helm/mcp-server \ + --namespace gitguardian \ + --set image.registry=ghcr.io \ + --set image.repository=your-org/gitguardian-mcp-server \ + --set image.tag=developer \ + --set image.useDockerImage=true \ + --set gitguardian.existingSecret=gitguardian-pat +``` + +## Comparison: Git-based vs Docker Image + +### Git-based Installation (Default) + +**Pros:** +- No need to build/maintain Docker images +- Always gets the latest code from the repository +- Simpler CI/CD for development + +**Cons:** +- Slower pod startup time (needs to download and install at startup) +- Requires internet access to GitHub during pod initialization +- Less control over exact versions +- Larger base image (needs Python + uv) + +### Pre-built Docker Image + +**Pros:** +- Faster pod startup time +- No internet access required after image pull +- Immutable deployments (exact version control) +- Smaller attack surface +- Better for air-gapped environments + +**Cons:** +- Requires maintaining a container registry +- Need to rebuild and push for updates +- More complex CI/CD pipeline + +## Production Recommendations + +For production deployments, we recommend: + +1. **Use pre-built Docker images** (`useDockerImage: true`) +2. **Use specific version tags** (not `latest`) +3. **Use private container registry** +4. **Implement image scanning** in CI/CD +5. **Use image pull secrets** for authentication +6. **Enable image signature verification** + +Example production configuration: + +```yaml +image: + registry: your-registry.com + repository: gitguardian/mcp-server + tag: "0.1.0-developer" # Specific version + pullPolicy: IfNotPresent + useDockerImage: true + +imagePullSecrets: + - name: registry-credentials + +# Scan images in CI/CD +podAnnotations: + container.apparmor.security.beta.kubernetes.io/mcp-server: runtime/default +``` + +## Automated Builds with CI/CD + +### GitHub Actions Example + +Create `.github/workflows/docker-build.yml`: + +```yaml +name: Build and Push Docker Image + +on: + push: + tags: + - 'v*' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to GHCR + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract version + id: version + run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT + + - name: Build and push + uses: docker/build-push-action@v4 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + tags: | + ghcr.io/${{ github.repository }}:${{ steps.version.outputs.VERSION }}-developer + ghcr.io/${{ github.repository }}:latest-developer +``` + +## Security Scanning + +### Using Trivy + +```bash +# Scan the image for vulnerabilities +docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \ + aquasec/trivy image gitguardian-mcp-server:developer + +# Scan with severity threshold +docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \ + aquasec/trivy image --severity HIGH,CRITICAL \ + gitguardian-mcp-server:developer +``` + +### Using Snyk + +```bash +# Scan with Snyk +snyk container test gitguardian-mcp-server:developer + +# Monitor the image +snyk container monitor gitguardian-mcp-server:developer +``` + +## Troubleshooting + +### Image pull failures + +```bash +# Check image pull secrets +kubectl get secret regcred -n gitguardian -o yaml + +# Verify the secret is attached to service account +kubectl describe serviceaccount -n gitguardian + +# Check pod events +kubectl describe pod -n gitguardian +``` + +### Container crashes + +```bash +# Check logs +kubectl logs -n gitguardian + +# Check if the command is correct +kubectl get pod -n gitguardian -o yaml | grep -A 5 command: +``` + +### Slow startup + +If using git-based installation and experiencing slow startup: +1. Consider switching to pre-built Docker images +2. Check network connectivity to GitHub +3. Consider using a caching proxy + +## Next Steps + +- Set up automated image builds in CI/CD +- Implement image scanning and vulnerability management +- Configure image pull policies based on your environment +- Set up monitoring and alerting for image updates diff --git a/helm/mcp-server/INSTALL.md b/helm/mcp-server/INSTALL.md new file mode 100644 index 0000000..eb17279 --- /dev/null +++ b/helm/mcp-server/INSTALL.md @@ -0,0 +1,293 @@ +# Quick Installation Guide + +This guide provides step-by-step instructions for deploying the GitGuardian MCP Server using Helm. + +## Prerequisites + +Before you begin, ensure you have: + +1. A Kubernetes cluster (1.19+) with kubectl configured +2. Helm 3.2.0+ installed +3. A GitGuardian Personal Access Token (PAT) for authentication + +**Note**: Unlike traditional deployments, this Helm chart does NOT require creating Kubernetes secrets for PATs. Authentication is done per-request via HTTP headers. + +## Step 1: Install the Helm Chart + +### Basic Installation + +For a basic installation with default settings: + +```bash +kubectl create namespace gitguardian # Optional: create a dedicated namespace + +helm install mcp-server ./helm/mcp-server \ + --namespace gitguardian +``` + +### Installation with Custom Values + +For more control, use one of the example values files: + +```bash +# Basic setup +helm install mcp-server ./helm/mcp-server \ + --namespace gitguardian \ + -f helm/mcp-server/examples/values-basic.yaml + +# Production setup with ingress and autoscaling +helm install mcp-server ./helm/mcp-server \ + --namespace gitguardian \ + -f helm/mcp-server/examples/values-production.yaml + +# Self-hosted GitGuardian +helm install mcp-server ./helm/mcp-server \ + --namespace gitguardian \ + -f helm/mcp-server/examples/values-self-hosted.yaml + +# SecOps server type +helm install mcp-server ./helm/mcp-server \ + --namespace gitguardian \ + -f helm/mcp-server/examples/values-secops.yaml +``` + +## Step 2: Verify the Installation + +Check that the pod is running: + +```bash +kubectl get pods -n gitguardian -l app.kubernetes.io/name=gitguardian-mcp-server +``` + +Check the service: + +```bash +kubectl get service -n gitguardian mcp-server +``` + +View logs: + +```bash +kubectl logs -n gitguardian -l app.kubernetes.io/name=gitguardian-mcp-server +``` + +## Step 3: Test the Connection + +### Port Forward (for testing) + +```bash +kubectl port-forward -n gitguardian service/mcp-server 8000:8000 +``` + +### Test the API + +**Important**: You need a valid GitGuardian Personal Access Token to test the API. + +```bash +# List available tools +curl -X POST http://localhost:8000/tools/list \ + -H "Authorization: Bearer YOUR_PERSONAL_ACCESS_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{}' + +# Get authenticated user info +curl -X POST http://localhost:8000/tools/call \ + -H "Authorization: Bearer YOUR_PERSONAL_ACCESS_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"name": "get_authenticated_user_info", "arguments": {}}' +``` + +## Step 4: Configure Your MCP Client + +Update your MCP client configuration to use the deployed server: + +### For in-cluster clients: + +```json +{ + "mcpServers": { + "GitGuardian": { + "url": "http://mcp-server.gitguardian.svc.cluster.local:8000", + "transport": "http", + "headers": { + "Authorization": "Bearer YOUR_PERSONAL_ACCESS_TOKEN" + } + } + } +} +``` + +### If you configured an ingress: + +```json +{ + "mcpServers": { + "GitGuardian": { + "url": "https://mcp-server.example.com", + "transport": "http", + "headers": { + "Authorization": "Bearer YOUR_PERSONAL_ACCESS_TOKEN" + } + } + } +} +``` + +## Authentication + +### Per-Request Authentication + +This MCP server uses per-request authentication. Each HTTP request must include your GitGuardian Personal Access Token in the `Authorization` header. + +**Supported formats:** +- `Authorization: Bearer YOUR_PAT` +- `Authorization: Token YOUR_PAT` +- `Authorization: YOUR_PAT` + +### Creating a Personal Access Token + +1. Go to your GitGuardian dashboard +2. Navigate to API > Personal Access Tokens +3. Create a new token with the required scopes: + - For developer server: `scan`, `incidents:read`, `sources:read` + - For honeytokens: add `honeytokens:read`, `honeytokens:write` + - For SecOps: add `incidents:write` + +### No Secrets Required + +Unlike traditional deployments, you do NOT need to: +- Create Kubernetes secrets for PATs +- Store credentials in the cluster +- Manage token rotation through Kubernetes + +Each client manages their own PAT and includes it in their requests. + +## Upgrading + +To upgrade the deployment: + +```bash +helm upgrade mcp-server ./helm/mcp-server \ + --namespace gitguardian \ + -f your-values.yaml +``` + +## Uninstalling + +To remove the deployment: + +```bash +helm uninstall mcp-server --namespace gitguardian +``` + +## Troubleshooting + +### Pod is not starting + +Check pod events: +```bash +kubectl describe pod -n gitguardian -l app.kubernetes.io/name=gitguardian-mcp-server +``` + +### Authentication errors (401 Unauthorized) + +Verify your PAT: +- Check that the token is valid and not expired +- Ensure the token has the required scopes +- Verify the Authorization header format +- Create a new PAT if needed + +### Connection refused + +Verify the service is correctly configured: +```bash +kubectl get endpoints -n gitguardian mcp-server +``` + +### Testing with curl + +```bash +# Port forward the service +kubectl port-forward -n gitguardian service/mcp-server 8000:8000 + +# In another terminal, test the connection +curl -v -X POST http://localhost:8000/tools/list \ + -H "Authorization: Bearer YOUR_PAT" \ + -H "Content-Type: application/json" \ + -d '{}' +``` + +## Multi-User Setup + +Since authentication is per-request, multiple users can use the same deployment with different PATs: + +1. Deploy the MCP server once +2. Each user includes their own PAT in the Authorization header +3. No shared credentials or secrets needed +4. Each user's requests are authenticated independently + +## Production Considerations + +For production deployments: + +1. **Enable TLS/HTTPS**: Use ingress with TLS certificates + ```yaml + ingress: + enabled: true + tls: + - secretName: mcp-server-tls + hosts: + - mcp-server.example.com + ``` + +2. **Enable autoscaling**: Handle variable load + ```yaml + autoscaling: + enabled: true + minReplicas: 2 + maxReplicas: 10 + ``` + +3. **Set resource limits**: Prevent resource exhaustion + ```yaml + resources: + limits: + cpu: 1000m + memory: 512Mi + requests: + cpu: 200m + memory: 256Mi + ``` + +4. **Network policies**: Restrict access to the service + ```yaml + # Create a NetworkPolicy to allow only specific namespaces + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: mcp-server-access + spec: + podSelector: + matchLabels: + app.kubernetes.io/name: gitguardian-mcp-server + ingress: + - from: + - namespaceSelector: + matchLabels: + allowed: "true" + ``` + +5. **Monitoring**: Set up monitoring and alerting + - Pod health checks are included + - Add Prometheus annotations for metrics + - Set up log aggregation + +## Next Steps + +- Configure ingress for external access +- Set up monitoring and alerting +- Enable autoscaling for production workloads +- Review security best practices in the main README +- Configure network policies to restrict access + +For more detailed configuration options, see [README.md](./README.md). diff --git a/helm/mcp-server/README.md b/helm/mcp-server/README.md new file mode 100644 index 0000000..c607431 --- /dev/null +++ b/helm/mcp-server/README.md @@ -0,0 +1,367 @@ +# GitGuardian SecOps MCP Server Helm Chart + +A Helm chart for deploying GitGuardian SecOps MCP Server in HTTP mode on Kubernetes. + +## Overview + +This Helm chart deploys the GitGuardian SecOps MCP (Model Context Protocol) Server in HTTP/SSE mode, allowing you to host the server as a centralized service for comprehensive security operations that can be consumed by multiple AI agents or applications. + +**Authentication Model**: This server uses per-request authentication via HTTP headers. No secrets or Personal Access Tokens (PATs) are stored in the deployment. Each client request must include authentication credentials in the `Authorization` header. + +## Prerequisites + +- Kubernetes 1.19+ +- Helm 3.2.0+ +- Python and `uv` available in the container image (included in the deployment) +- GitGuardian Personal Access Token (PAT) for each client with appropriate SecOps scopes + +## Installation + +### Quick Start + +1. Install the Helm chart: + +```bash +helm install secops-mcp-server ./helm/mcp-server +``` + +2. The SecOps server will start and be ready to accept requests with per-request authentication. + +### Using a values file + +Create a `my-values.yaml` file: + +```yaml +# GitGuardian instance configuration +gitguardian: + url: "https://dashboard.gitguardian.com" + # SecOps typically requires comprehensive scopes + scopes: "scan,incidents:read,incidents:write,sources:read,honeytokens:read,honeytokens:write" + +# Service configuration +service: + type: ClusterIP + port: 8000 + +# Enable ingress if you want to expose the service externally +ingress: + enabled: true + className: "nginx" + hosts: + - host: secops-mcp.example.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: secops-mcp-tls + hosts: + - secops-mcp.example.com + +# Resource limits for SecOps workloads +resources: + limits: + cpu: 2000m + memory: 1Gi + requests: + cpu: 500m + memory: 512Mi +``` + +Install with your custom values: + +```bash +helm install secops-mcp-server ./helm/mcp-server -f my-values.yaml +``` + +## Configuration + +### GitGuardian Configuration + +#### GitGuardian SaaS (US) + +Default configuration: + +```yaml +gitguardian: + url: "https://dashboard.gitguardian.com" +``` + +#### GitGuardian SaaS (EU) + +For the EU region: + +```yaml +gitguardian: + url: "https://dashboard.eu1.gitguardian.com" +``` + +#### Self-Hosted GitGuardian + +For self-hosted instances: + +```yaml +gitguardian: + url: "https://dashboard.gitguardian.mycorp.local" +``` + +### Authentication + +**Important**: This MCP server uses per-request authentication. No secrets or PATs are stored in the Kubernetes deployment. + +Each HTTP request to the server must include authentication via the `Authorization` header: + +```bash +curl -X POST http://secops-mcp-server:8000/tools/list \ + -H "Authorization: Bearer YOUR_PERSONAL_ACCESS_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{}' +``` + +**Supported header formats:** +- `Authorization: Bearer ` +- `Authorization: Token ` +- `Authorization: ` + +**Why per-request authentication?** +- **Security**: No credentials stored in the cluster +- **Multi-tenancy**: Different SecOps teams can use different PATs +- **Flexibility**: Easy to rotate tokens without redeploying +- **Audit trail**: Each request is authenticated individually + +### Required Scopes for SecOps + +SecOps operations typically require the following scopes: +- `scan` - Scan code for secrets +- `incidents:read` - Read security incidents +- `incidents:write` - Manage and resolve incidents +- `sources:read` - Read source information +- `honeytokens:read` - List honeytokens +- `honeytokens:write` - Create and manage honeytokens + +### Service Configuration + +Configure how the service is exposed: + +```yaml +service: + type: ClusterIP # Options: ClusterIP, NodePort, LoadBalancer + port: 8000 + targetPort: 8000 +``` + +### Ingress Configuration + +To expose the SecOps MCP server externally: + +```yaml +ingress: + enabled: true + className: "nginx" + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-prod" + hosts: + - host: secops-mcp.example.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: secops-mcp-tls + hosts: + - secops-mcp.example.com +``` + +### Resource Management + +SecOps workloads may require more resources: + +```yaml +resources: + limits: + cpu: 2000m + memory: 1Gi + requests: + cpu: 500m + memory: 512Mi +``` + +### Autoscaling + +Enable horizontal pod autoscaling for variable SecOps workloads: + +```yaml +autoscaling: + enabled: true + minReplicas: 2 + maxReplicas: 10 + targetCPUUtilizationPercentage: 75 + targetMemoryUtilizationPercentage: 80 +``` + +### Security Context + +The chart includes secure defaults: + +```yaml +podSecurityContext: + runAsNonRoot: true + runAsUser: 1000 + fsGroup: 1000 + +securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 +``` + +## Usage + +### Connecting to the SecOps MCP Server + +Once deployed, you can connect to the server using HTTP requests with Bearer token authentication: + +```bash +# Get the service URL +kubectl get service secops-mcp-server + +# List available SecOps tools +curl -X POST http://:8000/tools/list \ + -H "Authorization: Bearer YOUR_PAT" \ + -H "Content-Type: application/json" \ + -d '{}' + +# Example: Get authenticated user info +curl -X POST http://:8000/tools/call \ + -H "Authorization: Bearer YOUR_PAT" \ + -H "Content-Type: application/json" \ + -d '{"name": "get_authenticated_user_info", "arguments": {}}' +``` + +### Using with MCP Clients + +Configure your MCP client to use the HTTP transport with per-request authentication: + +```json +{ + "mcpServers": { + "GitGuardianSecOps": { + "url": "http://secops-mcp-server:8000", + "transport": "http", + "headers": { + "Authorization": "Bearer YOUR_PERSONAL_ACCESS_TOKEN" + } + } + } +} +``` + +## Configuration Parameters + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `replicaCount` | Number of replicas | `1` | +| `image.registry` | Container image registry | `ghcr.io` | +| `image.repository` | Container image repository | `gitguardian/mcp-server` | +| `image.tag` | Image tag | `""` (uses chart appVersion) | +| `image.pullPolicy` | Image pull policy | `IfNotPresent` | +| `image.useDockerImage` | Use pre-built Docker image vs uvx | `false` | +| `mcp.port` | MCP server port | `8000` | +| `mcp.host` | MCP server host | `0.0.0.0` | +| `gitguardian.url` | GitGuardian instance URL | `https://dashboard.gitguardian.com` | +| `gitguardian.scopes` | OAuth scopes (for reference) | `""` | +| `service.type` | Kubernetes service type | `ClusterIP` | +| `service.port` | Service port | `8000` | +| `ingress.enabled` | Enable ingress | `false` | +| `resources.limits.cpu` | CPU limit | `1000m` | +| `resources.limits.memory` | Memory limit | `512Mi` | +| `resources.requests.cpu` | CPU request | `100m` | +| `resources.requests.memory` | Memory request | `128Mi` | +| `autoscaling.enabled` | Enable HPA | `false` | + +## Upgrading + +To upgrade the chart: + +```bash +helm upgrade secops-mcp-server ./helm/mcp-server -f my-values.yaml +``` + +## Uninstallation + +To uninstall the chart: + +```bash +helm uninstall secops-mcp-server +``` + +## Troubleshooting + +### Check Pod Status + +```bash +kubectl get pods -l app.kubernetes.io/name=gitguardian-secops-mcp-server +``` + +### View Pod Logs + +```bash +kubectl logs -l app.kubernetes.io/name=gitguardian-secops-mcp-server +``` + +### Check Service Endpoints + +```bash +kubectl get endpoints secops-mcp-server +``` + +### Test Connectivity + +```bash +# Port forward to test locally +kubectl port-forward service/secops-mcp-server 8000:8000 + +# Test the endpoint (requires a valid PAT with SecOps scopes) +curl -X POST http://localhost:8000/tools/list \ + -H "Authorization: Bearer YOUR_PAT" \ + -H "Content-Type: application/json" \ + -d '{}' +``` + +### Authentication Errors + +If you receive 401 Unauthorized errors: +- Verify your PAT is valid and not expired +- Check the Authorization header format +- Ensure the PAT has the required SecOps scopes +- Create a new PAT at your GitGuardian dashboard + +## Security Considerations + +1. **No stored credentials**: The deployment does not store any PATs or secrets +2. **Per-request authentication**: Each request is authenticated individually +3. **Use TLS/HTTPS**: Enable ingress with TLS for production deployments +4. **Network policies**: Consider implementing Kubernetes network policies to restrict access +5. **RBAC**: The chart creates a service account with minimal permissions +6. **Token management**: SecOps teams are responsible for securing their own PATs +7. **Scope management**: Use least-privilege scopes for each team's PAT + +## Multi-Team SecOps + +This deployment supports multi-team SecOps naturally through per-request authentication: +- Different SecOps teams can use different PATs in their requests +- No shared credentials mean better security isolation between teams +- Each team's requests are authenticated and authorized independently +- Fine-grained access control through PAT scopes + +## Support + +For issues and questions: +- GitHub Issues: https://github.com/GitGuardian/gg-mcp/issues +- Documentation: https://docs.gitguardian.com/ + +## License + +MIT License - see LICENSE file for details diff --git a/helm/mcp-server/examples/values-production.yaml b/helm/mcp-server/examples/values-production.yaml new file mode 100644 index 0000000..73fdc9d --- /dev/null +++ b/helm/mcp-server/examples/values-production.yaml @@ -0,0 +1,72 @@ +# Production configuration example for GitGuardian SecOps MCP Server +# This example shows a production-ready setup with ingress, TLS, and autoscaling +# Authentication is done per-request via Authorization header + +# Multiple replicas for high availability +replicaCount: 2 + +gitguardian: + url: "https://dashboard.gitguardian.com" + +service: + type: ClusterIP + port: 8000 + annotations: + # Add any cloud provider specific annotations here + # service.beta.kubernetes.io/aws-load-balancer-type: "nlb" + +ingress: + enabled: true + className: "nginx" + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-prod" + nginx.ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/force-ssl-redirect: "true" + hosts: + - host: mcp-server.example.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: mcp-server-tls + hosts: + - mcp-server.example.com + +resources: + limits: + cpu: 1000m + memory: 512Mi + requests: + cpu: 200m + memory: 256Mi + +autoscaling: + enabled: true + minReplicas: 2 + maxReplicas: 10 + targetCPUUtilizationPercentage: 70 + targetMemoryUtilizationPercentage: 80 + +# Enhanced security context +podSecurityContext: + runAsNonRoot: true + runAsUser: 1000 + fsGroup: 1000 + seccompProfile: + type: RuntimeDefault + +securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 + +# Pod disruption budget for high availability +# Note: You would need to create a separate PDB resource +podAnnotations: + prometheus.io/scrape: "true" + prometheus.io/port: "8000" + prometheus.io/path: "/metrics" diff --git a/helm/mcp-server/examples/values-secops.yaml b/helm/mcp-server/examples/values-secops.yaml new file mode 100644 index 0000000..ea30284 --- /dev/null +++ b/helm/mcp-server/examples/values-secops.yaml @@ -0,0 +1,37 @@ +# SecOps MCP Server configuration example +# This example shows a typical SecOps deployment +# Authentication is done per-request via Authorization header + +gitguardian: + url: "https://dashboard.gitguardian.com" + +service: + type: ClusterIP + port: 8000 + +# SecOps may require more resources due to comprehensive tooling +resources: + limits: + cpu: 2000m + memory: 1Gi + requests: + cpu: 500m + memory: 512Mi + +# Enable autoscaling for variable load +autoscaling: + enabled: true + minReplicas: 2 + maxReplicas: 5 + targetCPUUtilizationPercentage: 75 + +# Optional: Add node selector for dedicated SecOps nodes +nodeSelector: + workload: secops + +# Optional: Add tolerations for tainted nodes +tolerations: + - key: "workload" + operator: "Equal" + value: "secops" + effect: "NoSchedule" diff --git a/helm/mcp-server/templates/NOTES.txt b/helm/mcp-server/templates/NOTES.txt new file mode 100644 index 0000000..afb0dbc --- /dev/null +++ b/helm/mcp-server/templates/NOTES.txt @@ -0,0 +1,65 @@ +Thank you for installing {{ .Chart.Name }}! + +Your GitGuardian SecOps MCP Server has been deployed. + +1. Get the application URL: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "mcp-server.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status by running: + kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "mcp-server.fullname" . }} + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "mcp-server.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "mcp-server.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + echo "Visit http://127.0.0.1:8000 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8000:{{ .Values.mcp.port }} +{{- end }} + +2. Test the MCP server (requires GitGuardian Personal Access Token): + + # List available tools + curl -X POST http://localhost:8000/tools/list \ + -H "Authorization: Bearer YOUR_PERSONAL_ACCESS_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{}' + +3. Configure your MCP client: + + Authentication is done per-request via the Authorization header. + Each client request must include your GitGuardian Personal Access Token. + + { + "mcpServers": { + "GitGuardian": { + "url": "http://{{ include "mcp-server.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.service.port }}", + "transport": "http", + "headers": { + "Authorization": "Bearer YOUR_PERSONAL_ACCESS_TOKEN" + } + } + } + } + +4. View logs: + + kubectl logs --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "mcp-server.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" + +IMPORTANT: Authentication + - This MCP server does NOT store any secrets or PATs + - Authentication is done per-request via the Authorization header + - Each client must provide their own GitGuardian Personal Access Token + - Create a PAT at: {{ .Values.gitguardian.url }}/api/personal-access-tokens + +For more information, visit: + - Documentation: https://github.com/GitGuardian/gg-mcp + - Chart README: helm/mcp-server/README.md diff --git a/helm/mcp-server/templates/_helpers.tpl b/helm/mcp-server/templates/_helpers.tpl new file mode 100644 index 0000000..5eec11d --- /dev/null +++ b/helm/mcp-server/templates/_helpers.tpl @@ -0,0 +1,70 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "mcp-server.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "mcp-server.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "mcp-server.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "mcp-server.labels" -}} +helm.sh/chart: {{ include "mcp-server.chart" . }} +{{ include "mcp-server.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "mcp-server.selectorLabels" -}} +app.kubernetes.io/name: {{ include "mcp-server.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "mcp-server.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "mcp-server.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Create the image name +*/}} +{{- define "mcp-server.image" -}} +{{- $registry := .Values.image.registry }} +{{- $repository := .Values.image.repository }} +{{- $tag := .Values.image.tag | default .Chart.AppVersion }} +{{- printf "%s/%s:%s" $registry $repository $tag }} +{{- end }} diff --git a/helm/mcp-server/templates/deployment.yaml b/helm/mcp-server/templates/deployment.yaml new file mode 100644 index 0000000..2af2c48 --- /dev/null +++ b/helm/mcp-server/templates/deployment.yaml @@ -0,0 +1,113 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "mcp-server.fullname" . }} + labels: + {{- include "mcp-server.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "mcp-server.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "mcp-server.labels" . | nindent 8 }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "mcp-server.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: {{ include "mcp-server.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- if .Values.image.useDockerImage }} + command: + - "secops-mcp-server" + {{- else }} + command: + - "uvx" + - "--from" + - "git+https://github.com/GitGuardian/gg-mcp.git" + - "secops-mcp-server" + {{- end }} + ports: + - name: http + containerPort: {{ .Values.mcp.port }} + protocol: TCP + env: + # MCP Server configuration + - name: MCP_PORT + value: {{ .Values.mcp.port | quote }} + - name: MCP_HOST + value: {{ .Values.mcp.host | quote }} + # OAuth must be disabled in HTTP mode + - name: ENABLE_LOCAL_OAUTH + value: "false" + # GitGuardian configuration + - name: GITGUARDIAN_URL + value: {{ .Values.gitguardian.url | quote }} + {{- if .Values.gitguardian.clientId }} + - name: GITGUARDIAN_CLIENT_ID + value: {{ .Values.gitguardian.clientId | quote }} + {{- end }} + {{- if .Values.gitguardian.scopes }} + - name: GITGUARDIAN_SCOPES + value: {{ .Values.gitguardian.scopes | quote }} + {{- end }} + {{- if .Values.gitguardian.tokenName }} + - name: GITGUARDIAN_TOKEN_NAME + value: {{ .Values.gitguardian.tokenName | quote }} + {{- end }} + {{- if .Values.gitguardian.tokenLifetime }} + - name: GITGUARDIAN_TOKEN_LIFETIME + value: {{ .Values.gitguardian.tokenLifetime | quote }} + {{- end }} + {{- with .Values.extraEnv }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.livenessProbe }} + livenessProbe: + {{- toYaml .Values.livenessProbe | nindent 12 }} + {{- end }} + {{- if .Values.readinessProbe }} + readinessProbe: + {{- toYaml .Values.readinessProbe | nindent 12 }} + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.volumeMounts }} + volumeMounts: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.volumes }} + volumes: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/helm/mcp-server/templates/hpa.yaml b/helm/mcp-server/templates/hpa.yaml new file mode 100644 index 0000000..38569c8 --- /dev/null +++ b/helm/mcp-server/templates/hpa.yaml @@ -0,0 +1,32 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "mcp-server.fullname" . }} + labels: + {{- include "mcp-server.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "mcp-server.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/helm/mcp-server/templates/ingress.yaml b/helm/mcp-server/templates/ingress.yaml new file mode 100644 index 0000000..2b3940b --- /dev/null +++ b/helm/mcp-server/templates/ingress.yaml @@ -0,0 +1,41 @@ +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "mcp-server.fullname" . }} + labels: + {{- include "mcp-server.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.ingress.className }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: + name: {{ include "mcp-server.fullname" $ }} + port: + number: {{ $.Values.service.port }} + {{- end }} + {{- end }} +{{- end }} diff --git a/helm/mcp-server/templates/service.yaml b/helm/mcp-server/templates/service.yaml new file mode 100644 index 0000000..36efa75 --- /dev/null +++ b/helm/mcp-server/templates/service.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "mcp-server.fullname" . }} + labels: + {{- include "mcp-server.labels" . | nindent 4 }} + {{- with .Values.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: {{ .Values.service.targetPort }} + protocol: TCP + name: http + selector: + {{- include "mcp-server.selectorLabels" . | nindent 4 }} diff --git a/helm/mcp-server/templates/serviceaccount.yaml b/helm/mcp-server/templates/serviceaccount.yaml new file mode 100644 index 0000000..9f110ee --- /dev/null +++ b/helm/mcp-server/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "mcp-server.serviceAccountName" . }} + labels: + {{- include "mcp-server.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +automountServiceAccountToken: {{ .Values.serviceAccount.automount }} +{{- end }} diff --git a/helm/mcp-server/tests/deployment_test.yaml b/helm/mcp-server/tests/deployment_test.yaml new file mode 100644 index 0000000..2b31c4f --- /dev/null +++ b/helm/mcp-server/tests/deployment_test.yaml @@ -0,0 +1,99 @@ +suite: test deployment +templates: + - deployment.yaml +tests: + - it: should render deployment with default values + asserts: + - isKind: + of: Deployment + - equal: + path: metadata.name + value: RELEASE-NAME-gitguardian-secops-mcp-server + - equal: + path: spec.replicas + value: 1 + - isNotNull: + path: spec.template.spec.containers[0] + + - it: should set correct image repository + set: + image.registry: ghcr.io + image.repository: gitguardian/mcp-server + image.tag: "1.0.0" + asserts: + - equal: + path: spec.template.spec.containers[0].image + value: ghcr.io/gitguardian/mcp-server:1.0.0 + + - it: should use appVersion as default tag + asserts: + - matchRegex: + path: spec.template.spec.containers[0].image + pattern: ':0\.1\.0$' + + - it: should set resource limits + set: + resources: + limits: + cpu: 500m + memory: 256Mi + requests: + cpu: 50m + memory: 64Mi + asserts: + - equal: + path: spec.template.spec.containers[0].resources.limits.cpu + value: 500m + - equal: + path: spec.template.spec.containers[0].resources.limits.memory + value: 256Mi + - equal: + path: spec.template.spec.containers[0].resources.requests.cpu + value: 50m + - equal: + path: spec.template.spec.containers[0].resources.requests.memory + value: 64Mi + + - it: should set replica count + set: + replicaCount: 3 + asserts: + - equal: + path: spec.replicas + value: 3 + + - it: should set port correctly + set: + mcp.port: 9000 + asserts: + - equal: + path: spec.template.spec.containers[0].ports[0].containerPort + value: 9000 + + - it: should have security context + asserts: + - equal: + path: spec.template.spec.securityContext.runAsUser + value: 1000 + - equal: + path: spec.template.spec.securityContext.runAsNonRoot + value: true + - equal: + path: spec.template.spec.containers[0].securityContext.allowPrivilegeEscalation + value: false + + - it: should have livenessProbe + asserts: + - isNotNull: + path: spec.template.spec.containers[0].livenessProbe + - equal: + path: spec.template.spec.containers[0].livenessProbe.httpGet.path + value: /health + + - it: should have readinessProbe + asserts: + - isNotNull: + path: spec.template.spec.containers[0].readinessProbe + - equal: + path: spec.template.spec.containers[0].readinessProbe.httpGet.path + value: /health diff --git a/helm/mcp-server/tests/hpa_test.yaml b/helm/mcp-server/tests/hpa_test.yaml new file mode 100644 index 0000000..6c366c4 --- /dev/null +++ b/helm/mcp-server/tests/hpa_test.yaml @@ -0,0 +1,44 @@ +suite: test horizontal pod autoscaler +templates: + - hpa.yaml +tests: + - it: should not create HPA by default + asserts: + - hasDocuments: + count: 0 + + - it: should create HPA when enabled + set: + autoscaling.enabled: true + autoscaling.minReplicas: 2 + autoscaling.maxReplicas: 10 + autoscaling.targetCPUUtilizationPercentage: 80 + asserts: + - isKind: + of: HorizontalPodAutoscaler + - equal: + path: metadata.name + value: RELEASE-NAME-gitguardian-secops-mcp-server + - equal: + path: spec.minReplicas + value: 2 + - equal: + path: spec.maxReplicas + value: 10 + + - it: should set CPU target when specified + set: + autoscaling.enabled: true + autoscaling.targetCPUUtilizationPercentage: 75 + asserts: + - equal: + path: spec.metrics[0].resource.target.averageUtilization + value: 75 + + - it: should support memory target + set: + autoscaling.enabled: true + autoscaling.targetMemoryUtilizationPercentage: 80 + asserts: + - isNotNull: + path: spec.metrics diff --git a/helm/mcp-server/tests/ingress_test.yaml b/helm/mcp-server/tests/ingress_test.yaml new file mode 100644 index 0000000..33a342b --- /dev/null +++ b/helm/mcp-server/tests/ingress_test.yaml @@ -0,0 +1,63 @@ +suite: test ingress +templates: + - ingress.yaml +tests: + - it: should not create ingress by default + asserts: + - hasDocuments: + count: 0 + + - it: should create ingress when enabled + set: + ingress.enabled: true + ingress.hosts: + - host: mcp-server.example.com + paths: + - path: / + pathType: Prefix + asserts: + - isKind: + of: Ingress + - equal: + path: metadata.name + value: RELEASE-NAME-gitguardian-secops-mcp-server + - equal: + path: spec.rules[0].host + value: mcp-server.example.com + - equal: + path: spec.rules[0].http.paths[0].path + value: / + + - it: should support ingress annotations + set: + ingress.enabled: true + ingress.annotations: + custom-annotation: custom-value + ingress.hosts: + - host: mcp-server.example.com + paths: + - path: / + pathType: Prefix + asserts: + - isNotNull: + path: metadata.annotations + + - it: should support TLS configuration + set: + ingress.enabled: true + ingress.tls: + - secretName: mcp-server-tls + hosts: + - mcp-server.example.com + ingress.hosts: + - host: mcp-server.example.com + paths: + - path: / + pathType: Prefix + asserts: + - equal: + path: spec.tls[0].secretName + value: mcp-server-tls + - equal: + path: spec.tls[0].hosts[0] + value: mcp-server.example.com diff --git a/helm/mcp-server/tests/service_test.yaml b/helm/mcp-server/tests/service_test.yaml new file mode 100644 index 0000000..4b058da --- /dev/null +++ b/helm/mcp-server/tests/service_test.yaml @@ -0,0 +1,48 @@ +suite: test service +templates: + - service.yaml +tests: + - it: should render service with default values + asserts: + - isKind: + of: Service + - equal: + path: metadata.name + value: RELEASE-NAME-gitguardian-secops-mcp-server + - equal: + path: spec.type + value: ClusterIP + - equal: + path: spec.ports[0].port + value: 8000 + - equal: + path: spec.ports[0].targetPort + value: 8000 + + - it: should set custom port + set: + service.port: 9000 + service.targetPort: 9000 + asserts: + - equal: + path: spec.ports[0].port + value: 9000 + - equal: + path: spec.ports[0].targetPort + value: 9000 + + - it: should support service annotations + set: + service.annotations: + custom-annotation: "custom-value" + asserts: + - isNotNull: + path: metadata.annotations + + - it: should set service type to LoadBalancer + set: + service.type: LoadBalancer + asserts: + - equal: + path: spec.type + value: LoadBalancer diff --git a/helm/mcp-server/tests/serviceaccount_test.yaml b/helm/mcp-server/tests/serviceaccount_test.yaml new file mode 100644 index 0000000..844d984 --- /dev/null +++ b/helm/mcp-server/tests/serviceaccount_test.yaml @@ -0,0 +1,29 @@ +suite: test service account +templates: + - serviceaccount.yaml +tests: + - it: should create service account when enabled + set: + serviceAccount.create: true + asserts: + - isKind: + of: ServiceAccount + - equal: + path: metadata.name + value: RELEASE-NAME-gitguardian-secops-mcp-server + + - it: should support service account annotations + set: + serviceAccount.create: true + serviceAccount.annotations: + custom-annotation: custom-value + asserts: + - isNotNull: + path: metadata.annotations + + - it: should not create service account when disabled + set: + serviceAccount.create: false + asserts: + - hasDocuments: + count: 0 diff --git a/helm/mcp-server/tests/simple_test.yaml b/helm/mcp-server/tests/simple_test.yaml new file mode 100644 index 0000000..1ababe1 --- /dev/null +++ b/helm/mcp-server/tests/simple_test.yaml @@ -0,0 +1,8 @@ +suite: test simple +templates: + - deployment.yaml +tests: + - it: should render + asserts: + - isKind: + of: Deployment diff --git a/helm/mcp-server/values.yaml b/helm/mcp-server/values.yaml new file mode 100644 index 0000000..ef8c3b2 --- /dev/null +++ b/helm/mcp-server/values.yaml @@ -0,0 +1,183 @@ +# Default values for gitguardian-mcp-server. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +# Replica count for the deployment +replicaCount: 1 + +image: + # Container registry + registry: ghcr.io + # Repository name (adjust this to your actual image location) + repository: gitguardian/mcp-server + # Image pull policy + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "" + # Set to true if using a pre-built Docker image, false to use uvx with git + # When true: Uses the built container image with the server pre-installed + # When false: Uses a Python image and installs via uvx from git (slower startup) + useDockerImage: false + +# Image pull secrets for private registries +imagePullSecrets: [ ] + +# Override the name of the chart +nameOverride: "" +# Override the full name of the resources +fullnameOverride: "" + +# This chart deploys the SecOps MCP Server only + +# MCP Server configuration +mcp: + # Port for the HTTP/SSE transport + port: 8000 + # Host address to bind to (use 0.0.0.0 for all interfaces) + host: "0.0.0.0" + +# GitGuardian configuration +gitguardian: + # GitGuardian instance URL + # Examples: + # - US SaaS: https://dashboard.gitguardian.com (default) + # - EU SaaS: https://dashboard.eu1.gitguardian.com + # - Self-hosted: https://dashboard.gitguardian.mycorp.local + url: "https://dashboard.gitguardian.com" + + # Authentication in HTTP mode is done per-request via Authorization header + # No secrets or PATs are stored in the deployment + # Clients must provide authentication in each HTTP request + + # OAuth scopes to request (optional, for reference only) + # For self-hosted with honeytokens: "scan,incidents:read,sources:read,honeytokens:read,honeytokens:write" + # For SaaS: Auto-detected + scopes: "" + +# Service account configuration +serviceAccount: + # Specifies whether a service account should be created + create: true + # Automatically mount a ServiceAccount's API credentials + automount: true + # Annotations to add to the service account + annotations: { } + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +# Pod annotations +podAnnotations: { } + +# Pod labels +podLabels: { } + +# Pod security context +podSecurityContext: + runAsNonRoot: true + runAsUser: 1000 + fsGroup: 1000 + +# Container security context +securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 + +# Service configuration +service: + # Service type (ClusterIP, NodePort, LoadBalancer) + type: ClusterIP + # Service port + port: 8000 + # Target port on the container + targetPort: 8000 + # Annotations for the service + annotations: { } + +# Ingress configuration +ingress: + # Enable ingress + enabled: false + # Ingress class name + className: "" + # Ingress annotations + annotations: { } + # kubernetes.io/ingress.class: nginx + # cert-manager.io/cluster-issuer: letsencrypt-prod + # Ingress hosts configuration + hosts: + - host: mcp-server.example.com + paths: + - path: / + pathType: Prefix + # TLS configuration + tls: [ ] + # - secretName: mcp-server-tls + # hosts: + # - mcp-server.example.com + +# Resource limits and requests +resources: + limits: + cpu: 1000m + memory: 512Mi + requests: + cpu: 100m + memory: 128Mi + +# Liveness probe configuration +livenessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + +# Readiness probe configuration +readinessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 + +# Autoscaling configuration +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 10 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +# Additional volumes +volumes: [ ] +# - name: tmp +# emptyDir: {} + +# Additional volume mounts +volumeMounts: [ ] +# - name: tmp +# mountPath: /tmp + +# Node selector +nodeSelector: { } + +# Tolerations +tolerations: [ ] + +# Affinity rules +affinity: { } + +# Additional environment variables +extraEnv: [ ] +# - name: CUSTOM_VAR +# value: "custom-value" diff --git a/packages/gg_api_core/src/gg_api_core/tools/list_repo_occurrences.py b/packages/gg_api_core/src/gg_api_core/tools/list_repo_occurrences.py index 3dfc47e..efefd9e 100644 --- a/packages/gg_api_core/src/gg_api_core/tools/list_repo_occurrences.py +++ b/packages/gg_api_core/src/gg_api_core/tools/list_repo_occurrences.py @@ -63,7 +63,7 @@ class ListRepoOccurrencesBaseParams(BaseModel): repository_name: str | None = Field( default=None, - description="The full repository name. For example, for https://github.com/GitGuardian/gg-mcp.git the full name is GitGuardian/gg-mcp. Pass the current repository name if not provided. Not required if source_id is provided.", + description="The full repository name. For example, for https://github.com/GitGuardian/ggmcp.git the full name is GitGuardian/ggmcp. Pass the current repository name if not provided. Not required if source_id is provided.", ) source_id: str | int | None = Field( default=None,