You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
|`SONARQUBE_TOOLSETS`| Comma-separated list of toolsets to enable. When set, only these toolsets will be available. If not set, default important toolsets are enabled (`analysis`, `issues`, `projects`, `quality-gates`, `rules`, `duplications`, `measures`, `security-hotspots`, `dependency-risks`). **Note:** The `projects` toolset is always enabled as it's required to find project keys for other operations. |
539
-
|`SONARQUBE_READ_ONLY`| When set to `true`, enables read-only mode which disables all write operations (changing issue status for example). This filter is cumulative with `SONARQUBE_TOOLSETS` if both are set. Default: `false`. |
538
+
|`SONARQUBE_TOOLSETS`| Comma-separated list of toolsets to enable. When set, only these toolsets will be available. If not set, default important toolsets are enabled (`analysis`, `issues`, `projects`, `quality-gates`, `rules`, `duplications`, `measures`, `security-hotspots`, `dependency-risks`). **Note:** The `projects` toolset is always enabled as it's required to find project keys for other operations. In HTTP(S) mode, clients can send a `SONARQUBE_TOOLSETS` HTTP header to narrow this further per-request, but cannot enable toolsets beyond what the server was launched with (see [HTTP/HTTPS Transport](#2-http) below). |
539
+
|`SONARQUBE_READ_ONLY`| When set to `true`, enables read-only mode which disables all write operations (changing issue status for example). This filter is cumulative with `SONARQUBE_TOOLSETS` if both are set. Default: `false`. In HTTP(S) mode, clients can send a `SONARQUBE_READ_ONLY` HTTP header to further restrict individual requests to read-only, but cannot lift a server-level read-only restriction (see [HTTP/HTTPS Transport](#2-http) below).|
540
540
541
541
<details>
542
542
<summary>Available Toolsets</summary>
@@ -648,7 +648,7 @@ Unencrypted HTTP transport. Use HTTPS instead for multi-user deployments.
648
648
**Note:** In HTTP(S) mode, the server is stateless — each client request must include a `SONARQUBE_TOKEN` header carrying the user's own SonarQube token. For SonarQube Cloud, the organization is resolved as follows:
649
649
- If `SONARQUBE_ORG` is set at server startup, all requests are routed to that organization. Clients must **not** send a `SONARQUBE_ORG` header — doing so will result in an error.
650
650
- If `SONARQUBE_ORG` is not set at server startup, each client **must** supply a `SONARQUBE_ORG` header on every request.
651
-
651
+
Clients can also narrow the visible tools per-request by supplying `SONARQUBE_TOOLSETS` and/or `SONARQUBE_READ_ONLY` headers; these apply additional filtering on top of the server-level configuration — they can only reduce the scope, never expand it.
652
652
No session state is maintained between requests.
653
653
654
654
#### 3. **HTTPS** (Recommended for Multi-User Production Deployments)
> **Note:**`SONARQUBE_TOOLSETS` and `SONARQUBE_READ_ONLY` are optional per-request headers that narrow the server-level tool set for that specific request. They can only reduce scope — they cannot enable toolsets or lift restrictions beyond what the server was launched with.
718
+
713
719
**Note:** For local development, use Stdio transport instead (the default). HTTPS is intended for multi-user production deployments with proper SSL certificates.
-**Purpose**: Bootstraps Jetty server and configures the stateless servlet transport with a context extractor that reads the `SONARQUBE_TOKEN` header into a `McpTransportContext` for each request
79
+
-**Purpose**: Bootstraps Jetty server and configures the stateless servlet transport with a context extractor that reads `SONARQUBE_TOKEN`, `SONARQUBE_ORG`, `SONARQUBE_TOOLSETS`, and `SONARQUBE_READ_ONLY` headers into a `McpTransportContext` for each request
-**Purpose**: Validates that every request carries a non-blank `SONARQUBE_TOKEN` header. No session state is created or maintained.
83
+
-**Purpose**: Validates that every request carries a non-blank `SONARQUBE_TOKEN` header. No session state is created or maintained. Runs **after**`McpSecurityFilter` so CORS preflight (OPTIONS) requests and Origin validation are handled before authentication, allowing browsers to complete their preflight handshake without a token.
-**Purpose**: Wraps the SDK's `McpStatelessServerHandler` to intercept `tools/list` responses and filter the returned tool list based on the per-request `SONARQUBE_TOOLSETS` and `SONARQUBE_READ_ONLY` headers from `McpTransportContext`. A well-behaved MCP client will only call tools it received from `tools/list`, so filtering the list is sufficient enforcement.
-**Purpose**: In HTTP stateless mode, reads the current request's `McpTransportContext` from a `ThreadLocal` to extract the token and create a per-request `ServerApi` instance
95
+
-**Purpose**: In HTTP stateless mode, reads the current request's `McpTransportContext` from a `ThreadLocal` to extract the token and create a per-request `ServerApi` instance.
81
96
82
97
---
83
98
@@ -103,26 +118,38 @@ Clients configure the HTTP endpoint with authentication:
├─> Resolve org: use server-level env var (header must be absent) OR per-request header (required if env var not set)
@@ -138,6 +165,8 @@ Clients configure the HTTP endpoint with authentication:
138
165
- Uses custom header format:
139
166
-`SONARQUBE_TOKEN: <token>` — required on every request
140
167
-`SONARQUBE_ORG: <org>` — for SonarQube Cloud, identifies the organization. **Mutually exclusive with the server-level `SONARQUBE_ORG` env var**: if the env var is set at startup, clients must not send this header (results in an error); if the env var is not set, clients must send this header on every request
168
+
-`SONARQUBE_TOOLSETS: <comma-separated-keys>` — optional; narrows the server-level toolset for this request (cannot add toolsets beyond what the server was launched with)
169
+
-`SONARQUBE_READ_ONLY: true|false` — optional; can further restrict to read-only for this request (cannot lift a server-level read-only restriction)
141
170
142
171
#### `OAUTH` Mode (Not Yet Implemented)
143
172
- OAuth 2.1 with PKCE
@@ -156,13 +185,15 @@ The MCP SDK makes this context available via a `ThreadLocal<McpTransportContext>
156
185
157
186
```
158
187
1. HTTP Request arrives
159
-
└─> contextExtractor reads SONARQUBE_TOKEN and SONARQUBE_ORG headers
└─> Reads SONARQUBE_TOOLSETS and SONARQUBE_READ_ONLY from McpTransportContext
194
+
└─> Filters the SDK's full tool list down to the per-request allowed subset
164
195
165
-
3. ServerApiProvider.get()
196
+
3. ServerApiProvider.get() (tools/call)
166
197
└─> Reads McpTransportContext from ThreadLocal
167
198
└─> Extracts token and org (strict: server-level env var XOR per-request header — mixing both is an error)
168
199
└─> Creates a fresh ServerApi for this request
@@ -177,6 +208,47 @@ The MCP SDK makes this context available via a `ThreadLocal<McpTransportContext>
177
208
178
209
---
179
210
211
+
## Per-Request Tool Filtering
212
+
213
+
In HTTP(S) mode, clients can **narrow** the set of visible tools on a per-request basis by sending optional HTTP headers. These headers apply additional filtering on top of the server-level `SONARQUBE_TOOLSETS` and `SONARQUBE_READ_ONLY` environment variables — they can only reduce the scope, never expand it:
214
+
215
+
- If the server was launched with `SONARQUBE_READ_ONLY=true`, write tools are absent from the server's tool set and cannot be re-enabled per-request.
216
+
- If the server was launched with a restricted `SONARQUBE_TOOLSETS`, per-request headers can select a subset of those toolsets, but cannot add toolsets beyond what the server was launched with.
217
+
218
+
### Headers
219
+
220
+
| Header | Description |
221
+
|--------|-------------|
222
+
|`SONARQUBE_TOOLSETS`| Comma-separated list of toolset keys to enable for this request (e.g., `issues,quality-gates`). Must be a subset of the server-level `SONARQUBE_TOOLSETS`; toolsets not enabled at server startup are silently ignored. The `projects` toolset is always included regardless of this header. |
223
+
|`SONARQUBE_READ_ONLY`| Set to `true` to restrict this request to read-only tools only. Has no effect if the server was already launched with `SONARQUBE_READ_ONLY=true` (write tools are already absent). |
224
+
225
+
### Filtering
226
+
227
+
Per-request filtering is applied at the **`tools/list` response**: `PerRequestToolFilteringHandler` intercepts the SDK's response and removes tools that are not allowed for this request. The MCP client only sees tools it is permitted to use, reducing its context window accordingly. A well-behaved MCP client will only call tools it received from `tools/list`, so filtering the list is sufficient.
228
+
229
+
### Security Notes
230
+
231
+
- The `SONARQUBE_TOOLSETS` and `SONARQUBE_READ_ONLY` headers are **not** authentication headers. They are read by the SDK's `contextExtractor` inside the servlet — which only runs after both `McpSecurityFilter` and `AuthenticationFilter` have passed the request through. An unauthenticated request is rejected with HTTP 401 by `AuthenticationFilter` before the `contextExtractor` (and thus any filtering logic) runs.
232
+
- The raw header values are **never** echoed back in responses or error messages. Invalid toolset keys are silently discarded by `ToolCategory.parseCategories()`, preventing any header injection via error payloads.
0 commit comments