Claude Code + Recce MCP #252
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Claude Code + Recce MCP | |
| on: | |
| issue_comment: | |
| types: [created] | |
| jobs: | |
| claude: | |
| if: | | |
| (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || | |
| (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || | |
| (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || | |
| (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| issues: read | |
| id-token: write | |
| actions: read # Required for Claude to read CI results on PRs | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| # For PR events, checkout PR head; for issue_comment, will be updated in Get PR information step | |
| ref: ${{ github.event.pull_request.head.sha || github.sha }} | |
| - name: Set up Python | |
| uses: actions/setup-python@v4 | |
| with: | |
| # Python 3.10 is used for compatibility with Recce and dbt-core | |
| # Recce requires Python 3.9+ per its setup.py | |
| python-version: '3.12' | |
| - name: Cache Python dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cache/pip | |
| ~/.local/lib/python3.12/site-packages | |
| key: pip-recce-nightly-${{ runner.os }}-py3.12-${{ hashFiles('requirements.txt') }} | |
| restore-keys: | | |
| pip-recce-nightly-${{ runner.os }}-py3.12- | |
| pip-recce-nightly- | |
| - name: Cache dbt packages | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| dbt_packages/ | |
| ~/.dbt/ | |
| key: dbt-packages-${{ hashFiles('packages.yml') }} | |
| restore-keys: | | |
| dbt-packages- | |
| - name: Install Python dependencies | |
| run: | | |
| pip install -r requirements.txt | |
| - name: Get PR information for artifacts | |
| if: github.event.pull_request != null || github.event.issue.pull_request != null | |
| id: pr-info-artifacts | |
| run: | | |
| if [ "${{ github.event_name }}" == "issue_comment" ]; then | |
| PR_NUMBER=${{ github.event.issue.number }} | |
| else | |
| PR_NUMBER="${{ github.event.pull_request.number }}" | |
| fi | |
| echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT | |
| echo "::notice::PR Number: $PR_NUMBER" | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| - name: Download Current Artifacts | |
| if: | | |
| (github.event.pull_request != null || github.event.issue.pull_request != null) && | |
| (contains(github.event.comment.body, '/ms2') || contains(github.event.comment.body, '/ms3') || contains(github.event.comment.body, '/one-summary')) | |
| id: download-current | |
| continue-on-error: false | |
| uses: dawidd6/action-download-artifact@v11 | |
| with: | |
| name: dbt-artifacts-current-${{ steps.pr-info-artifacts.outputs.pr_number }} | |
| path: target/ | |
| github_token: ${{ github.token }} | |
| workflow: dbt-build-pr.yml | |
| pr: ${{ steps.pr-info-artifacts.outputs.pr_number }} | |
| check_artifacts: true | |
| search_artifacts: true | |
| - name: Download Base Artifacts | |
| if: | | |
| (github.event.pull_request != null || github.event.issue.pull_request != null) && | |
| (contains(github.event.comment.body, '/ms2') || contains(github.event.comment.body, '/ms3') || contains(github.event.comment.body, '/one-summary')) | |
| id: download-base | |
| continue-on-error: false | |
| uses: dawidd6/action-download-artifact@v11 | |
| with: | |
| name: dbt-artifacts-base-main | |
| path: target-base/ | |
| github_token: ${{ github.token }} | |
| workflow: dbt-build-base.yml | |
| branch: main | |
| check_artifacts: true | |
| search_artifacts: true | |
| - name: Install Recce with MCP support | |
| if: contains(github.event.comment.body, '/ms2') || contains(github.event.comment.body, '/ms3') || contains(github.event.comment.body, '/one-summary') | |
| run: | | |
| echo "::group::Installing Recce Nightly with MCP support" | |
| # Upgrade pip, setuptools, and wheel to ensure proper dependency resolution | |
| echo "Upgrading pip, setuptools, and wheel..." | |
| pip install --upgrade pip setuptools wheel | |
| # Install Recce Nightly with MCP extras | |
| echo "Installing recce-nightly[mcp]..." | |
| pip install "git+https://github.com/DataRecce/recce.git@main#egg=recce[mcp]" | |
| # Verify installation | |
| if command -v recce >/dev/null 2>&1; then | |
| echo "✓ Recce installed successfully: $(which recce)" | |
| echo "✓ Recce version: $(recce version)" | |
| else | |
| echo "::error::Recce installation failed - command not found" | |
| exit 1 | |
| fi | |
| # Verify critical dependencies are installed | |
| echo "Verifying critical dependencies..." | |
| python -c "import mcp; import fastapi; import click; print('✓ All critical dependencies verified')" || { | |
| echo "::error::Some critical dependencies are missing" | |
| echo "Installed packages:" | |
| pip list | |
| exit 1 | |
| } | |
| echo "::endgroup::" | |
| - name: Validate Artifacts | |
| if: | | |
| (github.event.pull_request != null || github.event.issue.pull_request != null) && | |
| (contains(github.event.comment.body, '/ms2') || contains(github.event.comment.body, '/ms3') || contains(github.event.comment.body, '/one-summary')) | |
| id: validate-artifacts | |
| run: | | |
| echo "::group::Validating downloaded artifacts" | |
| # Check current artifacts (from dbt-build-artifacts.yml) | |
| if [ -f "target/manifest.json" ] && [ -f "target/catalog.json" ]; then | |
| echo "✓ Current artifacts found (from PR build)" | |
| echo "current_exists=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "::error::Current artifacts not found. Cannot proceed with Recce analysis." | |
| echo "current_exists=false" >> $GITHUB_OUTPUT | |
| exit 1 | |
| fi | |
| # Check base artifacts (from dbt-build-base.yml) | |
| if [ -f "target-base/manifest.json" ] && [ -f "target-base/catalog.json" ]; then | |
| echo "✓ Base artifacts found (from main branch build)" | |
| echo "base_exists=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "::error::Base artifacts not found. Please ensure 'dbt Base Environment' workflow has run successfully." | |
| echo "base_exists=false" >> $GITHUB_OUTPUT | |
| exit 1 | |
| fi | |
| echo "::endgroup::" | |
| - name: Prepare System Prompt | |
| if: github.event.pull_request != null || github.event.issue.pull_request != null | |
| id: system-prompt | |
| run: | | |
| echo "::group::Composing system prompt based on milestone" | |
| COMMENT_BODY="${{ github.event.comment.body }}" | |
| # Store comment body for system prompt (will be injected into prompt to provide context) | |
| # Note: We don't use the 'prompt' parameter to preserve Claude action's default MCP and PR comment behavior | |
| echo "comment_body=$COMMENT_BODY" >> $GITHUB_OUTPUT | |
| # Detect milestone command | |
| if echo "$COMMENT_BODY" | grep -q "/ms1"; then | |
| MILESTONE="ms1" | |
| SYSTEM_PROMPT_FILE=".github/prompts/ms1-system-prompt.md" | |
| RESPONSE_TEMPLATE=".github/prompts/ms1-response-format.md" | |
| echo "📋 Milestone: MS1 (PR/Git only)" | |
| elif echo "$COMMENT_BODY" | grep -q "/ms2"; then | |
| MILESTONE="ms2" | |
| SYSTEM_PROMPT_FILE=".github/prompts/ms2-system-prompt.md" | |
| RESPONSE_TEMPLATE=".github/prompts/ms2-response-format.md" | |
| echo "📋 Milestone: MS2 (+ dbt metadata)" | |
| elif echo "$COMMENT_BODY" | grep -q "/ms3"; then | |
| MILESTONE="ms3" | |
| SYSTEM_PROMPT_FILE=".github/prompts/ms3-system-prompt.md" | |
| RESPONSE_TEMPLATE=".github/prompts/ms3-response-format.md" | |
| echo "📋 Milestone: MS3 (+ data diffs)" | |
| elif echo "$COMMENT_BODY" | grep -q "/one-summary"; then | |
| # Backward compatibility: /one-summary uses MS3 with compact format | |
| MILESTONE="ms3" | |
| SYSTEM_PROMPT_FILE=".github/prompts/ms3-system-prompt.md" | |
| RESPONSE_TEMPLATE=".github/prompts/one-summary.md" | |
| echo "📋 Milestone: MS3 (one-summary format)" | |
| else | |
| # Default: no system prompt, general PR review | |
| echo "📋 Default: General PR review (no system prompt)" | |
| echo "milestone=default" >> $GITHUB_OUTPUT | |
| echo "has_system_prompt=false" >> $GITHUB_OUTPUT | |
| echo "::endgroup::" | |
| exit 0 | |
| fi | |
| # Validate required prompt files exist | |
| REQUIRED_FILES=( | |
| ".github/prompts/system-prompt-base.md" | |
| "$SYSTEM_PROMPT_FILE" | |
| "$RESPONSE_TEMPLATE" | |
| ".github/prompts/execution-notes.md" | |
| ) | |
| for file in "${REQUIRED_FILES[@]}"; do | |
| if [ ! -f "$file" ]; then | |
| echo "::error::Required prompt file not found: $file" | |
| exit 1 | |
| fi | |
| echo "✓ Found: $file" | |
| done | |
| # Compose system prompt for MS1/MS2/MS3 | |
| # Inject current comment context at the beginning | |
| echo "## Current User Request" > /tmp/system_prompt.txt | |
| echo "" >> /tmp/system_prompt.txt | |
| echo "The user mentioned @claude with the following comment:" >> /tmp/system_prompt.txt | |
| echo '```' >> /tmp/system_prompt.txt | |
| echo "$COMMENT_BODY" >> /tmp/system_prompt.txt | |
| echo '```' >> /tmp/system_prompt.txt | |
| echo "" >> /tmp/system_prompt.txt | |
| echo "**IMPORTANT**: Focus ONLY on this current comment. Ignore any previous @claude mentions or historical discussions in the PR thread." >> /tmp/system_prompt.txt | |
| echo "" >> /tmp/system_prompt.txt | |
| echo "---" >> /tmp/system_prompt.txt | |
| echo "" >> /tmp/system_prompt.txt | |
| # Add base system prompt | |
| cat .github/prompts/system-prompt-base.md >> /tmp/system_prompt.txt | |
| echo "" >> /tmp/system_prompt.txt | |
| # Add milestone-specific prompt | |
| cat "$SYSTEM_PROMPT_FILE" >> /tmp/system_prompt.txt | |
| echo "" >> /tmp/system_prompt.txt | |
| # Add response format | |
| cat "$RESPONSE_TEMPLATE" >> /tmp/system_prompt.txt | |
| echo "" >> /tmp/system_prompt.txt | |
| # Add execution notes | |
| cat .github/prompts/execution-notes.md >> /tmp/system_prompt.txt | |
| echo "milestone=$MILESTONE" >> $GITHUB_OUTPUT | |
| echo "has_system_prompt=true" >> $GITHUB_OUTPUT | |
| echo "prompt_file=/tmp/system_prompt.txt" >> $GITHUB_OUTPUT | |
| # Display prompt preview for debugging | |
| echo "System prompt composed successfully ($(wc -l < /tmp/system_prompt.txt) lines)" | |
| echo "::group::System Prompt Preview (first 50 lines)" | |
| head -50 /tmp/system_prompt.txt | |
| echo "::endgroup::" | |
| echo "::endgroup::" | |
| - name: Validate MCP Configuration | |
| if: | | |
| (github.event.pull_request != null || github.event.issue.pull_request != null) && | |
| (contains(github.event.comment.body, '/ms2') || contains(github.event.comment.body, '/ms3') || contains(github.event.comment.body, '/one-summary')) | |
| run: | | |
| # Show current working directory and files for debugging | |
| echo "Current directory: $(pwd)" | |
| echo "MCP config path: $(pwd)/.github/mcp_config.json" | |
| ls -la .github/ || echo "No .github directory" | |
| # Validate MCP config file exists and is valid JSON | |
| if [ -f ".github/mcp_config.json" ]; then | |
| echo "✓ MCP configuration file found" | |
| if jq empty .github/mcp_config.json 2>/dev/null; then | |
| echo "✓ MCP configuration is valid JSON" | |
| cat .github/mcp_config.json | |
| else | |
| echo "::error::MCP configuration is invalid JSON" | |
| exit 1 | |
| fi | |
| else | |
| echo "::error::MCP configuration file not found at .github/mcp_config.json" | |
| exit 1 | |
| fi | |
| # Check if Recce CLI is installed and has mcp-server command | |
| if command -v recce >/dev/null 2>&1; then | |
| echo "✓ Recce CLI is installed: $(which recce)" | |
| # Test if mcp-server command is available | |
| if recce mcp-server --help >/dev/null 2>&1; then | |
| echo "✓ Recce MCP server command is available" | |
| else | |
| echo "::error::Recce CLI found but mcp-server command not available" | |
| echo "This may indicate the feature branch is not installed correctly" | |
| exit 1 | |
| fi | |
| else | |
| echo "::error::Recce CLI not found in PATH" | |
| exit 1 | |
| fi | |
| - name: Test Recce MCP Server | |
| if: | | |
| (github.event.pull_request != null || github.event.issue.pull_request != null) && | |
| (contains(github.event.comment.body, '/ms2') || contains(github.event.comment.body, '/ms3') || contains(github.event.comment.body, '/one-summary')) | |
| run: | | |
| echo "::group::Testing Recce MCP server startup" | |
| CMD="recce mcp-server" | |
| LOG_FILE="recce_mcp_startup.log" | |
| echo "Command to test: $CMD" | |
| echo "Log file: $LOG_FILE" | |
| echo "" | |
| # 啟動 Recce MCP server 並收集輸出 | |
| echo "Starting Recce MCP server (with 5s timeout)..." | |
| timeout 5s bash -c "$CMD" >"$LOG_FILE" 2>&1 & | |
| MCP_PID=$! | |
| echo "Process started with PID: $MCP_PID" | |
| # 等待 server 初始化 | |
| sleep 2 | |
| # 檢查 process 狀態 | |
| echo "" | |
| echo "Checking process status..." | |
| if ! kill -0 "$MCP_PID" 2>/dev/null; then | |
| echo "::error::Recce MCP server process is not running" | |
| echo "::group::MCP Server Output (Failed Start)" | |
| cat "$LOG_FILE" || echo "No log output captured" | |
| echo "::endgroup::" | |
| exit 1 | |
| else | |
| echo "✓ Process is running (PID: $MCP_PID)" | |
| fi | |
| # 檢查 log 內容是否有錯誤 | |
| echo "" | |
| echo "Checking server logs for errors..." | |
| if grep -qi "error\|exception\|too few parameters\|failed" "$LOG_FILE"; then | |
| echo "::error::Recce MCP server started but errors detected in logs" | |
| echo "::group::MCP Server Output (With Errors)" | |
| cat "$LOG_FILE" | |
| echo "::endgroup::" | |
| kill "$MCP_PID" 2>/dev/null || true | |
| exit 1 | |
| else | |
| echo "✓ No critical errors found in logs" | |
| fi | |
| # 顯示完整 log | |
| echo "" | |
| echo "::group::MCP Server Output (Full Log)" | |
| cat "$LOG_FILE" || echo "No log output captured" | |
| echo "::endgroup::" | |
| # 清理 process | |
| echo "" | |
| echo "Stopping MCP server process..." | |
| kill "$MCP_PID" 2>/dev/null || true | |
| sleep 1 | |
| # 確認 process 已結束 | |
| if kill -0 "$MCP_PID" 2>/dev/null; then | |
| echo "::warning::Process still running, sending SIGKILL..." | |
| kill -9 "$MCP_PID" 2>/dev/null || true | |
| fi | |
| echo "✓ Recce MCP server test completed successfully" | |
| echo "::endgroup::" | |
| - name: Run Claude Code with Recce MCP | |
| id: claude | |
| uses: anthropics/claude-code-action@v1 | |
| with: | |
| anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} | |
| # NOTE: We do NOT set 'prompt' parameter here to preserve Claude action's default behavior | |
| # The comment context is injected into system prompt instead | |
| # This is an optional setting that allows Claude to read CI results on PRs | |
| additional_permissions: | | |
| actions: read | |
| # Configure Recce MCP tools via claude_args | |
| # | |
| # Milestone-Based Tool Access: | |
| # - Default (@claude): General PR review, uses Claude action's default behavior (no custom args) | |
| # - MS1 (/ms1): Git/PR context only - GitHub CLI, Git commands | |
| # - MS2 (/ms2): + dbt metadata - lineage_diff MCP tool only | |
| # - MS3 (/ms3): + data diffs - all Recce MCP tools | |
| # - /one-summary: MS3 with compact format | |
| # | |
| # Available MCP tools (5 total for MS3): | |
| # 1. get_lineage_diff: Get lineage diff (added/removed/modified models) | |
| # 2. row_count_diff: Compare row counts between base and current | |
| # 3. query: Execute SQL query on specified environment (supports Jinja templates) | |
| # 4. query_diff: Compare SQL query results between base and current | |
| # 5. profile_diff: Compare statistical profiles (min/max/avg/distinct count) | |
| # | |
| # For default @claude: don't pass any custom args to preserve default behavior | |
| claude_args: | | |
| ${{ steps.system-prompt.outputs.milestone == 'default' && '' || '--mcp-config .github/mcp_config.json' }} | |
| ${{ steps.system-prompt.outputs.milestone == 'default' && '' || '--max-turns 50' }} | |
| ${{ steps.system-prompt.outputs.has_system_prompt == 'true' && format('--system-prompt-file {0}', steps.system-prompt.outputs.prompt_file) || '' }} | |
| ${{ steps.system-prompt.outputs.milestone == 'ms1' && '--allowedTools "Read(*),Bash(gh pr *),Bash(git diff *),Bash(git log *),Bash(git show *)"' || '' }} | |
| ${{ steps.system-prompt.outputs.milestone == 'ms2' && '--allowedTools "Read(recce.yml),Read(*.yml),Read(*.sql),Bash(gh pr *),Bash(recce mcp-server),Bash(recce version),mcp__recce__get_lineage_diff"' || '' }} | |
| ${{ steps.system-prompt.outputs.milestone == 'ms3' && '--allowedTools "Read(recce.yml),Read(*.yml),Read(*.sql),Bash(recce),Bash(recce mcp-server),Bash(gh pr *),Bash(jq *),mcp__recce__get_lineage_diff,mcp__recce__row_count_diff,mcp__recce__query,mcp__recce__query_diff,mcp__recce__profile_diff"' || '' }} | |
| ${{ steps.system-prompt.outputs.milestone == 'default' && '' || '--debug' }} |