Skip to content

Commit 772fce8

Browse files
committed
MCP-195 Let users selectively enable tools
1 parent 3ed6076 commit 772fce8

33 files changed

+482
-34
lines changed

README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,49 @@ You should add the following variable when running the MCP Server:
478478
| `STORAGE_PATH` | Mandatory absolute path to a writable directory where SonarQube MCP Server will store its files (e.g., for creation, updates, and persistence), it is automatically provided when using Docker |
479479
| `SONARQUBE_IDE_PORT` | Optional port number between 64120 and 64130 used to connect SonarQube MCP Server with SonarQube for IDE. |
480480

481+
### Selective Tool Enablement
482+
483+
By default, all tools are enabled. You can selectively enable specific toolsets to reduce context overhead and focus on specific functionality.
484+
485+
| Environment variable | Description |
486+
|------------------------|-------------------------------------------------------------------------------------------------------------------|
487+
| `SONARQUBE_TOOLSETS` | Comma-separated list of toolsets to enable. When set, only these toolsets will be available. If not set, all tools are enabled. **Note:** The `projects` toolset is always enabled as it's required to find project keys for other operations. |
488+
489+
<details>
490+
491+
**<summary>Available Toolsets</summary>**
492+
493+
| Toolset | Key | Description |
494+
|--------------------|---------------------|----------------------------------------------------------------------|
495+
| **Analysis** | `analysis` | Code analysis tools (analyze code snippets and files) |
496+
| **Issues** | `issues` | Search and manage SonarQube issues |
497+
| **Projects** | `projects` | Browse and search SonarQube projects |
498+
| **Quality Gates** | `quality-gates` | Access quality gates and their status |
499+
| **Rules** | `rules` | Browse and search SonarQube rules |
500+
| **Sources** | `sources` | Access source code and SCM information |
501+
| **Measures** | `measures` | Retrieve metrics and measures (includes both measures and metrics tools) |
502+
| **Languages** | `languages` | List supported programming languages |
503+
| **Portfolios** | `portfolios` | Manage portfolios and enterprises (Cloud and Server) |
504+
| **System** | `system` | System administration tools (Server only) |
505+
| **Webhooks** | `webhooks` | Manage webhooks |
506+
| **Dependency Risks**| `dependency-risks` | Analyze dependency risks and security issues (SCA) |
507+
508+
#### Example
509+
510+
**Enable analysis, issues, and quality gates toolsets (using Docker with SonarQube Cloud):**
511+
512+
```bash
513+
docker run -i --name sonarqube-mcp-server --rm \
514+
-e SONARQUBE_TOKEN="<token>" \
515+
-e SONARQUBE_ORG="<org>" \
516+
-e SONARQUBE_TOOLSETS="analysis,issues,quality-gates" \
517+
mcp/sonarqube
518+
```
519+
520+
Note: The `projects` toolset is always enabled automatically, so you don't need to include it in `SONARQUBE_TOOLSETS`.
521+
522+
</details>
523+
481524
#### SonarQube Cloud
482525

483526
To enable full functionality, the following environment variables must be set before starting the server:

src/main/java/org/sonarsource/sonarqube/mcp/SonarQubeMcpServer.java

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -150,40 +150,42 @@ private void initializeServices() {
150150
LOG.info("Startup initialization completed");
151151
}
152152
private void initTools() {
153+
var allTools = new ArrayList<Tool>();
154+
153155
boolean useIdeBridge = false;
154156
if (!mcpConfiguration.isHttpEnabled() && mcpConfiguration.getSonarQubeIdePort() != null) {
155157
var sonarqubeIdeBridgeClient = initializeBridgeClient(mcpConfiguration);
156158
if (sonarqubeIdeBridgeClient.isAvailable()) {
157159
LOG.info("SonarQube for IDE integration detected - loading IDE specific tools");
158160
backendService.notifySonarQubeIdeIntegration();
159-
this.supportedTools.add(new AnalyzeFileListTool(sonarqubeIdeBridgeClient));
160-
this.supportedTools.add(new ToggleAutomaticAnalysisTool(sonarqubeIdeBridgeClient));
161+
allTools.add(new AnalyzeFileListTool(sonarqubeIdeBridgeClient));
162+
allTools.add(new ToggleAutomaticAnalysisTool(sonarqubeIdeBridgeClient));
161163
useIdeBridge = true;
162164
}
163165
}
164166

165167
// Load standard analysis tool when IDE bridge is not used
166168
if (!useIdeBridge) {
167169
LOG.info("SonarQube for IDE integration not detected - loading standard analysis tool");
168-
this.supportedTools.add(new AnalysisTool(backendService, this));
170+
allTools.add(new AnalysisTool(backendService, this));
169171
}
170172

171173
// SonarQube Cloud specific tools
172174
if (mcpConfiguration.isSonarCloud()) {
173175
LOG.info("SonarQube Cloud detected - loading SonarQube Cloud specific tools");
174-
this.supportedTools.add(new ListEnterprisesTool(this));
176+
allTools.add(new ListEnterprisesTool(this));
175177
} else {
176178
LOG.info("SonarQube Server detected - loading SonarQube Server specific tools");
177179
// SonarQube Server specific tools
178-
this.supportedTools.addAll(List.of(
180+
allTools.addAll(List.of(
179181
new SystemHealthTool(this),
180182
new SystemInfoTool(this),
181183
new SystemLogsTool(this),
182184
new SystemPingTool(this),
183185
new SystemStatusTool(this)));
184186
}
185187

186-
this.supportedTools.addAll(List.of(
188+
allTools.addAll(List.of(
187189
new ChangeIssueStatusTool(this),
188190
new SearchMyProjectsTool(this),
189191
new SearchIssuesTool(this),
@@ -200,6 +202,13 @@ private void initTools() {
200202
new ListWebhooksTool(this),
201203
new ListPortfoliosTool(this, mcpConfiguration.isSonarCloud()),
202204
new SearchDependencyRisksTool(this, sonarQubeVersionChecker)));
205+
206+
// Filter tools based on enabled categories
207+
this.supportedTools.addAll(allTools.stream()
208+
.filter(tool -> mcpConfiguration.isToolCategoryEnabled(tool.getCategory()))
209+
.toList());
210+
211+
LOG.info("Loaded " + this.supportedTools.size() + " tools after category filtering");
203212
}
204213

205214
public void start() {

src/main/java/org/sonarsource/sonarqube/mcp/configuration/McpServerLaunchConfiguration.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@
2121
import java.nio.file.Paths;
2222
import java.util.Locale;
2323
import java.util.Map;
24+
import java.util.Set;
2425
import javax.annotation.CheckForNull;
2526
import javax.annotation.Nullable;
2627
import org.jetbrains.annotations.NotNull;
2728
import org.sonarsource.sonarqube.mcp.SonarQubeMcpServer;
2829
import org.sonarsource.sonarqube.mcp.authentication.AuthMode;
30+
import org.sonarsource.sonarqube.mcp.tools.ToolCategory;
2931

3032
import static java.util.Objects.requireNonNull;
3133

@@ -41,6 +43,9 @@ public class McpServerLaunchConfiguration {
4143
private static final String SONARQUBE_IDE_PORT_ENV = "SONARQUBE_IDE_PORT";
4244
private static final String TELEMETRY_DISABLED = "TELEMETRY_DISABLED";
4345

46+
// Tool category configuration
47+
private static final String SONARQUBE_TOOLSETS = "SONARQUBE_TOOLSETS";
48+
4449
// HTTP/HTTPS transport configuration
4550
private static final String SONARQUBE_TRANSPORT = "SONARQUBE_TRANSPORT";
4651
private static final String SONARQUBE_HTTP_PORT = "SONARQUBE_HTTP_PORT";
@@ -89,6 +94,9 @@ public class McpServerLaunchConfiguration {
8994
private final String httpsTruststoreType;
9095
@Nullable
9196
private final AuthMode authMode;
97+
98+
// Tool category configuration
99+
private final Set<ToolCategory> enabledToolsets;
92100

93101
public McpServerLaunchConfiguration(Map<String, String> environment) {
94102
var storagePathString = getValueViaEnvOrPropertyOrDefault(environment, STORAGE_PATH, null);
@@ -137,6 +145,10 @@ public McpServerLaunchConfiguration(Map<String, String> environment) {
137145
this.httpsTruststoreType = getValueViaEnvOrPropertyOrDefault(environment, SONARQUBE_HTTPS_TRUSTSTORE_TYPE, DEFAULT_KEYSTORE_TYPE);
138146

139147
this.authMode = parseAuthMode(environment);
148+
149+
// Parse tool category configuration
150+
var toolsetsStr = getValueViaEnvOrPropertyOrDefault(environment, SONARQUBE_TOOLSETS, null);
151+
this.enabledToolsets = ToolCategory.parseCategories(toolsetsStr);
140152
}
141153

142154
@NotNull
@@ -324,4 +336,29 @@ private AuthMode parseAuthMode(Map<String, String> environment) {
324336
return null;
325337
}
326338

339+
/**
340+
* Determines if a tool category should be enabled based on configuration.
341+
* Rules:
342+
* 1. PROJECTS toolset is always enabled (required to find project keys)
343+
* 2. If SONARQUBE_TOOLSETS is set, only those toolsets (plus PROJECTS) are enabled
344+
* 3. If SONARQUBE_TOOLSETS is not set, all toolsets are enabled (default)
345+
*/
346+
public boolean isToolCategoryEnabled(ToolCategory category) {
347+
// PROJECTS is always enabled as it's required to find project keys
348+
if (category == ToolCategory.PROJECTS) {
349+
return true;
350+
}
351+
352+
// If toolsets is explicitly set, only enable those
353+
if (!enabledToolsets.isEmpty()) {
354+
return enabledToolsets.contains(category);
355+
}
356+
// Default: enable all categories
357+
return true;
358+
}
359+
360+
public Set<ToolCategory> getEnabledToolsets() {
361+
return Set.copyOf(enabledToolsets);
362+
}
363+
327364
}

src/main/java/org/sonarsource/sonarqube/mcp/tools/Tool.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,21 @@
2424

2525
public abstract class Tool {
2626
private final McpSchema.Tool definition;
27+
private final ToolCategory category;
2728

28-
protected Tool(McpSchema.Tool definition) {
29+
protected Tool(McpSchema.Tool definition, ToolCategory category) {
2930
this.definition = definition;
31+
this.category = category;
3032
}
3133

3234
public McpSchema.Tool definition() {
3335
return definition;
3436
}
3537

38+
public ToolCategory getCategory() {
39+
return category;
40+
}
41+
3642
public abstract Result execute(Arguments arguments);
3743

3844
public static class Arguments {
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* SonarQube MCP Server
3+
* Copyright (C) 2025 SonarSource
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12+
* See the Sonar Source-Available License for more details.
13+
*
14+
* You should have received a copy of the Sonar Source-Available License
15+
* along with this program; if not, see https://sonarsource.com/license/ssal/
16+
*/
17+
package org.sonarsource.sonarqube.mcp.tools;
18+
19+
import java.util.Arrays;
20+
import java.util.Locale;
21+
import java.util.Objects;
22+
import java.util.Set;
23+
import java.util.stream.Collectors;
24+
import javax.annotation.CheckForNull;
25+
import javax.annotation.Nullable;
26+
27+
/**
28+
* Categories of tools that can be selectively enabled or disabled.
29+
* Each tool belongs to exactly one category.
30+
*/
31+
public enum ToolCategory {
32+
ANALYSIS("analysis"),
33+
ISSUES("issues"),
34+
PROJECTS("projects"),
35+
QUALITY_GATES("quality-gates"),
36+
RULES("rules"),
37+
SOURCES("sources"),
38+
MEASURES("measures"),
39+
LANGUAGES("languages"),
40+
PORTFOLIOS("portfolios"),
41+
SYSTEM("system"),
42+
WEBHOOKS("webhooks"),
43+
DEPENDENCY_RISKS("dependency-risks");
44+
45+
private final String key;
46+
47+
ToolCategory(String key) {
48+
this.key = key;
49+
}
50+
51+
public String getKey() {
52+
return key;
53+
}
54+
55+
@CheckForNull
56+
public static ToolCategory fromKey(@Nullable String key) {
57+
if (key == null || key.isBlank()) {
58+
return null;
59+
}
60+
var normalizedKey = key.trim().toLowerCase(Locale.getDefault());
61+
for (ToolCategory category : values()) {
62+
if (category.key.equals(normalizedKey)) {
63+
return category;
64+
}
65+
}
66+
return null;
67+
}
68+
69+
/**
70+
* Parses a comma-separated list of category keys into a Set of ToolCategory.
71+
* Invalid keys are silently ignored.
72+
*/
73+
public static Set<ToolCategory> parseCategories(@Nullable String categoriesStr) {
74+
if (categoriesStr == null || categoriesStr.isBlank()) {
75+
return Set.of();
76+
}
77+
return Arrays.stream(categoriesStr.split(","))
78+
.map(String::trim)
79+
.map(ToolCategory::fromKey)
80+
.filter(Objects::nonNull)
81+
.collect(Collectors.toSet());
82+
}
83+
84+
public static Set<ToolCategory> all() {
85+
return Set.of(values());
86+
}
87+
}
88+

src/main/java/org/sonarsource/sonarqube/mcp/tools/analysis/AnalysisTool.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import org.sonarsource.sonarqube.mcp.slcore.BackendService;
3535
import org.sonarsource.sonarqube.mcp.tools.SchemaToolBuilder;
3636
import org.sonarsource.sonarqube.mcp.tools.Tool;
37+
import org.sonarsource.sonarqube.mcp.tools.ToolCategory;
3738

3839
import static java.util.stream.Collectors.toMap;
3940
import static org.sonarsource.sonarqube.mcp.analysis.LanguageUtils.getSonarLanguageFromInput;
@@ -59,7 +60,8 @@ public AnalysisTool(BackendService backendService, ServerApiProvider serverApiPr
5960
.addRequiredStringProperty(SNIPPET_PROPERTY, "Code snippet or full file content")
6061
.addStringProperty(LANGUAGE_PROPERTY, "Language of the code snippet")
6162
.setReadOnlyHint()
62-
.build());
63+
.build(),
64+
ToolCategory.ANALYSIS);
6365
this.backendService = backendService;
6466
this.serverApiProvider = serverApiProvider;
6567
}

src/main/java/org/sonarsource/sonarqube/mcp/tools/analysis/AnalyzeFileListTool.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.sonarsource.sonarqube.mcp.bridge.SonarQubeIdeBridgeClient;
2121
import org.sonarsource.sonarqube.mcp.tools.SchemaToolBuilder;
2222
import org.sonarsource.sonarqube.mcp.tools.Tool;
23+
import org.sonarsource.sonarqube.mcp.tools.ToolCategory;
2324

2425
public class AnalyzeFileListTool extends Tool {
2526
private static final McpLogger LOG = McpLogger.getInstance();
@@ -37,7 +38,8 @@ public AnalyzeFileListTool(SonarQubeIdeBridgeClient bridgeClient) {
3738
"This tool connects to a running SonarQube for IDE instance to perform code quality analysis on a list of files.")
3839
.addArrayProperty(FILE_ABSOLUTE_PATHS_PROPERTY, "string", "List of absolute file paths to analyze")
3940
.setReadOnlyHint()
40-
.build());
41+
.build(),
42+
ToolCategory.ANALYSIS);
4143
this.bridgeClient = bridgeClient;
4244
}
4345

src/main/java/org/sonarsource/sonarqube/mcp/tools/analysis/ToggleAutomaticAnalysisTool.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.sonarsource.sonarqube.mcp.bridge.SonarQubeIdeBridgeClient;
2020
import org.sonarsource.sonarqube.mcp.tools.SchemaToolBuilder;
2121
import org.sonarsource.sonarqube.mcp.tools.Tool;
22+
import org.sonarsource.sonarqube.mcp.tools.ToolCategory;
2223

2324
public class ToggleAutomaticAnalysisTool extends Tool {
2425

@@ -35,7 +36,8 @@ public ToggleAutomaticAnalysisTool(SonarQubeIdeBridgeClient bridgeClient) {
3536
"When enabled, SonarQube for IDE will automatically analyze files as they are modified in the working directory. " +
3637
"When disabled, automatic analysis is turned off.")
3738
.addBooleanProperty(ENABLED_PROPERTY, "Enable or disable the automatic analysis")
38-
.build());
39+
.build(),
40+
ToolCategory.ANALYSIS);
3941
this.bridgeClient = bridgeClient;
4042
}
4143

src/main/java/org/sonarsource/sonarqube/mcp/tools/dependencyrisks/SearchDependencyRisksTool.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.sonarsource.sonarqube.mcp.serverapi.sca.response.DependencyRisksResponse;
2323
import org.sonarsource.sonarqube.mcp.tools.SchemaToolBuilder;
2424
import org.sonarsource.sonarqube.mcp.tools.Tool;
25+
import org.sonarsource.sonarqube.mcp.tools.ToolCategory;
2526

2627
public class SearchDependencyRisksTool extends Tool {
2728

@@ -43,7 +44,8 @@ public SearchDependencyRisksTool(ServerApiProvider serverApiProvider, SonarQubeV
4344
.addStringProperty(BRANCH_KEY_PROPERTY, "The branch key")
4445
.addStringProperty(PULL_REQUEST_KEY_PROPERTY, "The pull request key")
4546
.setReadOnlyHint()
46-
.build());
47+
.build(),
48+
ToolCategory.DEPENDENCY_RISKS);
4749
this.serverApiProvider = serverApiProvider;
4850
this.sonarQubeVersionChecker = sonarQubeVersionChecker;
4951
}

src/main/java/org/sonarsource/sonarqube/mcp/tools/enterprises/ListEnterprisesTool.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.sonarsource.sonarqube.mcp.serverapi.enterprises.response.ListResponse;
2222
import org.sonarsource.sonarqube.mcp.tools.SchemaToolBuilder;
2323
import org.sonarsource.sonarqube.mcp.tools.Tool;
24+
import org.sonarsource.sonarqube.mcp.tools.ToolCategory;
2425

2526
public class ListEnterprisesTool extends Tool {
2627

@@ -30,7 +31,7 @@ public class ListEnterprisesTool extends Tool {
3031
private final ServerApiProvider serverApiProvider;
3132

3233
public ListEnterprisesTool(ServerApiProvider serverApiProvider) {
33-
super(createToolDefinition());
34+
super(createToolDefinition(), ToolCategory.PORTFOLIOS);
3435
this.serverApiProvider = serverApiProvider;
3536
}
3637

0 commit comments

Comments
 (0)