Skip to content

CLI-321 introduce 'run mcp' command#223

Open
sophio-japharidze-sonarsource wants to merge 2 commits intomasterfrom
CLI-321-mcp-start-command
Open

CLI-321 introduce 'run mcp' command#223
sophio-japharidze-sonarsource wants to merge 2 commits intomasterfrom
CLI-321-mcp-start-command

Conversation

@sophio-japharidze-sonarsource
Copy link
Copy Markdown
Contributor

No description provided.

@hashicorp-vault-sonar-prod
Copy link
Copy Markdown

hashicorp-vault-sonar-prod Bot commented Apr 28, 2026

CLI-321

@sonar-review-alpha
Copy link
Copy Markdown
Contributor

sonar-review-alpha Bot commented Apr 28, 2026

Summary

This PR introduces a new hidden run mcp command that spawns a SonarQube MCP (Model Context Protocol) server in a Docker container, enabling integration with AI agents like Claude Code.

What changed:

  • Added sonar run mcp command with options for debug mode, read-only operation, toolset selection, and explicit project key
  • Implemented MCP server configuration builder that constructs Docker container invocations with appropriate environment variables and volume mounts
  • Integrated MCP setup into the integrate claude flow to auto-configure Claude's MCP client files
  • Auto-discovers project key and workspace directory when available (skips if running from home directory)
  • Validates container runtime availability (Docker/Podman/Nerdctl)

Why:
This enables the CLI to be used as a managed MCP server for AI agents, providing programmatic access to SonarQube analysis capabilities via the Model Context Protocol standard. The container-based approach ensures environment isolation and dependency management.

What reviewers should know

Start here: Review src/cli/commands/run/mcp.ts for the main command implementation, then src/lib/mcp/server-config.ts for how Docker invocation is built.

Key design decisions:

  • The command is hidden (not user-facing) — it's designed for agent/programmatic use
  • Uses stdio inheritance for MCP protocol transport (bidirectional I/O)
  • Read-only filesystem mount (/app/mcp-workspace:ro) prevents container from modifying host files
  • Project discovery is automatic but skipped if running from home directory (prevents unintended scope)
  • Environment variables pass authentication and options to the container

Integration points:

  • Command registration in command-tree.ts (lines ~258-279)
  • Claude integration writes MCP config to ~/.anthropic/claude.json via setupMcpServer() in src/cli/commands/integrate/claude/mcp.ts
  • Auth and project discovery leverage existing CLI infrastructure

Test coverage:

  • Unit tests for server config generation and MCP setup
  • Integration test for the full run mcp flow
  • Tests for Claude integration configuration writing

Watch for:

  • Container runtime detection is platform-dependent; review tool-detector.ts changes if testing on non-Linux
  • Home directory detection uses path canonicalization to avoid symlink issues
  • Project mounting only happens for discovered/specified projects outside home directory

  • Generate Walkthrough
  • Generate Diagram

🗣️ Give feedback

sonar-review-alpha[bot]

This comment was marked as resolved.

};

if (projectInfo.hasSonarProps && projectInfo.sonarPropsData) {
print('Found sonar-project.properties');
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This change is needed so that project discovery does not pollute stdio for mcp

sonar-review-alpha[bot]

This comment was marked as resolved.

Copy link
Copy Markdown
Member

@nquinquenel nquinquenel left a comment

Choose a reason for hiding this comment

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

Looks good overall, few comments

Comment on lines +70 to +72
for (const configSource of project.configSources) {
print(`Found ${configSource}`);
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this wanted in scope of this PR? Was it for debug only?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No, it's needed. Because before this print line was inside discoverProject method that is now also called from runMcp. And any print lines would interfere with the stdio.

Additionally, these prints do not belong in the discoverProject as it is an utility method that should not affect the CLI UX.

child.on('error', reject);
child.on('exit', (code) => {
process.exitCode = code ?? 1;
resolve();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think it should not resolve() for non 0 exit code, maybe reject with a CommandFailedError message?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Rejecting here would add another redundant error message to stdout (through generic CommandFailedError handler). When container exists with non-0 exit code, this command will exit too, with the same exit code as the continer.

Comment on lines +65 to +68
if (auth.connectionType === 'cloud') {
args.push('-e', 'SONARQUBE_ORG');
env.SONARQUBE_ORG = org ?? '';
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If we want to optimize, SONARQUBE_URL is not needed in scope of SQC (it defaults to sonarcloud.io)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think it's ok to keep it because like this we will always explicitly set the URL and will not need extra checks saying something like isCloud && region==US, etc.


if (context.withFsMount) {
const hostPath = normalizePath(context.projectRoot);
args.push('-v', `${hostPath}:/app/mcp-workspace:ro`);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I know it's not ideal but CAG requires to have :rw access (write included). Worth checking what we want to do, it's maybe not a good practice to put a write access via a wrapper command that is doing opaque things.


if (context.withFsMount) {
const hostPath = normalizePath(context.projectRoot);
args.push('-v', `${hostPath}:/app/mcp-workspace:ro`);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I know it's not ideal but CAG requires to have :rw access (write included). Worth checking what we want to do, it's maybe not a good practice to put a write access via a wrapper command that is doing opaque things.


if (auth.connectionType === 'cloud') {
args.push('-e', 'SONARQUBE_ORG');
env.SONARQUBE_ORG = org ?? '';
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think it should probably fail if we don't have an org defined. Currently, we won't be able to start the server properly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I have changed auth-resolver to NOT resolve auth if connection type is cloud and org key is not defined.

* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

// Integration tests for `sonar run mcp`
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we test read only/toolsets options here?

sonar-review-alpha[bot]

This comment was marked as outdated.

@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown
Contributor

@sonar-review-alpha sonar-review-alpha Bot left a comment

Choose a reason for hiding this comment

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

Solid implementation. The key architectural moves — extracting getMcpServerConfig into a shared lib, and refactoring discoverProject to return configSources instead of printing directly — are clean and well-motivated. The dedicated integration test asserting stdout cleanliness for MCP JSON-RPC transport is exactly the right thing to test. One uncovered path in auth-resolver.ts is worth a test before this is in production.

🗣️ Give feedback

Comment thread src/lib/auth-resolver.ts
Comment on lines +132 to +134
if (connectionType === 'cloud' && orgKey === undefined) {
return null;
}
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.

Missing test coverage: No test exercises the path where a saved connection has type: 'cloud' but orgKey is undefined, which makes resolveFromState return null. All existing auth-resolver tests supply orgKey: 'my-org'.

This guard prevents a malformed cloud state entry from producing auth — a regression here would silently allow the downstream code to pass an empty SONARQUBE_ORG env var to the container. A unit test against resolveFromState with a state fixture of { type: 'cloud', serverUrl: '...', /* no orgKey */ } would cover it.

  • Mark as noise

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants