SonarQube MCP Server

repository·master·Indexed 20 days ago

https://github.com/sonarsource/sonarqube-mcp-server

A Model Context Protocol (MCP) server that enables AI agents to interact with SonarQube Server or SonarCloud for code quality and security insights. It provides capabilities for SonarQube integration and direct code snippet analysis. The server can be configured for various environments including Antigravity, Claude Code, Codex CLI, GitHub Copilot Coding Agent, and Zed.

Tokens
17.7K
Snippets
34
Records
66
Agent score
69%

What's inside sonarqube-mcp-server

  1. Overview of SonarQube MCP Server

    master

    The SonarQube MCP Server is a Model Context Protocol (MCP) server designed to integrate AI agents with SonarQube Server or SonarCloud. It provides two primary capabilities:

    1. SonarQube Integration: Connects to your existing SonarQube Server or Cloud instances to provide context regarding code quality and security.
    2. Snippet Analysis: Enables the analysis of code snippets directly within the agent's context, even without a full SonarQube project connection.
  2. Compare Stdio vs HTTP Transport modes

    master

    The SonarQube MCP Server supports two transport modes. Choose based on your deployment needs:

    • Stdio Transport: Best for individual developers. It uses environment variables for tokens, has no network exposure, and supports the 'SonarQube for IDE Bridge'. However, it requires one JVM process per user.
    • HTTP Transport: Best for shared server/gateway environments. It uses per-request headers for tokens and allows multiple users to share a single JVM, but has higher setup complexity and does not support the IDE bridge.
    | Feature                      | Stdio                  | HTTP                  |
    |------------------------------|------------------------|-----------------------|
    | **Setup Complexity**         | ✅ Simple               | ⚠️ Moderate           |
    | **Network Exposure**         | ✅ None                 | ⚠️ Optional           |
    | **Multi-User**               | ❌ One process per user | ✅ Shared gateway      |
    | **Token Handling**           | ✅ Environment variable | ✅ Per-request header  |
    | **SonarQube for IDE Bridge** | ✅ Supported            | ❌ Disabled            |
    | **Process Lifecycle**        | ✅ Automatic            | ⚠️ Manual             |
    | **Resource Usage**           | ⚠️ One JVM per user    | ✅ Shared JVM          |
    | **Use Case**                 | Individual developers  | Shared server/gateway |
  3. Reduce context bloat using Workspace Mount

    master

    By default, the analyze_code_snippet tool requires the agent to pass full file content as fileContent, which increases context window usage.

    To avoid this, mount your project directory into the container at /app/mcp-workspace. When this mount is detected, the server reads files directly from disk using the project-relative filePath argument, and file content is never passed through the agent context.

    When the mount is active:

    • run_advanced_code_analysis becomes available (if entitled).
    • analyze_code_snippet requires filePath instead of fileContent.
    {
      "args": [
        "run", "-i", "--rm", "--init", "--pull=always",
        "-e", "SONARQUBE_TOKEN",
        "-e", "SONARQUBE_ORG",
        "-v", "/path/to/your/project:/app/mcp-workspace",
        "sonarsource/sonarqube-mcp"
      ]
    }
  4. Manage environment variables for proxied MCP servers

    master

    Proxied MCP servers use an explicit allowlist model for security. They do not inherit the full environment of the parent process. Only variables explicitly defined in the env field or listed in the inherits field are passed to the proxied server.

    Scenarios:

    1. Explicit Values: Use the env field to set specific values.
    2. Inheritance: Use the inherits field to pass specific variables from the parent process (e.g., SONARQUBE_TOKEN).
    3. Overriding: If a variable is listed in both env and inherits, the value in env takes precedence.

    Security Note: This prevents accidental credential leakage. If neither env nor inherits are specified, the proxied server starts with an empty environment.

    // Example: Inheriting specific variables and setting an explicit one
    {
      "name": "my-server",
      "command": "python",
      "args": ["-m", "my_mcp_server"],
      "env": {
        "DEBUG": "true"
      },
      "inherits": ["SONARQUBE_TOKEN", "SONARQUBE_URL"]
    }
  5. Understand the stateless token propagation design

    master

    The server uses a stateless per-request token extraction model via HttpServletStatelessServerTransport. This ensures high scalability and isolation.

    Request Lifecycle

    1. Extraction: For every incoming POST request, a contextExtractor runs synchronously to populate an McpTransportContext with headers from the request (e.g., Authorization: Bearer, SONARQUBE_ORG, SONARQUBE_TOOLSETS, SONARQUBE_READ_ONLY).
    2. Storage: The context is stored in a ThreadLocal<McpTransportContext> for the duration of the request thread.
    3. Filtering: The PerRequestToolFilteringHandler reads the toolset and read-only headers from the context to filter the tools/list response.
    4. Execution: When a tool is called (tools/call), ServerApiProvider.get() retrieves the token and organization from the ThreadLocal context to create a fresh ServerApi for that specific request.

    Security Constraints

    • Strictness: The server enforces a strict rule: you must use either a server-level environment variable OR a per-request header for the token. Mixing both is considered an error.
  6. Understand the Authentication Flow and Request Lifecycle

    master

    The server uses a stateless architecture where authentication is validated on every request. The lifecycle is as follows:

    1. Security/CORS: McpSecurityFilter handles CORS preflight (OPTIONS) requests without requiring authentication, allowing browsers to complete the handshake.
    2. Authentication: AuthenticationFilter extracts the token from Authorization: Bearer (or the deprecated SONARQUBE_TOKEN) and rejects the request with a 401 if no valid token is found.
    3. Context Extraction: HttpServletStatelessServerTransport extracts all relevant headers (SONARQUBE_ORG, SONARQUBE_TOOLSETS, etc.) into a McpTransportContext stored in a ThreadLocal.
    4. Tool Filtering: PerRequestToolFilteringHandler intercepts tools/list to apply requested filters.
    5. Execution: When a tool is called, SonarQubeMcpServer reads the context from the ThreadLocal to resolve the organization and token, then executes the call against the SonarQube API.
  7. Choose a transport mode: Stdio vs Streamable HTTP

    master

    The SonarQube MCP Server supports two transport mechanisms:

    1. Stdio (Default)

    • Use case: Local development and single-user setups (e.g., Cursor, Claude Code, VS Code).
    • Configuration: No SONARQUBE_TRANSPORT variable needed.

    2. Streamable HTTP

    • Use case: Remote or multi-user deployments.
    • Configuration: Set SONARQUBE_TRANSPORT=http or https.
    • Statelessness: In this mode, the server is stateless. Each client request must include an Authorization: Bearer <token> header.
    • Organization Resolution:
      • If SONARQUBE_ORG was set at server startup, all requests are routed to that org. Clients must not send a SONARQUBE_ORG header.
      • If SONARQUBE_ORG was not set at startup, clients must supply a SONARQUBE_ORG header on every request.
    • Endpoints: Exposes /mcp for MCP traffic, and unauthenticated /health and /info endpoints for monitoring.
  8. Narrow tool visibility using per-request HTTP headers

    master

    In HTTP(S) mode, clients can reduce the set of visible tools for a specific request by sending optional HTTP headers. These headers can only reduce the scope of available tools; they cannot expand it beyond the server-level configuration set via environment variables.

    Available Headers

    HeaderDescription
    SONARQUBE_TOOLSETSA comma-separated list of toolset keys to enable for this request (e.g., issues,quality-gates). This must be a subset of the server-level SONARQUBE_TOOLSETS. The projects toolset is always included.
    SONARQUBE_READ_ONLYSet to true to restrict the request to read-only tools. This has no effect if the server was already started with SONARQUBE_READ_ONLY=true

    How it works

    Filtering is applied during the tools/list response. The PerRequestToolFilteringHandler intercepts the response and removes unauthorized tools, ensuring the MCP client only sees and attempts to call permitted tools.

    {
      "mcpServers": {
        "sonarqube-https": {
          "url": "https://your-server:8443/mcp",
          "headers": {
            "Authorization": "Bearer <your-token>",
            "SONARQUBE_ORG": "<your-org>",
            "SONARQUBE_TOOLSETS": "issues,quality-gates",
            "SONARQUBE_READ_ONLY": "true"
          }
        }
      }
    }
  9. How tool loading and analyzer synchronization works

    master

    The SonarQube MCP Server uses a two-phase initialization process to ensure that tools are available immediately without waiting for slow analyzer downloads.

    Phase 1: Immediate Startup (Synchronous)

    • The backend is initialized with an empty set of analyzers.
    • IDE bridge availability is checked.
    • All tools are loaded and registered.
    • The MCP server starts, making the full tool list available to the user immediately.

    Phase 2: Analyzer Download (Background)

    • While the server is running, a background thread downloads and synchronizes analyzer plugins.
    • Once complete, the BackendService.restartWithAnalyzers() method is called to restart the backend with the new analyzers.
    • Code analysis tools become fully functional only after this phase completes.

    This architecture allows users to perform REST API-based tasks (like project search or issue management) immediately, even while the system is still preparing for deep code analysis.

  10. Integration test architecture and dependencies

    master

    The integration tests utilize Testcontainers to orchestrate the server and a proxied server binary within Alpine Linux containers.

    Key Components

    • Binary Compatibility: The sonar-context-augmentation binary is compiled for Alpine Linux (musl libc) and is intended to run inside the test containers rather than directly on most host systems.
    • Container Dependencies: To match the production environment, the test containers automatically include:
      • nodejs: For runtime dependencies.
      • wget: For HTTP testing utilities.
  11. Understand the Stdio Transport Architecture

    master

    The SonarQube MCP Server uses a Stdio Transport mode for direct process communication between an MCP Client (like Cursor, VS Code, or GitHub Copilot) and the server (running as a Java process).

    In this mode, the client spawns the server as a child process. The server's StdioServerTransportProvider reads incoming JSON-RPC messages from System.in and writes responses back to System.out. This architecture is highly recommended for individual developers because it is simple to set up, has no network exposure, and automatically manages the process lifecycle.

    ┌─────────────────────────────────────────────────────┐
    │                  MCP Client                         │
    │         (Cursor, VS Code, GitHub Copilot)           │
    └────────────────┬────────────────────────────────────┘
                     │ spawn process
                     ▼
    ┌─────────────────────────────────────────────────────┐
    │            SonarQube MCP Server                     │
    │              (Java Process)                         │
    │                                                     │
    │  ┌───────────────────────────────────────────┐     │
    │  │  StdioServerTransportProvider             │     │
    │  │  - Reads from System.in                   │     │
    │  │  - Writes to System.out                   │     │
    │  └───────────────────────────────────────────┘     │
    │                     │                               │
    │                     ▼                               │
    │  ┌───────────────────────────────────────────┐     │
    │  │  MCP Session                              │     │
    │  │  - JSON-RPC message handling              │     │
    │  │  - Tool dispatch                          │     │
    │  └───────────────────────────────────────────┘     │
    │                     │                               │
    │                     ▼                               │
    │  ┌───────────────────────────────────────────┐     │
    │  │  MCP Tools                                │     │
    │  │  - Use global ServerApi instance          │     │
    │  │  - SonarQube API calls                    │     │
    │  └───────────────────────────────────────────┘     │
    └─────────────────────────────────────────────────────┘
                     │ stdin/stdout
                     ▼
    ┌─────────────────────────────────────────────────────┐
    │                  MCP Client                         │
    └─────────────────────────────────────────────────────┘
  12. Understand Branch vs Pull Request Context

    master

    When querying SonarQube, you must distinguish between branch and pull request contexts to get the correct data.

    • Pull Request Analysis: Use the pullRequest parameter. Use list_pull_requests to discover available keys. Never pass a git branch name to the pullRequest parameter.
    • Branch Analysis: Use the branch parameter.
      • For SonarQube Cloud: Use list_branches with branchTypes: "SHORT" for feature branches and "LONG" for main/develop.
      • For SonarQube Server: Supports both branch and pull request.

    Rule of Thumb: If the user is working on a pull request, prefer pullRequest. Otherwise, use branch.

    // Analyze a short-lived branch
    list_branches({ projectKey: "my-project", branchTypes: "SHORT" });
    search_sonar_issues_in_projects({ projects: ["my-project"], branch: "feature/my-fix" });
    
    // Analyze an open pull request
    list_pull_requests({ projectKey: "my-project" });
    search_sonar_issues_in_projects({ projects: ["my-project"], pullRequest: "123" });