|
| 1 | +# HTTP Transport and Authentication Architecture |
| 2 | + |
| 3 | +--- |
| 4 | + |
| 5 | +## Table of Contents |
| 6 | + |
| 7 | +1. [Overview](#overview) |
| 8 | +2. [Architecture Components](#architecture-components) |
| 9 | +3. [Authentication Flow](#authentication-flow) |
| 10 | +4. [Token Propagation](#token-propagation) |
| 11 | +5. [Thread Model and Context Management](#thread-model-and-context-management) |
| 12 | +6. [Security Considerations](#security-considerations) |
| 13 | +7. [Configuration](#configuration) |
| 14 | +8. [Troubleshooting](#troubleshooting) |
| 15 | + |
| 16 | +--- |
| 17 | + |
| 18 | +## Overview |
| 19 | + |
| 20 | +The SonarQube MCP Server supports two transport modes: |
| 21 | + |
| 22 | +- **Stdio Transport**: Direct process communication (stdin/stdout) |
| 23 | +- **HTTP Transport**: Network-based communication using MCP Streamable HTTP specification |
| 24 | + |
| 25 | +This document focuses on the **HTTP Transport** and its authentication mechanism. |
| 26 | + |
| 27 | +--- |
| 28 | + |
| 29 | +## Architecture Components |
| 30 | + |
| 31 | +### 1. Transport Layer |
| 32 | + |
| 33 | +``` |
| 34 | +┌─────────────────────────────────────────────────────┐ |
| 35 | +│ MCP Client │ |
| 36 | +│ (Cursor, VS Code, etc.) │ |
| 37 | +└────────────────┬────────────────────────────────────┘ |
| 38 | + │ HTTP POST/GET/DELETE |
| 39 | + │ Header: SONARQUBE_TOKEN |
| 40 | + ▼ |
| 41 | +┌─────────────────────────────────────────────────────┐ |
| 42 | +│ Jetty HTTP Server │ |
| 43 | +│ (Port 8822) │ |
| 44 | +└────────────────┬────────────────────────────────────┘ |
| 45 | + │ |
| 46 | + ▼ |
| 47 | +┌─────────────────────────────────────────────────────┐ |
| 48 | +│ Servlet Filter Chain │ |
| 49 | +│ 1. AuthenticationFilter (token extraction) │ |
| 50 | +│ 2. McpSecurityFilter (CORS + Origin validation) │ |
| 51 | +└────────────────┬────────────────────────────────────┘ |
| 52 | + │ |
| 53 | + ▼ |
| 54 | +┌─────────────────────────────────────────────────────┐ |
| 55 | +│ HttpServletStreamableServerTransportProvider │ |
| 56 | +│ (MCP SDK - handles protocol) │ |
| 57 | +└────────────────┬────────────────────────────────────┘ |
| 58 | + │ |
| 59 | + ▼ |
| 60 | +┌─────────────────────────────────────────────────────┐ |
| 61 | +│ MCP Tool Execution │ |
| 62 | +│ (uses token from RequestContext) │ |
| 63 | +└─────────────────────────────────────────────────────┘ |
| 64 | +``` |
| 65 | + |
| 66 | +### 2. Key Classes |
| 67 | + |
| 68 | +#### `HttpServerTransportProvider` |
| 69 | +- **Location**: `org.sonarsource.sonarqube.mcp.transport.HttpServerTransportProvider` |
| 70 | +- **Purpose**: Bootstraps Jetty server and configures servlet transport |
| 71 | + |
| 72 | +#### `AuthenticationFilter` |
| 73 | +- **Location**: `org.sonarsource.sonarqube.mcp.authentication.AuthenticationFilter` |
| 74 | +- **Purpose**: Extracts and validates client tokens |
| 75 | + |
| 76 | +#### `McpSecurityFilter` |
| 77 | +- **Location**: `org.sonarsource.sonarqube.mcp.transport.McpSecurityFilter` |
| 78 | +- **Purpose**: Security and CORS handling |
| 79 | + |
| 80 | +#### `RequestContext` |
| 81 | +- **Location**: `org.sonarsource.sonarqube.mcp.context.RequestContext` |
| 82 | +- **Purpose**: Thread-local storage for request-scoped data |
| 83 | + |
| 84 | +--- |
| 85 | + |
| 86 | +## Authentication Flow |
| 87 | + |
| 88 | +### 1. Client Configuration |
| 89 | + |
| 90 | +Clients configure the HTTP endpoint with authentication: |
| 91 | + |
| 92 | +```json |
| 93 | +{ |
| 94 | + "servers": { |
| 95 | + "sonarqube-http": { |
| 96 | + "url": "http://localhost:8822/mcp", |
| 97 | + "headers": { |
| 98 | + "SONARQUBE_TOKEN": "your-sonarqube-token" |
| 99 | + } |
| 100 | + } |
| 101 | + } |
| 102 | +} |
| 103 | +``` |
| 104 | + |
| 105 | +### 2. Request Flow |
| 106 | + |
| 107 | +``` |
| 108 | +1. Client sends HTTP POST with token header |
| 109 | + └─> SONARQUBE_TOKEN: squ_abc123 |
| 110 | +
|
| 111 | +2. AuthenticationFilter intercepts request |
| 112 | + ├─> Extract token from SONARQUBE_TOKEN header |
| 113 | + ├─> Validate token presence |
| 114 | + ├─> Store in RequestContext (InheritableThreadLocal) |
| 115 | + └─> Pass to next filter |
| 116 | +
|
| 117 | +3. McpSecurityFilter validates security |
| 118 | + ├─> Check Origin header |
| 119 | + ├─> Set CORS headers |
| 120 | + └─> Pass to servlet |
| 121 | +
|
| 122 | +4. MCP SDK servlet processes request |
| 123 | + ├─> Parse JSON-RPC message |
| 124 | + ├─> Dispatch to tool (async, new thread) |
| 125 | + └─> Child thread inherits RequestContext |
| 126 | +
|
| 127 | +5. Tool execution |
| 128 | + ├─> Retrieve token from RequestContext |
| 129 | + ├─> Create ServerApi with client's token |
| 130 | + └─> Call SonarQube API |
| 131 | +
|
| 132 | +6. Async completion |
| 133 | + ├─> AsyncListener triggered |
| 134 | + └─> RequestContext cleared |
| 135 | +``` |
| 136 | + |
| 137 | +### 3. Authentication Modes |
| 138 | + |
| 139 | +#### `TOKEN` Mode (Default) |
| 140 | +- Clients provide their own SonarQube token |
| 141 | +- Token validated by SonarQube API (not MCP server) |
| 142 | +- Uses custom header format: |
| 143 | + - `SONARQUBE_TOKEN: <token>` |
| 144 | + |
| 145 | +#### `OAUTH` Mode (Not Yet Implemented) |
| 146 | +- OAuth 2.1 with PKCE |
| 147 | +- Per MCP specification |
| 148 | +- Future enhancement |
| 149 | + |
| 150 | +--- |
| 151 | + |
| 152 | +## Token Propagation |
| 153 | + |
| 154 | +### Problem: Async Processing and ThreadLocal |
| 155 | + |
| 156 | +The MCP SDK servlet uses **async processing** with **worker threads** to handle tool execution. This creates a challenge for `ThreadLocal` storage: |
| 157 | + |
| 158 | +``` |
| 159 | +Request Thread (Filter) Worker Thread (Tool) |
| 160 | + │ │ |
| 161 | + ├─ Set RequestContext │ |
| 162 | + ├─ doFilter() ──────────────> Async dispatch |
| 163 | + └─ finally { clear() } │ |
| 164 | + [Context cleared!] │ |
| 165 | + ├─ Tool.execute() |
| 166 | + └─ RequestContext.get() ❌ NULL! |
| 167 | +``` |
| 168 | + |
| 169 | +### Solution 1: InheritableThreadLocal |
| 170 | + |
| 171 | +```java |
| 172 | +private static final ThreadLocal<RequestContext> CONTEXT = new InheritableThreadLocal<>(); |
| 173 | +``` |
| 174 | + |
| 175 | +`InheritableThreadLocal` automatically propagates values from parent thread to child threads: |
| 176 | + |
| 177 | +``` |
| 178 | +Request Thread Worker Thread |
| 179 | + │ │ |
| 180 | + ├─ Set RequestContext │ |
| 181 | + ├─ doFilter() ──────────────> Inherit context ✓ |
| 182 | + │ │ |
| 183 | + │ ├─ Tool.execute() |
| 184 | + │ └─ RequestContext.get() ✓ Has token! |
| 185 | + │ │ |
| 186 | + └─ [Wait for async completion] └─ Complete |
| 187 | + │ |
| 188 | + └─ AsyncListener.onComplete() |
| 189 | + └─ RequestContext.clear() ✓ |
| 190 | +``` |
| 191 | + |
| 192 | +### Solution 2: AsyncListener for Cleanup |
| 193 | + |
| 194 | +The filter registers an `AsyncListener` to clean up the context **after** async processing completes. |
| 195 | + |
| 196 | +This ensures: |
| 197 | +1. Token is available during entire request processing |
| 198 | +2. Context is properly cleaned up to prevent memory leaks |
| 199 | +3. No thread pool contamination |
| 200 | + |
| 201 | +### Why Not Store in Servlet Request Attributes? |
| 202 | + |
| 203 | +Servlet request attributes don't propagate to worker threads either. |
| 204 | + |
| 205 | +`InheritableThreadLocal` is the Java standard pattern for this use case. |
| 206 | + |
| 207 | +--- |
| 208 | + |
| 209 | +## Security Considerations |
| 210 | + |
| 211 | +### DNS Rebinding Protection |
| 212 | + |
| 213 | +**Threat**: Attacker could use DNS rebinding to access local MCP server from remote website. |
| 214 | + |
| 215 | +**Mitigation**: `McpSecurityFilter` validates `Origin` header: |
| 216 | + |
| 217 | +**Allowed Origins**: |
| 218 | +- When bound to `127.0.0.1`: Only `http://localhost`, `http://127.0.0.1`, etc. |
| 219 | +- When bound to `0.0.0.0`: All origins allowed (less secure, logs warning) |
| 220 | + |
| 221 | +### Token Security |
| 222 | + |
| 223 | +**Server-side**: |
| 224 | +- Token **never logged** in plain text |
| 225 | +- Token **not validated** by MCP server (delegated to SonarQube API) |
| 226 | +- Token **cleared** from memory after request |
| 227 | + |
| 228 | +**Transport**: |
| 229 | +- HTTPS recommended for production (not enforced for localhost development) |
| 230 | +- Token in HTTP header (not URL query parameter) |
| 231 | + |
| 232 | +**Storage**: |
| 233 | +- Client responsible for secure token storage |
| 234 | +- Token never persisted by MCP server |
| 235 | + |
| 236 | +--- |
| 237 | + |
| 238 | +## References |
| 239 | + |
| 240 | +- [MCP Specification - Streamable HTTP Transport](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports) |
| 241 | +- [MCP Specification - Authorization](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization) |
| 242 | +- [MCP Java SDK Documentation](https://modelcontextprotocol.io/sdk/java/mcp-overview) |
| 243 | +- [Jakarta Servlet Specification](https://jakarta.ee/specifications/servlet/) |
| 244 | + |
| 245 | +--- |
0 commit comments