tanuki-mcp 0.2.0

A GitLab MCP server with fine-grained access control
Documentation
version: "3"

tasks:
  default:
    desc: List available tasks
    cmds:
      - task --list

  check:
    desc: Run all checks (same as CI)
    cmds:
      - task: fmt:check
      - task: clippy
      - task: test
      - task: doc

  fmt:
    desc: Format code with rustfmt
    cmds:
      - cargo fmt --all

  fmt:check:
    desc: Check code formatting
    cmds:
      - cargo fmt --all -- --check

  clippy:
    desc: Run clippy linter
    cmds:
      - cargo clippy --all-targets --all-features

  test:
    desc: Run unit tests (excludes e2e)
    cmds:
      - cargo test

  doc:
    desc: Build documentation
    cmds:
      - cargo doc --no-deps --all-features
    env:
      RUSTDOCFLAGS: -Dwarnings

  build:
    desc: Build in debug mode
    cmds:
      - cargo build

  build:release:
    desc: Build in release mode
    cmds:
      - cargo build --release

  clean:
    desc: Clean build artifacts
    cmds:
      - cargo clean

  pre-commit:install:
    desc: Install pre-commit hooks
    cmds:
      - pre-commit install

  pre-commit:run:
    desc: Run pre-commit on all files
    cmds:
      - pre-commit run --all-files

  # E2E Tests
  e2e:
    desc: Run E2E tests with both transports (requires Docker)
    deps: [build]
    cmds:
      - task: e2e:gitlab:up
      - task: e2e:wait
      - task: e2e:runner:setup
      - defer: { task: e2e:gitlab:down }
      - task: e2e:run

  e2e:stdio:
    desc: Run E2E tests with stdio transport only (no HTTP server)
    deps: [build]
    cmds:
      - task: e2e:gitlab:up
      - task: e2e:wait
      - task: e2e:runner:setup
      - defer: { task: e2e:gitlab:down }
      - task: e2e:run:stdio-only

  e2e:http:
    desc: Run E2E tests with HTTP transport only
    deps: [build]
    cmds:
      - task: e2e:gitlab:up
      - task: e2e:wait
      - task: e2e:runner:setup
      - defer: { task: e2e:gitlab:down }
      - task: e2e:run
        vars:
          FILTER: http

  e2e:run:
    desc: Run E2E tests (internal - creates PAT, starts MCP server in Docker, runs tests)
    internal: true
    cmds:
      - |
        TOKEN=$(./e2e/scripts/create-pat.sh)
        export GITLAB_URL=http://localhost:8080
        export GITLAB_TOKEN="$TOKEN"
        export RUST_TEST_THREADS="${RUST_TEST_THREADS:-1}"

        # Start MCP HTTP server in Docker container
        echo "Starting MCP HTTP server container..."
        docker compose -f e2e/docker-compose.yml --profile mcp up -d --build

        cleanup() {
          echo "Stopping MCP server container..."
          docker compose -f e2e/docker-compose.yml --profile mcp stop
        }
        trap cleanup EXIT

        # Wait for MCP server health endpoint
        echo "Waiting for MCP server health endpoint..."
        for i in $(seq 1 30); do
          curl -sf http://localhost:20399/health >/dev/null 2>&1 && break
          sleep 1
        done
        curl -sf http://localhost:20399/health >/dev/null 2>&1 || { echo "ERROR: MCP server health check failed after 30s"; docker compose -f e2e/docker-compose.yml --profile mcp logs; exit 1; }
        echo "MCP server is ready"

        export MCP_HTTP_URL=http://localhost:20399/mcp
        cargo test -p tanuki-mcp-e2e {{.FILTER}} {{.CLI_ARGS}}

  e2e:run:stdio-only:
    desc: Run E2E tests without HTTP server (stdio only)
    internal: true
    cmds:
      - |
        TOKEN=$(./e2e/scripts/create-pat.sh)
        export GITLAB_URL=http://localhost:8080
        export GITLAB_TOKEN="$TOKEN"
        export RUST_TEST_THREADS="${RUST_TEST_THREADS:-1}"

        echo "Running stdio-only tests (HTTP tests will be filtered out)..."
        cargo test -p tanuki-mcp-e2e {{.CLI_ARGS}} -- --skip "case_2_http"

  e2e:gitlab:up:
    desc: Start GitLab CE container for E2E tests
    cmds:
      - docker compose -f e2e/docker-compose.yml --profile infra up -d
    status:
      - docker inspect tanuki-mcp-e2e-gitlab --format '{{.State.Running}}' 2>/dev/null | grep -q true

  e2e:gitlab:down:
    desc: Stop GitLab CE container
    cmds:
      - docker compose -f e2e/docker-compose.yml --profile infra --profile mcp down -v

  e2e:gitlab:logs:
    desc: Show GitLab container logs
    cmds:
      - docker compose -f e2e/docker-compose.yml logs -f gitlab

  e2e:mcp:logs:
    desc: Show MCP server container logs
    cmds:
      - docker compose -f e2e/docker-compose.yml --profile mcp logs -f

  e2e:wait:
    desc: Wait for GitLab to be ready (may take 3-5 minutes)
    cmds:
      - |
        echo "Waiting for GitLab to be ready (this may take 3-5 minutes)..."
        until curl -sf http://localhost:8080/users/sign_in >/dev/null 2>&1; do
          echo "  Waiting for login page..."
          sleep 10
        done
        echo "GitLab is ready!"

  e2e:runner:setup:
    desc: Setup GitLab runner (create token, register, start)
    cmds:
      - |
        echo "Setting up GitLab runner..."

        # Create runner token via Rails console
        echo "Creating runner token..."
        TOKEN=$(docker exec tanuki-mcp-e2e-gitlab gitlab-rails runner "
          Ci::Runner.where(description: 'e2e-runner').destroy_all
          runner = Ci::Runner.create!(
            runner_type: :instance_type,
            description: 'e2e-runner',
            run_untagged: true,
            active: true
          )
          puts runner.token
        " 2>/dev/null | tail -1)

        if [ -z "$TOKEN" ]; then
          echo "ERROR: Failed to create runner token"
          exit 1
        fi
        echo "Created runner token"

        # Unregister any existing runners
        docker exec tanuki-mcp-e2e-gitlab-runner gitlab-runner unregister --all-runners 2>/dev/null || true

        # Register the runner
        echo "Registering runner..."
        docker exec tanuki-mcp-e2e-gitlab-runner gitlab-runner register \
          --non-interactive \
          --url http://gitlab:80 \
          --token "$TOKEN" \
          --executor shell \
          --description "e2e-shell-runner"

        # Start the runner daemon in background
        echo "Starting runner daemon..."
        docker exec -d tanuki-mcp-e2e-gitlab-runner gitlab-runner run

        # Wait for runner to connect
        sleep 2
        echo "GitLab runner setup complete!"

  e2e:status:
    desc: Check GitLab container status
    cmds:
      - docker compose -f e2e/docker-compose.yml ps
      - |
        if curl -sf http://localhost:8080/users/sign_in >/dev/null 2>&1; then
          echo "GitLab is healthy and ready"
        else
          echo "GitLab is not ready yet"
        fi

  # Development Environment
  dev:
    desc: Start full development environment (GitLab + MCP + Inspector)
    cmds:
      - task: e2e:gitlab:up
      - task: e2e:wait
      - task: e2e:runner:setup
      - task: dev:mcp:up
      - |
        echo ""
        echo "=== Development Environment Ready ==="
        echo "GitLab:        http://localhost:8080 (root/testpassword123!)"
        echo "MCP Server:    http://localhost:20399/mcp"
        echo "Dashboard:     http://localhost:20400"
        echo "MCP Inspector: http://localhost:6274"
        echo ""
        echo "To connect Inspector to MCP server:"
        echo "  Transport: Streamable HTTP"
        echo "  URL: http://tanuki-mcp:20289/mcp"
        echo ""
        echo "Stop with: task dev:down"

  dev:mcp:up:
    desc: Start MCP server and Inspector
    internal: true
    cmds:
      - |
        TOKEN=$(./e2e/scripts/create-pat.sh)
        export GITLAB_TOKEN="$TOKEN"
        docker compose -f e2e/docker-compose.yml --profile mcp --profile inspector up -d --build

        echo "Waiting for MCP server..."
        for i in $(seq 1 30); do
          curl -sf http://localhost:20399/health >/dev/null 2>&1 && break
          sleep 1
        done
        curl -sf http://localhost:20399/health >/dev/null 2>&1 || echo "WARNING: MCP server not responding"

  dev:down:
    desc: Stop development environment
    cmds:
      - docker compose -f e2e/docker-compose.yml --profile infra --profile mcp --profile inspector down -v

  # Release Management
  # Requires: go-task (brew install go-task), cargo-edit (cargo install cargo-edit)
  #
  # Usage:
  #   task release VERSION=patch   # 0.1.1 → 0.1.2
  #   task release VERSION=minor   # 0.1.1 → 0.2.0
  #   task release VERSION=major   # 0.1.1 → 1.0.0
  #   task release VERSION=0.2.5   # explicit version
  release:
    desc: "Create a release. Usage: task release VERSION=patch|minor|major|x.y.z"
    vars:
      # E2E tests enabled by default, can be disabled with SKIP_E2E=true
      SKIP_E2E: '{{.SKIP_E2E | default "false"}}'
    preconditions:
      - sh: '[ -n "{{.VERSION}}" ]'
        msg: "VERSION is required. Use: patch, minor, major, or explicit version (e.g., 0.2.0)"
    cmds:
      # Verify we're on a clean git state
      - |
        git diff --quiet || (echo "ERROR: Working directory not clean" && exit 1)
      # Bump version in all Cargo.toml files using cargo-edit
      - echo "Bumping version..."
      - |
        case "{{.VERSION}}" in
          patch|minor|major) cargo set-version --workspace --bump "{{.VERSION}}" ;;
          *) cargo set-version --workspace "{{.VERSION}}" ;;
        esac
      # Update Cargo.lock to reflect new version
      - cargo generate-lockfile
      # Show the new version
      - |
        RELEASE_VERSION=$(cargo pkgid | cut -d "@" -f2)
        echo "=== Release v$RELEASE_VERSION ==="
      # Commit the version bump
      - git add Cargo.toml tanuki-mcp-macros/Cargo.toml e2e/Cargo.toml Cargo.lock
      - |
        RELEASE_VERSION=$(cargo pkgid | cut -d "@" -f2)
        git commit -m "chore: release v$RELEASE_VERSION"
      # Run checks
      - echo "Running checks..."
      - task: check
      # Run E2E tests (optional)
      - |
        if [ "{{.SKIP_E2E}}" = "false" ]; then
          echo "Running E2E tests..."
          task e2e
        else
          echo "Skipping E2E tests (SKIP_E2E=true)"
        fi
      # Create the tag and show completion message
      - |
        RELEASE_VERSION=$(cargo pkgid | cut -d "@" -f2)
        git tag -a "v$RELEASE_VERSION" -m "Release v$RELEASE_VERSION"
        echo "Created tag v$RELEASE_VERSION"
        echo ""
        echo "=== Release v$RELEASE_VERSION created successfully ==="
        echo "To complete the release, push the tag and commit:"
        echo "  git push origin main"
        echo "  git push origin v$RELEASE_VERSION"