Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions src/cli/command-tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ integrateCommand
.description(
'Setup SonarQube integration for Claude Code. This will install secrets scanning hooks, configure SonarQube Agentic Analysis and MCP Server.',
)
.option('-p, --project <project>', 'Project key')
.option('-p, --project <project>', 'Project key. Ignored when --global is used.')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe to discuss with Clifford, but I know for some other commands, we fail if some options are incompatible. I think it's less confusing because as a user you don't know which option 'wins'

.option('--non-interactive', 'Non-interactive mode (no prompts)')
.option(
'-g, --global',
Expand Down Expand Up @@ -169,10 +169,14 @@ integrateCommand
.description(
'Setup SonarQube integration for Copilot. This will install secrets scanning hooks, configure SonarQube Agentic Analysis and MCP Server.',
)
.authenticatedAction((_auth, _options: IntegrateCopilotOptions): Promise<void> => {
integrateCopilot(_auth, _options);
return Promise.resolve();
});
.option(
'-g, --global',
'Install hooks and config globally to ~/.copilot instead of project directory',
)
.option('-p, --project <project>', 'Project key. Ignored when --global is used.')
.authenticatedAction((_auth, options: IntegrateCopilotOptions) =>
integrateCopilot(_auth, options),
);

// List Sonar resources
const list = COMMAND_TREE.command('list').description('List issues and projects from SonarQube');
Expand Down
52 changes: 52 additions & 0 deletions src/cli/commands/integrate/_common/mcp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* SonarQube CLI
* Copyright (C) SonarSource Sàrl
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

import { CLI_COMMAND } from '../../../../lib/config-constants';
import {
getMcpConfig,
getMcpConfigFilePath,
writeMcpServerEntry,
} from '../../../../lib/mcp/mcp-helper';
import { error, info, success } from '../../../../ui';

export async function setupMcpServerForAgent(
agent: 'claude' | 'copilot',
Comment thread
sophio-japharidze-sonarsource marked this conversation as resolved.
Outdated
projectRoot: string,
isGlobal: boolean,
projectKey: string | undefined,
): Promise<void> {
info(`Setting up SonarQube MCP Server for ${agent}...`);

const targetFile = getMcpConfigFilePath(agent, isGlobal, projectRoot);
const serverConfig = getMcpConfig(
CLI_COMMAND,
isGlobal ? { withFsMount: false } : { withFsMount: true, projectRoot, projectKey },
Comment thread
sonar-review-alpha[bot] marked this conversation as resolved.
Outdated
);

try {
await writeMcpServerEntry(targetFile, serverConfig);
} catch (e: unknown) {
if (e instanceof Error) {
error(`Failed to configure SonarQube MCP Server in ${targetFile}: ${e.message}`);
}
return;
}
success(`SonarQube MCP Server configured in ${targetFile}`);
}
9 changes: 7 additions & 2 deletions src/cli/commands/integrate/claude/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ import { SonarQubeClient } from '../../../../sonarqube/client';
import { blank, info, intro, note, outro, print, success, text, warn } from '../../../../ui';
import { CommandFailedError } from '../../_common/error';
import { installSecretsBinary } from '../../_common/install/secrets';
import { setupMcpServerForAgent } from '../_common/mcp';
import { runHealthChecks } from './health';
import { detectSecretsHook, installHooks } from './hooks';
import { setupMcpServer } from './mcp';
import { repairToken } from './repair';
import { updateStateAfterConfiguration } from './state';

Expand Down Expand Up @@ -140,7 +140,12 @@ export async function integrateClaude(
});
reportHookInstallationOutcome(isGlobal, globalSecretsHookPath);

await setupMcpServer('claude', project.rootDir, isGlobal, auth, project.projectKey);
await setupMcpServerForAgent(
'claude',
project.rootDir,
isGlobal,
options.project || project.projectKey,
);

blank();
text('Phase 3/3: Final Verification');
Expand Down
106 changes: 0 additions & 106 deletions src/cli/commands/integrate/claude/mcp.ts

This file was deleted.

20 changes: 16 additions & 4 deletions src/cli/commands/integrate/copilot/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import type { ResolvedAuth } from '../../../../lib/auth-resolver';
import { intro } from '../../../../ui';
import { discoverProject } from '../../../../lib/project-workspace';
import { intro, print } from '../../../../ui';
import { setupMcpServerForAgent } from '../_common/mcp';

/*
* SonarQube CLI
Expand All @@ -45,10 +47,20 @@ export interface IntegrateCopilotOptions {
global?: boolean;
}

export function integrateCopilot(_auth: ResolvedAuth, _options: IntegrateCopilotOptions) {
intro('SonarQube Copilot integration - coming soon');
export async function integrateCopilot(_auth: ResolvedAuth, options: IntegrateCopilotOptions) {
intro('SonarQube integration for Copilot');

const project = await discoverProject(process.cwd());
Comment thread
sonar-review-alpha[bot] marked this conversation as resolved.
for (const configSource of project.configSources) {
print(`Found ${configSource}`);
}

// TODO setup hooks

// TODO setup MCP Server
await setupMcpServerForAgent(
'copilot',
project.rootDir,
options.global ?? false,
options.project || project.projectKey,
);
}
10 changes: 5 additions & 5 deletions src/cli/commands/run/mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { homedir } from 'node:os';

import type { ResolvedAuth } from '../../../lib/auth-resolver.js';
import { canonicalizePath } from '../../../lib/fs-utils.js';
import { getMcpServerConfig, type McpServerContext } from '../../../lib/mcp/server-config.js';
import { getMcpContainerCommand, type McpServerContext } from '../../../lib/mcp/mcp-helper.js';
import { discoverProject } from '../../../lib/project-workspace/project-info.js';
import { detectContainerRuntime } from '../../../lib/tool-detector.js';
import { CommandFailedError } from '../_common/error.js';
Expand All @@ -48,16 +48,16 @@ export async function runMcp(auth: ResolvedAuth, options: McpRunOptions = {}): P
const cwd = process.cwd();
const cwdIsHomeDir = canonicalizePath(cwd) === canonicalizePath(homedir());
const discovered = cwdIsHomeDir ? undefined : await discoverProject(cwd);
const projectKey = options.project ?? discovered?.projectKey;
const projectKey = options.project || discovered?.projectKey;
const discoveredRootIsHomeDir =
discovered && canonicalizePath(discovered.rootDir) === canonicalizePath(homedir());
const projectRoot = discoveredRootIsHomeDir ? undefined : discovered?.rootDir;

const context: McpServerContext = projectRoot
? { withFsMount: true, projectRoot, discoveredProjectKey: projectKey }
: { withFsMount: false, discoveredProjectKey: projectKey };
? { withFsMount: true, projectRoot, projectKey }
: { withFsMount: false, projectKey };

const config = getMcpServerConfig(auth, runtime, context, options);
const config = getMcpContainerCommand(auth, runtime, context, options);

await new Promise<void>((resolve, reject) => {
const child = spawn(config.command, config.args, {
Expand Down
3 changes: 3 additions & 0 deletions src/lib/config-constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ import { join } from 'node:path';

export const APP_NAME = 'sonarqube-cli';

/** The CLI command name as it appears on PATH after installation. */
export const CLI_COMMAND = process.platform === 'win32' ? 'sonar.exe' : 'sonar';

// ---------------------------------------------------------------------------
// CLI data directory
// ---------------------------------------------------------------------------
Expand Down
Loading
Loading