3232import java .util .Objects ;
3333import java .util .concurrent .CompletableFuture ;
3434import java .util .concurrent .ExecutionException ;
35+ import java .util .concurrent .TimeoutException ;
3536import java .util .function .Function ;
3637import javax .annotation .Nullable ;
3738import org .sonarsource .sonarqube .mcp .bridge .SonarQubeIdeBridgeClient ;
4445import org .sonarsource .sonarqube .mcp .serverapi .ServerApi ;
4546import org .sonarsource .sonarqube .mcp .serverapi .ServerApiHelper ;
4647import org .sonarsource .sonarqube .mcp .serverapi .ServerApiProvider ;
48+ import org .sonarsource .sonarqube .mcp .serverapi .features .Feature ;
4749import org .sonarsource .sonarqube .mcp .slcore .BackendService ;
4850import org .sonarsource .sonarqube .mcp .tools .Tool ;
4951import org .sonarsource .sonarqube .mcp .tools .ToolExecutor ;
@@ -129,7 +131,7 @@ public SonarQubeMcpServer(Map<String, String> environment) {
129131 this .transportProvider = new StdioServerTransportProvider (new ObjectMapper (), this ::shutdown );
130132 }
131133
132- initializeBasicServices ();
134+ initializeBasicServicesAndTools ();
133135 }
134136
135137 public void start () {
@@ -138,7 +140,6 @@ public void start() {
138140 httpServerManager .startServer ().join ();
139141 }
140142
141- // Build and start MCP server immediately with NO tools, they will be added dynamically once background initialization completes
142143 Function <Object , McpSyncServer > serverBuilder = provider -> {
143144 var builder = switch (provider ) {
144145 case McpServerTransportProvider p -> McpServer .sync (p );
@@ -151,7 +152,7 @@ public void start() {
151152 "Analyze code, monitor project health, investigate issues, and understand quality gates. " +
152153 "Note: Tools are being loaded in the background and will be available shortly." )
153154 .capabilities (McpSchema .ServerCapabilities .builder ().tools (true ).logging ().build ())
154- // Start with no tools - they will be added dynamically after initialization
155+ . tools ( filterForEnabledTools ( supportedTools ). stream (). map ( this :: toSpec ). toArray ( McpServerFeatures . SyncToolSpecification []:: new ))
155156 .build ();
156157 };
157158
@@ -168,10 +169,9 @@ public void start() {
168169 }
169170
170171 /**
171- * Quick initialization - only creates basic services needed for configuration validation.
172- * Heavy operations (version check, plugin download, backend init) are deferred to background.
172+ * Quick operations only - heavy operations (plugin download, backend init) are deferred to background.
173173 */
174- private void initializeBasicServices () {
174+ private void initializeBasicServicesAndTools () {
175175 this .backendService = new BackendService (mcpConfiguration );
176176 this .httpClientProvider = new HttpClientProvider (mcpConfiguration .getUserAgent ());
177177 this .toolExecutor = new ToolExecutor (backendService , initializationFuture );
@@ -181,21 +181,21 @@ private void initializeBasicServices() {
181181 if (mcpConfiguration .isHttpEnabled ()) {
182182 var initServerApi = createServerApiWithToken (mcpConfiguration .getSonarQubeToken ());
183183 this .sonarQubeVersionChecker = new SonarQubeVersionChecker (initServerApi );
184+ loadBackendIndependentTools (initServerApi );
184185 } else {
185186 this .serverApi = initializeServerApi (mcpConfiguration );
186187 this .sonarQubeVersionChecker = new SonarQubeVersionChecker (serverApi );
188+ loadBackendIndependentTools (serverApi );
187189 }
190+
191+ sonarQubeVersionChecker .failIfSonarQubeServerVersionIsNotSupported ();
188192 }
189193
190194 /**
191195 * Heavy initialization that runs in background after the server has started.
192196 */
193197 private void initializeBackgroundServices () {
194198 try {
195- sonarQubeVersionChecker .failIfSonarQubeServerVersionIsNotSupported ();
196-
197- loadBackendIndependentTools ();
198-
199199 PluginsSynchronizer pluginsSynchronizer ;
200200 if (mcpConfiguration .isHttpEnabled ()) {
201201 var initServerApi = createServerApiWithToken (mcpConfiguration .getSonarQubeToken ());
@@ -229,20 +229,19 @@ private void initializeBackgroundServices() {
229229 * These can be loaded BEFORE plugin synchronization (which is slow).
230230 * This makes most tools available to users within seconds instead of minutes.
231231 */
232- private void loadBackendIndependentTools () {
233- var independentTools = new ArrayList <Tool >();
232+ private void loadBackendIndependentTools (ServerApi serverApi ) {
234233 if (mcpConfiguration .isSonarCloud ()) {
235- independentTools .add (new ListEnterprisesTool (this ));
234+ supportedTools .add (new ListEnterprisesTool (this ));
236235 } else {
237- independentTools .addAll (List .of (
236+ supportedTools .addAll (List .of (
238237 new SystemHealthTool (this ),
239238 new SystemInfoTool (this ),
240239 new SystemLogsTool (this ),
241240 new SystemPingTool (this ),
242241 new SystemStatusTool (this )));
243242 }
244243
245- independentTools .addAll (List .of (
244+ supportedTools .addAll (List .of (
246245 new ChangeIssueStatusTool (this ),
247246 new SearchMyProjectsTool (this ),
248247 new SearchIssuesTool (this ),
@@ -259,7 +258,11 @@ private void loadBackendIndependentTools() {
259258 new ListWebhooksTool (this ),
260259 new ListPortfoliosTool (this , mcpConfiguration .isSonarCloud ())));
261260
262- registerAndNotifyBatch (independentTools );
261+ var scaSupportedOnSQC = serverApi .isSonarQubeCloud () && serverApi .scaApi ().isScaEnabled ();
262+ var scaSupportedOnSQS = !serverApi .isSonarQubeCloud () && serverApi .featuresApi ().listFeatures ().contains (Feature .SCA );
263+ if (scaSupportedOnSQC || scaSupportedOnSQS ) {
264+ supportedTools .add (new SearchDependencyRisksTool (this , sonarQubeVersionChecker ));
265+ }
263266 }
264267
265268 /**
@@ -284,22 +287,24 @@ private void loadBackendDependentTools() {
284287 LOG .info ("Standard analysis mode (no IDE bridge)" );
285288 dependentTools .add (new AnalysisTool (backendService , this ));
286289 }
287- dependentTools .add (new SearchDependencyRisksTool (this , sonarQubeVersionChecker ));
288290
289291 registerAndNotifyBatch (dependentTools );
290292 var filterReason = mcpConfiguration .isReadOnlyMode () ? "category and read-only filtering" : "category filtering" ;
291293 LOG .info ("All tools loaded: " + this .supportedTools .size () + " tools after " + filterReason );
292294 }
293295
296+ private List <Tool > filterForEnabledTools (List <Tool > toolsToFilter ) {
297+ return toolsToFilter .stream ()
298+ .filter (tool -> mcpConfiguration .isToolCategoryEnabled (tool .getCategory ()))
299+ .filter (tool -> !mcpConfiguration .isReadOnlyMode () || tool .definition ().annotations ().readOnlyHint ())
300+ .toList ();
301+ }
302+
294303 /**
295304 * Registers a batch of tools after filtering based on configuration.
296- * Tools are filtered by category and read-only mode before being registered.
297305 */
298306 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 ();
307+ var filteredTools = filterForEnabledTools (tools );
303308
304309 this .supportedTools .addAll (filteredTools );
305310
@@ -402,6 +407,19 @@ public void shutdown() {
402407 }
403408 isShutdown = true ;
404409
410+ // Wait for background initialization to complete or cancel it
411+ if (!initializationFuture .isDone ()) {
412+ LOG .info ("Waiting for background initialization to complete before shutdown..." );
413+ try {
414+ initializationFuture .get (30 , java .util .concurrent .TimeUnit .SECONDS );
415+ } catch (TimeoutException | ExecutionException e ) {
416+ LOG .warn ("Background initialization did not complete within 30 seconds, proceeding with shutdown" );
417+ initializationFuture .cancel (true );
418+ } catch (Exception e ) {
419+ LOG .error ("Background initialization failed or was interrupted" , e );
420+ }
421+ }
422+
405423 // Stop HTTP server if running
406424 if (httpServerManager != null ) {
407425 try {
@@ -414,7 +432,9 @@ public void shutdown() {
414432 }
415433
416434 try {
417- httpClientProvider .shutdown ();
435+ if (httpClientProvider != null ) {
436+ httpClientProvider .shutdown ();
437+ }
418438 } catch (Exception e ) {
419439 LOG .error ("Error shutting down HTTP client" , e );
420440 }
@@ -438,7 +458,7 @@ public SonarQubeMcpServer(McpServerTransportProviderBase transportProvider, @Nul
438458 this .mcpConfiguration = new McpServerLaunchConfiguration (environment );
439459 this .transportProvider = transportProvider ;
440460 this .httpServerManager = httpServerManager ;
441- initializeBasicServices ();
461+ initializeBasicServicesAndTools ();
442462 }
443463
444464 // Package-private getters for testing
0 commit comments