Skip to content

Commit 0da45d8

Browse files
committed
MCP-171 Implement Token-Based Authentication Strategy
1 parent d8d359b commit 0da45d8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1345
-220
lines changed

README.md

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,11 @@ env = { "SONARQUBE_TOKEN" = "<YOUR_TOKEN>", "SONARQUBE_URL" = "<YOUR_SERVER_URL>
6868
{
6969
"mcpServers": {
7070
"sonarqube-http": {
71-
"url": "<host>:<port>/mcp"
71+
"type": "http",
72+
"url": "http://<host>:<port>/mcp",
73+
"headers": {
74+
"SONARQUBE_TOKEN": "<your-token>"
75+
}
7276
}
7377
}
7478
}
@@ -474,42 +478,58 @@ You should add the following variable when running the MCP Server:
474478

475479
### HTTP Transport
476480

477-
By default, the SonarQube MCP Server uses stdio transport for communication. You can optionally enable HTTP transport for advanced use cases such as web-based clients or multi-user scenarios:
481+
By default, the SonarQube MCP Server uses stdio transport. Enable HTTP transport for multi-user scenarios where multiple clients connect to a shared server:
478482

479483
| Environment variable | Description | Default |
480484
|----------------------|--------------------------------------------------------------------------------------------------|-----------------|
481-
| `SONARQUBE_HTTP_ENABLED` | Enable HTTP transport mode instead of stdio. Set to `true` to enable HTTP mode. | `false` |
482-
| `SONARQUBE_HTTP_PORT` | Port number for HTTP server when HTTP transport is enabled (1024-65535, unprivileged ports only). | `8080` |
483-
| `SONARQUBE_HTTP_HOST` | Host address to bind HTTP server to. Use `127.0.0.1` for localhost only, `0.0.0.0` for all interfaces. | `127.0.0.1` |
485+
| `SONARQUBE_HTTP_ENABLED` | Enable HTTP transport mode. Set to `true` to enable. | `false` |
486+
| `SONARQUBE_HTTP_PORT` | Port number for HTTP server (1-65535). | `8080` |
487+
| `SONARQUBE_HTTP_HOST` | Host to bind. Use `127.0.0.1` for localhost, `0.0.0.0` for all interfaces. | `127.0.0.1` |
484488

485-
#### Security
489+
#### Multi-User Gateway Architecture
486490

487-
The HTTP transport implementation follows the MCP Streamable HTTP specification and includes important security protections:
491+
In HTTP mode, the server acts as a **multi-user gateway**:
492+
- Each client provides their own SonarQube token via the `SONARQUBE_TOKEN` header
493+
- The `SONARQUBE_TOKEN` environment variable is still required at startup for server initialization only
494+
- Clients' tokens are used for all SonarQube API calls (not the server's startup token)
488495

489-
**Network Binding:**
490-
- **Default:** `127.0.0.1` (localhost only) - Recommended for local development
491-
- **0.0.0.0** (all interfaces) - Only use when you need to accept connections from other machines on your network
492-
- ⚠️ **Warning:** Binding to `0.0.0.0` exposes your MCP server to your entire network. Only use this if you understand the security implications.
496+
#### Client Configuration
493497

494-
#### HTTP Transport Examples
498+
Clients must include the `SONARQUBE_TOKEN` header in all requests:
499+
500+
```json
501+
{
502+
"mcpServers": {
503+
"sonarqube-http": {
504+
"type": "http",
505+
"url": "http://localhost:8822/mcp",
506+
"headers": {
507+
"SONARQUBE_TOKEN": "your-sonarqube-token-here"
508+
}
509+
}
510+
}
511+
}
512+
```
513+
514+
#### Docker Example
495515

496-
**Docker with HTTP transport:**
497516
```bash
517+
# Start server (requires token for initialization)
498518
docker run -p 8080:8080 \
499519
-e SONARQUBE_HTTP_ENABLED=true \
500-
-e SONARQUBE_HTTP_PORT=8080 \
501-
-e SONARQUBE_HTTP_HOST="127.0.0.1" \
502-
-e SONARQUBE_TOKEN="<token>" \
520+
-e SONARQUBE_HTTP_PORT=8822 \
521+
-e SONARQUBE_TOKEN="<init-token>" \
503522
-e SONARQUBE_ORG="<org>" \
504523
mcp/sonarqube
524+
505525
```
506526

507-
The server will be available at `http://127.0.0.1:8080/mcp` and supports:
508-
- **POST /mcp** - JSON-RPC requests (tool calls, resource requests)
509-
- **GET /mcp** - Server-Sent Events for streaming notifications
510-
- **OPTIONS /mcp** - CORS preflight for web clients
527+
The server exposes:
528+
- `POST /mcp` - JSON-RPC requests
529+
- `GET /mcp` - Server-Sent Events stream
530+
- `OPTIONS /mcp` - CORS preflight
511531

512-
**Note:** HTTP transport is designed for advanced integration scenarios. Most users should use the default stdio transport with their MCP client.
532+
**Note:** The SonarLint IDE bridge is automatically disabled in HTTP mode as it requires localhost communication.
513533

514534
#### SonarQube Cloud
515535

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
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

Comments
 (0)