4444import org .sonarsource .sonarqube .mcp .serverapi .ServerApi ;
4545import org .sonarsource .sonarqube .mcp .serverapi .ServerApiHelper ;
4646import org .sonarsource .sonarqube .mcp .serverapi .ServerApiProvider ;
47+ import org .sonarsource .sonarqube .mcp .serverapi .features .Feature ;
4748import org .sonarsource .sonarqube .mcp .slcore .BackendService ;
4849import org .sonarsource .sonarqube .mcp .tools .Tool ;
4950import org .sonarsource .sonarqube .mcp .tools .ToolExecutor ;
@@ -129,7 +130,9 @@ public SonarQubeMcpServer(Map<String, String> environment) {
129130 this .transportProvider = new StdioServerTransportProvider (new ObjectMapper ());
130131 }
131132
132- initializeBasicServices ();
133+ sonarQubeVersionChecker .failIfSonarQubeServerVersionIsNotSupported ();
134+
135+ initializeBasicServicesAndTools ();
133136 }
134137
135138 public void start () {
@@ -138,7 +141,6 @@ public void start() {
138141 httpServerManager .startServer ().join ();
139142 }
140143
141- // Build and start MCP server immediately with NO tools, they will be added dynamically once background initialization completes
142144 Function <Object , McpSyncServer > serverBuilder = provider -> {
143145 var builder = switch (provider ) {
144146 case McpServerTransportProvider p -> McpServer .sync (p );
@@ -151,7 +153,7 @@ public void start() {
151153 "Analyze code, monitor project health, investigate issues, and understand quality gates. " +
152154 "Note: Tools are being loaded in the background and will be available shortly." )
153155 .capabilities (McpSchema .ServerCapabilities .builder ().tools (true ).logging ().build ())
154- // Start with no tools - they will be added dynamically after initialization
156+ . tools ( filterForEnabledTools ( supportedTools ). stream (). map ( this :: toSpec ). toArray ( McpServerFeatures . SyncToolSpecification []:: new ))
155157 .build ();
156158 };
157159
@@ -168,10 +170,9 @@ public void start() {
168170 }
169171
170172 /**
171- * Quick initialization - only creates basic services needed for configuration validation.
172- * Heavy operations (version check, plugin download, backend init) are deferred to background.
173+ * Quick operations only - heavy operations (plugin download, backend init) are deferred to background.
173174 */
174- private void initializeBasicServices () {
175+ private void initializeBasicServicesAndTools () {
175176 this .backendService = new BackendService (mcpConfiguration );
176177 this .httpClientProvider = new HttpClientProvider (mcpConfiguration .getUserAgent ());
177178 this .toolExecutor = new ToolExecutor (backendService , initializationFuture );
@@ -181,9 +182,11 @@ private void initializeBasicServices() {
181182 if (mcpConfiguration .isHttpEnabled ()) {
182183 var initServerApi = createServerApiWithToken (mcpConfiguration .getSonarQubeToken ());
183184 this .sonarQubeVersionChecker = new SonarQubeVersionChecker (initServerApi );
185+ loadBackendIndependentTools (initServerApi );
184186 } else {
185187 this .serverApi = initializeServerApi (mcpConfiguration );
186188 this .sonarQubeVersionChecker = new SonarQubeVersionChecker (serverApi );
189+ loadBackendIndependentTools (serverApi );
187190 }
188191 }
189192
@@ -192,10 +195,6 @@ private void initializeBasicServices() {
192195 */
193196 private void initializeBackgroundServices () {
194197 try {
195- sonarQubeVersionChecker .failIfSonarQubeServerVersionIsNotSupported ();
196-
197- loadBackendIndependentTools ();
198-
199198 PluginsSynchronizer pluginsSynchronizer ;
200199 if (mcpConfiguration .isHttpEnabled ()) {
201200 var initServerApi = createServerApiWithToken (mcpConfiguration .getSonarQubeToken ());
@@ -229,20 +228,19 @@ private void initializeBackgroundServices() {
229228 * These can be loaded BEFORE plugin synchronization (which is slow).
230229 * This makes most tools available to users within seconds instead of minutes.
231230 */
232- private void loadBackendIndependentTools () {
233- var independentTools = new ArrayList <Tool >();
231+ private void loadBackendIndependentTools (ServerApi serverApi ) {
234232 if (mcpConfiguration .isSonarCloud ()) {
235- independentTools .add (new ListEnterprisesTool (this ));
233+ supportedTools .add (new ListEnterprisesTool (this ));
236234 } else {
237- independentTools .addAll (List .of (
235+ supportedTools .addAll (List .of (
238236 new SystemHealthTool (this ),
239237 new SystemInfoTool (this ),
240238 new SystemLogsTool (this ),
241239 new SystemPingTool (this ),
242240 new SystemStatusTool (this )));
243241 }
244242
245- independentTools .addAll (List .of (
243+ supportedTools .addAll (List .of (
246244 new ChangeIssueStatusTool (this ),
247245 new SearchMyProjectsTool (this ),
248246 new SearchIssuesTool (this ),
@@ -259,7 +257,11 @@ private void loadBackendIndependentTools() {
259257 new ListWebhooksTool (this ),
260258 new ListPortfoliosTool (this , mcpConfiguration .isSonarCloud ())));
261259
262- registerAndNotifyBatch (independentTools );
260+ var scaSupportedOnSQC = serverApi .isSonarQubeCloud () && serverApi .scaApi ().isScaEnabled ();
261+ var scaSupportedOnSQS = !serverApi .isSonarQubeCloud () && serverApi .featuresApi ().listFeatures ().contains (Feature .SCA );
262+ if (scaSupportedOnSQC || scaSupportedOnSQS ) {
263+ supportedTools .add (new SearchDependencyRisksTool (this , sonarQubeVersionChecker ));
264+ }
263265 }
264266
265267 /**
@@ -284,22 +286,24 @@ private void loadBackendDependentTools() {
284286 LOG .info ("Standard analysis mode (no IDE bridge)" );
285287 dependentTools .add (new AnalysisTool (backendService , this ));
286288 }
287- dependentTools .add (new SearchDependencyRisksTool (this , sonarQubeVersionChecker ));
288289
289290 registerAndNotifyBatch (dependentTools );
290291 var filterReason = mcpConfiguration .isReadOnlyMode () ? "category and read-only filtering" : "category filtering" ;
291292 LOG .info ("All tools loaded: " + this .supportedTools .size () + " tools after " + filterReason );
292293 }
293294
295+ private List <Tool > filterForEnabledTools (List <Tool > toolsToFilter ) {
296+ return toolsToFilter .stream ()
297+ .filter (tool -> mcpConfiguration .isToolCategoryEnabled (tool .getCategory ()))
298+ .filter (tool -> !mcpConfiguration .isReadOnlyMode () || tool .definition ().annotations ().readOnlyHint ())
299+ .toList ();
300+ }
301+
294302 /**
295303 * Registers a batch of tools after filtering based on configuration.
296- * Tools are filtered by category and read-only mode before being registered.
297304 */
298305 private void registerAndNotifyBatch (List <Tool > tools ) {
299- var filteredTools = tools .stream ()
300- .filter (tool -> mcpConfiguration .isToolCategoryEnabled (tool .getCategory ()))
301- .filter (tool -> !mcpConfiguration .isReadOnlyMode () || tool .definition ().annotations ().readOnlyHint ())
302- .toList ();
306+ var filteredTools = filterForEnabledTools (tools );
303307
304308 this .supportedTools .addAll (filteredTools );
305309
@@ -438,7 +442,7 @@ public SonarQubeMcpServer(McpServerTransportProviderBase transportProvider, @Nul
438442 this .mcpConfiguration = new McpServerLaunchConfiguration (environment );
439443 this .transportProvider = transportProvider ;
440444 this .httpServerManager = httpServerManager ;
441- initializeBasicServices ();
445+ initializeBasicServicesAndTools ();
442446 }
443447
444448 // Package-private getters for testing
0 commit comments