Building an AI-Powered Automated Code Reviewer in Python

  • SEO Title: Build a Custom AI Code Reviewer Using Python and MCP
  • Meta Description: Learn how to write an automated GitHub Action that leverages Python, MCP, and AI models to perform automated security and style code reviews.
  • Target Audience: CS Students, Python Developers, Open-Source Contributors.
  • Primary Focus: Building an automated GitHub Action that uses MCP and LLMs to review pull requests against custom style and security rules.

Introduction: The Noise Problem in Off-the-Shelf AI Code Reviews

Out-of-the-box AI code review tools often bombard pull requests with low-value, generic feedback. They flag standard naming conventions or invent non-existent security flaws because they lack context about your team’s specific architecture, internal APIs, and style guidelines.

By building a custom AI code reviewer in Python, you bridge this context gap. Utilizing the Model Context Protocol (MCP), your review agent queries your repository’s type definitions, linting rules, and historical security policies dynamically before evaluating code. Paired with a simple GitHub Action, you get a tailored, automated reviewer that flags high-priority vulnerabilities without the noise.

Project Prerequisites

Ensure you have the following installed and configured before starting:

  • Python 3.11+ (for native async capabilities and static typing support)
  • GitHub Repository with GitHub Actions enabled
  • MCP Python SDK (pip install "mcp[cli]" or uv add "mcp[cli]")
  • LLM API Access (OpenAI or Anthropic API key stored as a GitHub Repository Secret)

Step 1: Setting Up the GitHub Action Workflow

Create a GitHub Action workflow file at .github/workflows/ai-code-review.yml. This workflow triggers on every pull request event, checks out the PR diff, sets up Python, and invokes your review script.

YAML

name: AI Code Reviewer

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  review:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: write

    steps:
      - name: Checkout Repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Install Dependencies
        run: |
          python -m pip install --upgrade pip
          pip install mcp openai requests

      - name: Run AI Code Review
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          PR_NUMBER: ${{ github.event.pull_request.number }}
          REPOSITORY: ${{ github.repository }}
        run: python scripts/review_agent.py

Step 2: Building the MCP Server for Code Analysis

Next, build an inline MCP server in Python using FastMCP. This server exposes internal context tools (fetching git diffs, parsing linting rules, and retrieving type definitions) to the AI agent.

Python

# scripts/mcp_server.py
import subprocess
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("CodeReviewContext")

@mcp.tool()
def get_pr_diff() -> str:
    """Fetches the unified git diff for the current pull request against main."""
    result = subprocess.run(
        ["git", "diff", "origin/main...HEAD"], 
        capture_output=True, text=True, check=True
    )
    return result.stdout[:12000]  # Truncate to preserve context window

@mcp.tool()
def get_style_rules() -> str:
    """Returns local project formatting and security policy rules."""
    return """
    1. OWASP Risk Check: Never execute raw SQL queries; use Parameterized queries.
    2. Exception Handling: Never use bare 'except:'; catch specific Exception subclasses.
    3. Type Hinting: All public functions must declare explicit return type hints.
    """

if __name__ == "__main__":
    mcp.run()

Step 3: Constructing the Review Agent

Now construct the execution agent (scripts/review_agent.py) using the official Python SDK. The agent queries tools on your local MCP server, gathers contextual rules, evaluates the PR diff, and returns structured JSON suggestions.

Python

# scripts/review_agent.py
import os
import json
import openai
from scripts.mcp_server import get_pr_diff, get_style_rules

def generate_review() -> list[dict]:
    client = openai.OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
    
    diff = get_pr_diff()
    rules = get_style_rules()

    prompt = f"""
    You are an expert Security and Code Quality Auditor.
    Review the following Git Diff against our custom Project Rules:

    --- PROJECT RULES ---
    {rules}

    --- GIT DIFF ---
    {diff}

    Return feedback strictly as a JSON list of objects. Each object MUST contain:
    - "path": (string) relative file path
    - "line": (integer) exact modified line number in the diff
    - "body": (string) markdown feedback with severity emoji (🔴 Critical, 🟡 Minor)
    """

    response = client.chat.completions.create(
        model="gpt-4o",
        response_format={"type": "json_object"},
        messages=[{"role": "user", "content": prompt}]
    )
    
    result = json.loads(response.choices[0].message.content)
    return result.get("comments", [])

Step 4: Posting Inline Comments to GitHub

Finally, map the structured JSON output directly onto PR lines using the GitHub REST API (/repos/{owner}/{repo}/pulls/{pr_number}/reviews).

Python

# scripts/post_comments.py
import os
import requests
from scripts.review_agent import generate_review

def post_github_review():
    token = os.getenv("GITHUB_TOKEN")
    repo = os.getenv("REPOSITORY")
    pr_number = os.getenv("PR_NUMBER")
    
    comments = generate_review()
    if not comments:
        print("No issues detected.")
        return

    # Get the head commit SHA for the PR
    pr_url = f"https://api.github.com/repos/{repo}/pulls/{pr_number}"
    headers = {
        "Authorization": f"Bearer {token}",
        "Accept": "application/vnd.github.v3+json"
    }
    commit_sha = requests.get(pr_url, headers=headers).json()["head"]["sha"]

    review_url = f"https://api.github.com/repos/{repo}/pulls/{pr_number}/reviews"
    payload = {
        "commit_id": commit_sha,
        "event": "COMMENT",
        "comments": comments
    }

    res = requests.post(review_url, headers=headers, json=payload)
    if res.status_code == 200:
        print("Successfully posted inline code review comments.")
    else:
        print(f"Failed to post review: {res.status_code} - {res.text}")

if __name__ == "__main__":
    post_github_review()

Frequently Asked Questions (FAQ)

How do you keep API costs low when running code reviews on every commit?

To optimize token usage and reduce API expenses across frequent commits:

  • Diff Truncation & Filtering: Ignore non-code assets (e.g., .md, .json, package lockfiles) in your git diff script before sending payloads to the LLM.
  • Run on Event Conditions: Configure your GitHub Action trigger to run only when PRs are opened or marked ready for review, rather than on every minor push:YAMLon: pull_request: types: [opened, ready_for_review]
  • Use Tiered Models: Route routine style checks to fast, cost-effective models (e.g., gpt-4o-mini or Claude 3.5 Haiku) and reserve premier models exclusively for security-critical modules.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *