#
tokens: 49395/50000 52/236 files (page 2/11)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 2 of 11. Use http://codebase.md/sapientpants/sonarqube-mcp-server?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .adr-dir
├── .changeset
│   ├── config.json
│   └── README.md
├── .claude
│   ├── commands
│   │   ├── analyze-and-fix-github-issue.md
│   │   ├── fix-sonarqube-issues.md
│   │   ├── implement-github-issue.md
│   │   ├── release.md
│   │   ├── spec-feature.md
│   │   └── update-dependencies.md
│   ├── hooks
│   │   └── block-git-no-verify.ts
│   └── settings.json
├── .dockerignore
├── .github
│   ├── actionlint.yaml
│   ├── changeset.yml
│   ├── dependabot.yml
│   ├── ISSUE_TEMPLATE
│   │   ├── bug_report.md
│   │   └── feature_request.md
│   ├── pull_request_template.md
│   ├── scripts
│   │   ├── determine-artifact.sh
│   │   └── version-and-release.js
│   ├── workflows
│   │   ├── codeql.yml
│   │   ├── main.yml
│   │   ├── pr.yml
│   │   ├── publish.yml
│   │   ├── reusable-docker.yml
│   │   ├── reusable-security.yml
│   │   └── reusable-validate.yml
│   └── WORKFLOWS.md
├── .gitignore
├── .husky
│   ├── commit-msg
│   └── pre-commit
├── .markdownlint.yaml
├── .markdownlintignore
├── .npmrc
├── .prettierignore
├── .prettierrc
├── .trivyignore
├── .yaml-lint.yml
├── .yamllintignore
├── CHANGELOG.md
├── changes.md
├── CLAUDE.md
├── CODE_OF_CONDUCT.md
├── commitlint.config.js
├── COMPATIBILITY.md
├── CONTRIBUTING.md
├── docker-compose.yml
├── Dockerfile
├── docs
│   ├── architecture
│   │   └── decisions
│   │       ├── 0001-record-architecture-decisions.md
│   │       ├── 0002-use-node-js-with-typescript.md
│   │       ├── 0003-adopt-model-context-protocol-for-sonarqube-integration.md
│   │       ├── 0004-use-sonarqube-web-api-client-for-all-sonarqube-interactions.md
│   │       ├── 0005-domain-driven-design-of-sonarqube-modules.md
│   │       ├── 0006-expose-sonarqube-features-as-mcp-tools.md
│   │       ├── 0007-support-multiple-authentication-methods-for-sonarqube.md
│   │       ├── 0008-use-environment-variables-for-configuration.md
│   │       ├── 0009-file-based-logging-to-avoid-stdio-conflicts.md
│   │       ├── 0010-use-stdio-transport-for-mcp-communication.md
│   │       ├── 0011-docker-containerization-for-deployment.md
│   │       ├── 0012-add-elicitation-support-for-interactive-user-input.md
│   │       ├── 0014-current-security-model-and-future-oauth2-considerations.md
│   │       ├── 0015-transport-architecture-refactoring.md
│   │       ├── 0016-http-transport-with-oauth-2-0-metadata-endpoints.md
│   │       ├── 0017-comprehensive-audit-logging-system.md
│   │       ├── 0018-add-comprehensive-monitoring-and-observability.md
│   │       ├── 0019-simplify-to-stdio-only-transport-for-mcp-gateway-deployment.md
│   │       ├── 0020-testing-framework-and-strategy-vitest-with-property-based-testing.md
│   │       ├── 0021-code-quality-toolchain-eslint-prettier-strict-typescript.md
│   │       ├── 0022-package-manager-choice-pnpm.md
│   │       ├── 0023-release-management-with-changesets.md
│   │       ├── 0024-ci-cd-platform-github-actions.md
│   │       ├── 0025-container-and-security-scanning-strategy.md
│   │       ├── 0026-circuit-breaker-pattern-with-opossum.md
│   │       ├── 0027-docker-image-publishing-strategy-ghcr-to-docker-hub.md
│   │       └── 0028-session-based-http-transport-with-server-sent-events.md
│   ├── architecture.md
│   ├── security.md
│   └── troubleshooting.md
├── eslint.config.js
├── examples
│   └── http-client.ts
├── jest.config.js
├── LICENSE
├── LICENSES.md
├── osv-scanner.toml
├── package.json
├── pnpm-lock.yaml
├── README.md
├── scripts
│   ├── actionlint.sh
│   ├── ci-local.sh
│   ├── load-test.sh
│   ├── README.md
│   ├── run-all-tests.sh
│   ├── scan-container.sh
│   ├── security-scan.sh
│   ├── setup.sh
│   ├── test-monitoring-integration.sh
│   └── validate-docs.sh
├── SECURITY.md
├── sonar-project.properties
├── src
│   ├── __tests__
│   │   ├── additional-coverage.test.ts
│   │   ├── advanced-index.test.ts
│   │   ├── assign-issue.test.ts
│   │   ├── auth-methods.test.ts
│   │   ├── boolean-string-transform.test.ts
│   │   ├── components.test.ts
│   │   ├── config
│   │   │   └── service-accounts.test.ts
│   │   ├── dependency-injection.test.ts
│   │   ├── direct-handlers.test.ts
│   │   ├── direct-lambdas.test.ts
│   │   ├── direct-schema-validation.test.ts
│   │   ├── domains
│   │   │   ├── components-domain-full.test.ts
│   │   │   ├── components-domain.test.ts
│   │   │   ├── hotspots-domain.test.ts
│   │   │   └── source-code-domain.test.ts
│   │   ├── environment-validation.test.ts
│   │   ├── error-handler.test.ts
│   │   ├── error-handling.test.ts
│   │   ├── errors.test.ts
│   │   ├── function-tests.test.ts
│   │   ├── handlers
│   │   │   ├── components-handler-integration.test.ts
│   │   │   └── projects-authorization.test.ts
│   │   ├── handlers.test.ts
│   │   ├── handlers.test.ts.skip
│   │   ├── index.test.ts
│   │   ├── issue-resolution-elicitation.test.ts
│   │   ├── issue-resolution.test.ts
│   │   ├── issue-transitions.test.ts
│   │   ├── issues-enhanced-search.test.ts
│   │   ├── issues-new-parameters.test.ts
│   │   ├── json-array-transform.test.ts
│   │   ├── lambda-functions.test.ts
│   │   ├── lambda-handlers.test.ts.skip
│   │   ├── logger.test.ts
│   │   ├── mapping-functions.test.ts
│   │   ├── mocked-environment.test.ts
│   │   ├── null-to-undefined.test.ts
│   │   ├── parameter-transformations-advanced.test.ts
│   │   ├── parameter-transformations.test.ts
│   │   ├── protocol-version.test.ts
│   │   ├── pull-request-transform.test.ts
│   │   ├── quality-gates.test.ts
│   │   ├── schema-parameter-transforms.test.ts
│   │   ├── schema-transformation-mocks.test.ts
│   │   ├── schema-transforms.test.ts
│   │   ├── schema-validators.test.ts
│   │   ├── schemas
│   │   │   ├── components-schema.test.ts
│   │   │   ├── hotspots-tools-schema.test.ts
│   │   │   └── issues-schema.test.ts
│   │   ├── sonarqube-elicitation.test.ts
│   │   ├── sonarqube.test.ts
│   │   ├── source-code.test.ts
│   │   ├── standalone-handlers.test.ts
│   │   ├── string-to-number-transform.test.ts
│   │   ├── tool-handler-lambdas.test.ts
│   │   ├── tool-handlers.test.ts
│   │   ├── tool-registration-schema.test.ts
│   │   ├── tool-registration-transforms.test.ts
│   │   ├── transformation-util.test.ts
│   │   ├── transports
│   │   │   ├── base.test.ts
│   │   │   ├── factory.test.ts
│   │   │   ├── http.test.ts
│   │   │   ├── session-manager.test.ts
│   │   │   └── stdio.test.ts
│   │   ├── utils
│   │   │   ├── retry.test.ts
│   │   │   └── transforms.test.ts
│   │   ├── zod-boolean-transform.test.ts
│   │   ├── zod-schema-transforms.test.ts
│   │   └── zod-transforms.test.ts
│   ├── config
│   │   ├── service-accounts.ts
│   │   └── versions.ts
│   ├── domains
│   │   ├── base.ts
│   │   ├── components.ts
│   │   ├── hotspots.ts
│   │   ├── index.ts
│   │   ├── issues.ts
│   │   ├── measures.ts
│   │   ├── metrics.ts
│   │   ├── projects.ts
│   │   ├── quality-gates.ts
│   │   ├── source-code.ts
│   │   └── system.ts
│   ├── errors.ts
│   ├── handlers
│   │   ├── components.ts
│   │   ├── hotspots.ts
│   │   ├── index.ts
│   │   ├── issues.ts
│   │   ├── measures.ts
│   │   ├── metrics.ts
│   │   ├── projects.ts
│   │   ├── quality-gates.ts
│   │   ├── source-code.ts
│   │   └── system.ts
│   ├── index.ts
│   ├── monitoring
│   │   ├── __tests__
│   │   │   └── circuit-breaker.test.ts
│   │   ├── circuit-breaker.ts
│   │   ├── health.ts
│   │   └── metrics.ts
│   ├── schemas
│   │   ├── common.ts
│   │   ├── components.ts
│   │   ├── hotspots-tools.ts
│   │   ├── hotspots.ts
│   │   ├── index.ts
│   │   ├── issues.ts
│   │   ├── measures.ts
│   │   ├── metrics.ts
│   │   ├── projects.ts
│   │   ├── quality-gates.ts
│   │   ├── source-code.ts
│   │   └── system.ts
│   ├── sonarqube.ts
│   ├── transports
│   │   ├── base.ts
│   │   ├── factory.ts
│   │   ├── http.ts
│   │   ├── index.ts
│   │   ├── session-manager.ts
│   │   └── stdio.ts
│   ├── types
│   │   ├── common.ts
│   │   ├── components.ts
│   │   ├── hotspots.ts
│   │   ├── index.ts
│   │   ├── issues.ts
│   │   ├── measures.ts
│   │   ├── metrics.ts
│   │   ├── projects.ts
│   │   ├── quality-gates.ts
│   │   ├── source-code.ts
│   │   └── system.ts
│   └── utils
│       ├── __tests__
│       │   ├── elicitation.test.ts
│       │   ├── pattern-matcher.test.ts
│       │   └── structured-response.test.ts
│       ├── client-factory.ts
│       ├── elicitation.ts
│       ├── error-handler.ts
│       ├── logger.ts
│       ├── parameter-mappers.ts
│       ├── pattern-matcher.ts
│       ├── retry.ts
│       ├── structured-response.ts
│       └── transforms.ts
├── test-http-transport.sh
├── tmp
│   └── .gitkeep
├── tsconfig.build.json
├── tsconfig.json
├── vitest.config.d.ts
├── vitest.config.js
├── vitest.config.js.map
└── vitest.config.ts
```

# Files

--------------------------------------------------------------------------------
/src/handlers/measures.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import type {
 2 |   ComponentMeasuresParams,
 3 |   ComponentsMeasuresParams,
 4 |   MeasuresHistoryParams,
 5 |   ISonarQubeClient,
 6 | } from '../types/index.js';
 7 | import { getDefaultClient } from '../utils/client-factory.js';
 8 | import { createStructuredResponse } from '../utils/structured-response.js';
 9 | 
10 | /**
11 |  * Handler for getting measures for a specific component
12 |  * @param params Parameters for the component measures request
13 |  * @param client Optional SonarQube client instance
14 |  * @returns Promise with the component measures result
15 |  */
16 | export async function handleSonarQubeComponentMeasures(
17 |   params: ComponentMeasuresParams,
18 |   client: ISonarQubeClient = getDefaultClient()
19 | ) {
20 |   const result = await client.getComponentMeasures(params);
21 | 
22 |   return createStructuredResponse(result);
23 | }
24 | 
25 | /**
26 |  * Handler for getting measures for multiple components
27 |  * @param params Parameters for the components measures request
28 |  * @param client Optional SonarQube client instance
29 |  * @returns Promise with the components measures result
30 |  */
31 | export async function handleSonarQubeComponentsMeasures(
32 |   params: ComponentsMeasuresParams,
33 |   client: ISonarQubeClient = getDefaultClient()
34 | ) {
35 |   const result = await client.getComponentsMeasures(params);
36 | 
37 |   return createStructuredResponse(result);
38 | }
39 | 
40 | /**
41 |  * Handler for getting measures history
42 |  * @param params Parameters for the measures history request
43 |  * @param client Optional SonarQube client instance
44 |  * @returns Promise with the measures history result
45 |  */
46 | export async function handleSonarQubeMeasuresHistory(
47 |   params: MeasuresHistoryParams,
48 |   client: ISonarQubeClient = getDefaultClient()
49 | ) {
50 |   const result = await client.getMeasuresHistory(params);
51 | 
52 |   return createStructuredResponse(result);
53 | }
54 | 
```

--------------------------------------------------------------------------------
/src/transports/stdio.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
 2 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
 3 | import { ITransport } from './base.js';
 4 | 
 5 | /**
 6 |  * Extended interface for StdioServerTransport with the connect method.
 7 |  * This is a temporary workaround until the MCP SDK types are updated.
 8 |  * @deprecated Remove this interface when MCP SDK includes proper connect method typing
 9 |  */
10 | interface StdioServerTransportWithConnect extends StdioServerTransport {
11 |   connect: () => Promise<void>;
12 | }
13 | 
14 | /**
15 |  * STDIO transport implementation for MCP server.
16 |  * This transport uses standard input/output streams for communication.
17 |  */
18 | export class StdioTransport implements ITransport {
19 |   private readonly transport: StdioServerTransportWithConnect;
20 | 
21 |   constructor() {
22 |     this.transport = new StdioServerTransport() as StdioServerTransportWithConnect;
23 | 
24 |     // Add the connect method workaround for proper TypeScript compatibility
25 |     // This is a known issue with the current MCP SDK types
26 |     this.transport.connect = () => Promise.resolve();
27 |   }
28 | 
29 |   /**
30 |    * Connect the STDIO transport to the MCP server.
31 |    *
32 |    * @param server The MCP server instance to connect to
33 |    * @returns Promise that resolves when the connection is established
34 |    */
35 |   async connect(server: Server): Promise<void> {
36 |     await server.connect(this.transport);
37 |   }
38 | 
39 |   /**
40 |    * Get the name of this transport.
41 |    *
42 |    * @returns 'stdio'
43 |    */
44 |   getName(): string {
45 |     return 'stdio';
46 |   }
47 | 
48 |   /**
49 |    * Get the underlying StdioServerTransport instance.
50 |    * This is useful for backward compatibility and testing.
51 |    *
52 |    * @returns The underlying STDIO transport
53 |    */
54 |   getUnderlyingTransport(): StdioServerTransport {
55 |     return this.transport;
56 |   }
57 | }
58 | 
```

--------------------------------------------------------------------------------
/src/types/source-code.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import type { SonarQubeIssue } from './issues.js';
 2 | 
 3 | /**
 4 |  * Interface for source code location parameters
 5 |  */
 6 | export interface SourceCodeParams {
 7 |   key: string;
 8 |   from?: number;
 9 |   to?: number;
10 |   branch?: string;
11 |   pullRequest?: string;
12 | }
13 | 
14 | /**
15 |  * Interface for SCM blame parameters
16 |  */
17 | export interface ScmBlameParams {
18 |   key: string;
19 |   from?: number;
20 |   to?: number;
21 |   branch?: string;
22 |   pullRequest?: string;
23 | }
24 | 
25 | /**
26 |  * Interface for line issue in source code
27 |  */
28 | export interface SonarQubeLineIssue {
29 |   line: number;
30 |   issues: SonarQubeIssue[];
31 | }
32 | 
33 | /**
34 |  * Interface for SCM author information
35 |  */
36 | export interface SonarQubeScmAuthor {
37 |   revision: string;
38 |   date: string;
39 |   author: string;
40 | }
41 | 
42 | /**
43 |  * Interface for source code line with annotations
44 |  */
45 | export interface SonarQubeSourceLine {
46 |   line: number;
47 |   code: string;
48 |   scmAuthor: string | undefined;
49 |   scmDate: string | undefined;
50 |   scmRevision: string | undefined;
51 |   duplicated: boolean | undefined;
52 |   isNew: boolean | undefined;
53 |   lineHits: number | undefined;
54 |   conditions: number | undefined;
55 |   coveredConditions: number | undefined;
56 |   highlightedText: string | undefined;
57 |   issues: SonarQubeIssue[] | undefined;
58 | }
59 | 
60 | /**
61 |  * Interface for source code result
62 |  */
63 | export interface SonarQubeSourceResult {
64 |   component: {
65 |     key: string;
66 |     path: string | undefined;
67 |     qualifier: string;
68 |     name: string;
69 |     longName: string | undefined;
70 |     language: string | undefined;
71 |   };
72 |   sources: SonarQubeSourceLine[];
73 | }
74 | 
75 | /**
76 |  * Interface for SCM blame result
77 |  */
78 | export interface SonarQubeScmBlameResult {
79 |   component: {
80 |     key: string;
81 |     path?: string;
82 |     qualifier: string;
83 |     name: string;
84 |     longName?: string;
85 |     language?: string;
86 |   };
87 |   sources: {
88 |     [lineNumber: string]: SonarQubeScmAuthor;
89 |   };
90 | }
91 | 
```

--------------------------------------------------------------------------------
/src/transports/base.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
 2 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
 3 | 
 4 | /**
 5 |  * Base transport interface for MCP server transports.
 6 |  * This interface defines the contract that all transport implementations must follow.
 7 |  */
 8 | export interface ITransport {
 9 |   /**
10 |    * Connect the transport to the MCP server.
11 |    * This method should establish the connection and start listening for messages.
12 |    *
13 |    * @param server The MCP server instance to connect to
14 |    * @returns Promise that resolves when the connection is established
15 |    */
16 |   connect(server: Server): Promise<void>;
17 | 
18 |   /**
19 |    * Get the name of the transport for logging and debugging purposes.
20 |    *
21 |    * @returns The transport name (e.g., 'stdio')
22 |    */
23 |   getName(): string;
24 | }
25 | 
26 | /**
27 |  * Configuration options for transport initialization.
28 |  */
29 | export interface ITransportConfig {
30 |   /**
31 |    * The type of transport to use.
32 |    */
33 |   type: 'stdio' | 'http';
34 | 
35 |   /**
36 |    * Optional configuration specific to the transport type.
37 |    */
38 |   options?: Record<string, unknown>;
39 | }
40 | 
41 | /**
42 |  * HTTP-specific transport configuration.
43 |  */
44 | export interface IHttpTransportConfig extends ITransportConfig {
45 |   type: 'http';
46 |   options?: {
47 |     /**
48 |      * Port to listen on for HTTP requests.
49 |      */
50 |     port?: number;
51 | 
52 |     /**
53 |      * Allowed hosts for DNS rebinding protection.
54 |      */
55 |     allowedHosts?: string[];
56 | 
57 |     /**
58 |      * Allowed origins for CORS.
59 |      */
60 |     allowedOrigins?: string[];
61 | 
62 |     /**
63 |      * Session timeout in milliseconds.
64 |      */
65 |     sessionTimeout?: number;
66 | 
67 |     /**
68 |      * Enable DNS rebinding protection.
69 |      */
70 |     enableDnsRebindingProtection?: boolean;
71 |   };
72 | }
73 | 
74 | /**
75 |  * Type guard to check if a transport instance is STDIO transport.
76 |  * This is useful for maintaining backward compatibility.
77 |  */
78 | export function isStdioTransport(transport: unknown): transport is StdioServerTransport {
79 |   return transport instanceof StdioServerTransport;
80 | }
81 | 
```

--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------

```yaml
 1 | # Docker Compose for SonarQube MCP Server (stdio-only)
 2 | # Suitable for development and testing with MCP gateways
 3 | 
 4 | services:
 5 |   sonarqube-mcp-server:
 6 |     build: .
 7 |     image: sonarqube-mcp-server:1.7.0-stdio
 8 |     container_name: sonarqube-mcp-stdio
 9 | 
10 |     # Environment configuration
11 |     environment:
12 |       # SonarQube connection
13 |       SONARQUBE_URL: ${SONARQUBE_URL:-https://sonarcloud.io}
14 |       SONARQUBE_TOKEN: ${SONARQUBE_TOKEN}
15 |       SONARQUBE_ORGANIZATION: ${SONARQUBE_ORGANIZATION}
16 | 
17 |       # Logging
18 |       LOG_LEVEL: ${LOG_LEVEL:-INFO}
19 |       NODE_ENV: production
20 | 
21 |     # Stdio transport - no ports needed
22 |     # Network mode can be none for maximum isolation
23 |     network_mode: none
24 | 
25 |     # Resource limits for stdio operation
26 |     deploy:
27 |       resources:
28 |         limits:
29 |           memory: 512M
30 |           cpus: '0.5'
31 |         reservations:
32 |           memory: 256M
33 |           cpus: '0.25'
34 | 
35 |     # Health check using command execution (no HTTP endpoint)
36 |     healthcheck:
37 |       test: ['CMD', 'node', '-e', 'process.exit(0)']
38 |       interval: 30s
39 |       timeout: 10s
40 |       retries: 3
41 |       start_period: 10s
42 | 
43 |     # Restart policy
44 |     restart: unless-stopped
45 | 
46 |     # Security context
47 |     security_opt:
48 |       - no-new-privileges:true
49 |     read_only: true
50 |     tmpfs:
51 |       - /tmp:size=100M,noexec,nosuid,nodev
52 | 
53 |     # Volume for logs (optional)
54 |     volumes:
55 |       - ./logs:/app/logs
56 | 
57 |     # Labels for container management
58 |     labels:
59 |       - 'com.sonarqube.mcp.transport=stdio'
60 |       - 'com.sonarqube.mcp.version=1.7.0-stdio'
61 |       - 'com.sonarqube.mcp.description=SonarQube MCP Server - stdio transport only'
62 | 
63 |   # Example: MCP Gateway integration (commented out - requires actual gateway)
64 |   # mcp-gateway:
65 |   #   image: docker/mcp-gateway:latest  # Hypothetical gateway
66 |   #   container_name: mcp-gateway
67 |   #   ports:
68 |   #     - "8080:8080"
69 |   #   environment:
70 |   #     - MCP_SERVERS=sonarqube-mcp-stdio
71 |   #   depends_on:
72 |   #     - sonarqube-mcp-server
73 |   #   volumes:
74 |   #     - ./gateway-config:/config
75 | 
```

--------------------------------------------------------------------------------
/src/__tests__/components.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
 2 | import { resetDefaultClient } from '../utils/client-factory.js';
 3 | // Mock environment variables
 4 | process.env.SONARQUBE_TOKEN = 'test-token';
 5 | process.env.SONARQUBE_URL = 'http://localhost:9000';
 6 | process.env.SONARQUBE_ORGANIZATION = 'test-org';
 7 | // Save environment variables
 8 | const originalEnv = process.env;
 9 | describe('Components Handler', () => {
10 |   beforeEach(() => {
11 |     vi.clearAllMocks();
12 |     resetDefaultClient();
13 |     process.env = { ...originalEnv };
14 |   });
15 |   afterEach(() => {
16 |     process.env = originalEnv;
17 |     vi.clearAllMocks();
18 |     resetDefaultClient();
19 |   });
20 |   describe('Integration', () => {
21 |     it('should have components handler exported from index', async () => {
22 |       const module = await import('../index.js');
23 |       expect(module.componentsHandler).toBeDefined();
24 |       expect(typeof module.componentsHandler).toBe('function');
25 |     });
26 |     it('should have handleSonarQubeComponents exported from handlers', async () => {
27 |       const module = await import('../handlers/index.js');
28 |       expect(module.handleSonarQubeComponents).toBeDefined();
29 |       expect(typeof module.handleSonarQubeComponents).toBe('function');
30 |     });
31 |     it('should have ComponentsDomain exported from domains', async () => {
32 |       const module = await import('../domains/index.js');
33 |       expect(module.ComponentsDomain).toBeDefined();
34 |       expect(typeof module.ComponentsDomain).toBe('function');
35 |     });
36 |     it('should have components schemas exported', async () => {
37 |       const module = await import('../schemas/index.js');
38 |       expect(module.componentsToolSchema).toBeDefined();
39 |       expect(typeof module.componentsToolSchema).toBe('object');
40 |     });
41 |     it('should have components types exported', async () => {
42 |       const module = await import('../types/index.js');
43 |       // Check that types are available (will be stripped at runtime but validates imports work)
44 |       expect(module).toBeDefined();
45 |     });
46 |   });
47 | });
48 | 
```

--------------------------------------------------------------------------------
/.claude/hooks/block-git-no-verify.ts:
--------------------------------------------------------------------------------

```typescript
 1 | #!/usr/bin/env node
 2 | /**
 3 |  * Hook to prevent git commands with --no-verify flag from being executed.
 4 |  * This ensures all git hooks and verification steps are properly executed.
 5 |  */
 6 | 
 7 | import { readFileSync } from 'node:fs';
 8 | import process from 'node:process';
 9 | 
10 | interface ToolInput {
11 |   tool_name: string;
12 |   tool_input: {
13 |     command?: string;
14 |     [key: string]: unknown;
15 |   };
16 | }
17 | 
18 | function main(): void {
19 |   try {
20 |     // Read JSON input from stdin
21 |     const inputData = readFileSync(0, 'utf-8');
22 |     const parsedInput: ToolInput = JSON.parse(inputData);
23 | 
24 |     // Extract tool information
25 |     const toolName = parsedInput.tool_name ?? '';
26 |     const toolInput = parsedInput.tool_input ?? {};
27 | 
28 |     // Only process Bash tool calls
29 |     if (toolName !== 'Bash') {
30 |       process.exit(0);
31 |     }
32 | 
33 |     // Get the command from tool_input
34 |     const command = toolInput.command ?? '';
35 | 
36 |     // Check if it's a git command
37 |     if (!/\bgit\b/.test(command)) {
38 |       process.exit(0);
39 |     }
40 | 
41 |     // Remove quoted strings to avoid false positives
42 |     // Remove single-quoted strings
43 |     let cleanedCmd = command.replace(/'[^']*'/g, '');
44 |     // Remove double-quoted strings
45 |     cleanedCmd = cleanedCmd.replace(/"[^"]*"/g, '');
46 | 
47 |     // Check for --no-verify or -n flag
48 |     const noVerifyPattern = /(^|\s)--no-verify($|=|\s)/;
49 |     const shortNPattern = /(^|\s)-n($|\s)/;
50 | 
51 |     if (noVerifyPattern.test(cleanedCmd) || shortNPattern.test(cleanedCmd)) {
52 |       // Block with error message (exit code 2)
53 |       // eslint-disable-next-line no-console
54 |       console.error('Error: Git commands with --no-verify flag are not allowed.');
55 |       // eslint-disable-next-line no-console
56 |       console.error('This ensures all git hooks and verification steps are properly executed.');
57 |       // eslint-disable-next-line no-console
58 |       console.error('Please run the git command without the --no-verify flag.');
59 |       process.exit(2);
60 |     }
61 | 
62 |     // Allow the command to proceed
63 |     process.exit(0);
64 |   } catch {
65 |     // For any errors (including JSON parsing), allow the command (fail open)
66 |     process.exit(0);
67 |   }
68 | }
69 | 
70 | main();
71 | 
```

--------------------------------------------------------------------------------
/src/types/components.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import type { SonarQubeComponent } from './issues.js';
  2 | 
  3 | /**
  4 |  * Component qualifier types based on SonarQube API
  5 |  */
  6 | export type ComponentQualifier =
  7 |   | 'TRK' // Project
  8 |   | 'DIR' // Directory
  9 |   | 'FIL' // File
 10 |   | 'UTS' // Unit Test
 11 |   | 'BRC' // Branch
 12 |   | 'APP' // Application
 13 |   | 'VW' // View
 14 |   | 'SVW' // Sub-view
 15 |   | 'LIB'; // Library
 16 | 
 17 | /**
 18 |  * Result of component search operation
 19 |  */
 20 | export interface ComponentsResult {
 21 |   components: SonarQubeComponent[];
 22 |   paging: {
 23 |     pageIndex: number;
 24 |     pageSize: number;
 25 |     total: number;
 26 |   };
 27 | }
 28 | 
 29 | /**
 30 |  * Result of component tree navigation
 31 |  */
 32 | export interface ComponentsTreeResult {
 33 |   components: SonarQubeComponent[];
 34 |   baseComponent: SonarQubeComponent | undefined;
 35 |   paging: {
 36 |     pageIndex: number;
 37 |     pageSize: number;
 38 |     total: number;
 39 |   };
 40 | }
 41 | 
 42 | /**
 43 |  * Result of component show operation
 44 |  */
 45 | export interface ComponentShowResult {
 46 |   component: SonarQubeComponent;
 47 |   ancestors: SonarQubeComponent[];
 48 | }
 49 | 
 50 | /**
 51 |  * Parameters for searching components
 52 |  */
 53 | export interface ComponentsSearchParams {
 54 |   query?: string;
 55 |   qualifiers?: ComponentQualifier[];
 56 |   language?: string;
 57 |   page?: number;
 58 |   pageSize?: number;
 59 | }
 60 | 
 61 | /**
 62 |  * Parameters for navigating component tree
 63 |  */
 64 | export interface ComponentsTreeParams {
 65 |   component: string;
 66 |   strategy?: 'all' | 'children' | 'leaves';
 67 |   qualifiers?: ComponentQualifier[];
 68 |   sort?: 'name' | 'path' | 'qualifier';
 69 |   asc?: boolean;
 70 |   page?: number;
 71 |   pageSize?: number;
 72 |   branch?: string;
 73 |   pullRequest?: string;
 74 | }
 75 | 
 76 | /**
 77 |  * Parameters for showing component details
 78 |  */
 79 | export interface ComponentShowParams {
 80 |   key: string;
 81 |   branch?: string;
 82 |   pullRequest?: string;
 83 | }
 84 | 
 85 | /**
 86 |  * Combined parameters for components action
 87 |  */
 88 | export interface ComponentsParams {
 89 |   // Search parameters
 90 |   query?: string;
 91 |   qualifiers?: ComponentQualifier[];
 92 |   language?: string;
 93 | 
 94 |   // Tree navigation parameters
 95 |   component?: string;
 96 |   strategy?: 'all' | 'children' | 'leaves';
 97 | 
 98 |   // Show component parameter
 99 |   key?: string;
100 | 
101 |   // Common parameters
102 |   asc?: boolean;
103 |   ps?: number;
104 |   p?: number;
105 |   branch?: string;
106 |   pullRequest?: string;
107 | }
108 | 
```

--------------------------------------------------------------------------------
/src/utils/retry.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { createLogger } from './logger.js';
 2 | 
 3 | const logger = createLogger('Retry');
 4 | 
 5 | export interface RetryOptions {
 6 |   maxAttempts?: number;
 7 |   initialDelay?: number;
 8 |   maxDelay?: number;
 9 |   backoffMultiplier?: number;
10 |   shouldRetry?: (error: Error, attempt: number) => boolean;
11 | }
12 | 
13 | const DEFAULT_OPTIONS: Required<RetryOptions> = {
14 |   maxAttempts: 3,
15 |   initialDelay: 1000,
16 |   maxDelay: 10000,
17 |   backoffMultiplier: 2,
18 |   shouldRetry: (error: Error) => {
19 |     // Retry on network errors and 5xx server errors
20 |     const message = error.message.toLowerCase();
21 |     return (
22 |       message.includes('econnrefused') ||
23 |       message.includes('etimedout') ||
24 |       message.includes('enotfound') ||
25 |       message.includes('econnreset') ||
26 |       message.includes('socket hang up') ||
27 |       (message.includes('50') && !message.includes('40')) // 5xx errors
28 |     );
29 |   },
30 | };
31 | 
32 | /**
33 |  * Retry a function with exponential backoff
34 |  */
35 | export async function withRetry<T>(fn: () => Promise<T>, options: RetryOptions = {}): Promise<T> {
36 |   const opts = { ...DEFAULT_OPTIONS, ...options };
37 |   let lastError: Error | null = null;
38 | 
39 |   for (let attempt = 1; attempt <= opts.maxAttempts; attempt++) {
40 |     try {
41 |       return await fn();
42 |     } catch (error) {
43 |       lastError = error as Error;
44 | 
45 |       if (attempt === opts.maxAttempts || !opts.shouldRetry(lastError, attempt)) {
46 |         throw lastError;
47 |       }
48 | 
49 |       const delay = Math.min(
50 |         opts.initialDelay * Math.pow(opts.backoffMultiplier, attempt - 1),
51 |         opts.maxDelay
52 |       );
53 | 
54 |       logger.warn(`Attempt ${attempt} failed, retrying in ${delay}ms`, {
55 |         error: lastError.message,
56 |         attempt,
57 |         nextDelay: delay,
58 |       });
59 | 
60 |       await new Promise((resolve) => setTimeout(resolve, delay));
61 |     }
62 |   }
63 | 
64 |   throw new Error(`Max retry attempts reached: ${lastError?.message}`);
65 | }
66 | 
67 | /**
68 |  * Create a retryable version of a function
69 |  */
70 | export function makeRetryable<T extends unknown[], R>(
71 |   fn: (...args: T) => Promise<R>,
72 |   options: RetryOptions = {}
73 | ): (...args: T) => Promise<R> {
74 |   return async (...args: T): Promise<R> => {
75 |     return withRetry(() => fn(...args), options);
76 |   };
77 | }
78 | 
```

--------------------------------------------------------------------------------
/src/utils/pattern-matcher.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { createLogger } from './logger.js';
 2 | 
 3 | const logger = createLogger('PatternMatcher');
 4 | 
 5 | /**
 6 |  * A safe pattern matcher that uses glob-style patterns instead of regex.
 7 |  * This eliminates regex injection vulnerabilities while providing
 8 |  * sufficient flexibility for matching users and issuers.
 9 |  *
10 |  * Supported patterns:
11 |  * - * matches zero or more characters
12 |  * - ? matches exactly one character
13 |  * - All other characters match literally
14 |  *
15 |  * Examples:
16 |  * - *@example.com matches any email at example.com
17 |  * - user-? matches user-1, user-a, etc.
18 |  * - https://*.auth.com matches any subdomain of auth.com
19 |  */
20 | export class PatternMatcher {
21 |   private readonly pattern: string;
22 |   private readonly regex: RegExp;
23 | 
24 |   constructor(pattern: string) {
25 |     this.pattern = pattern;
26 |     this.regex = this.globToRegex(pattern);
27 |   }
28 | 
29 |   /**
30 |    * Convert a glob pattern to a safe regex
31 |    */
32 |   private globToRegex(pattern: string): RegExp {
33 |     // Escape all regex special characters except * and ?
34 |     const escaped = pattern
35 |       .replaceAll(/[\\^$.()|[\]{}+]/g, '\\$&') // Escape regex special chars
36 |       .replaceAll('*', '.*') // * matches any sequence
37 |       .replaceAll('?', '.'); // ? matches any single character
38 | 
39 |     // Create regex with anchors for full string matching
40 |     return new RegExp(`^${escaped}$`);
41 |   }
42 | 
43 |   /**
44 |    * Test if a string matches the pattern
45 |    */
46 |   test(value: string): boolean {
47 |     return this.regex.test(value);
48 |   }
49 | 
50 |   /**
51 |    * Get the original pattern
52 |    */
53 |   getPattern(): string {
54 |     return this.pattern;
55 |   }
56 | 
57 |   /**
58 |    * Create a pattern matcher from a string, with error handling
59 |    */
60 |   static create(pattern: string, context: string): PatternMatcher | undefined {
61 |     try {
62 |       const matcher = new PatternMatcher(pattern);
63 |       logger.debug('Created pattern matcher', {
64 |         context,
65 |         pattern,
66 |         regex: matcher.regex.source,
67 |       });
68 |       return matcher;
69 |     } catch (error) {
70 |       logger.warn('Failed to create pattern matcher', {
71 |         pattern,
72 |         context,
73 |         error: error instanceof Error ? error.message : 'Unknown error',
74 |       });
75 |       return undefined;
76 |     }
77 |   }
78 | }
79 | 
```

--------------------------------------------------------------------------------
/docs/architecture/decisions/0002-use-node-js-with-typescript.md:
--------------------------------------------------------------------------------

```markdown
 1 | # 2. Use Node.js with TypeScript
 2 | 
 3 | Date: 2025-06-13
 4 | 
 5 | ## Status
 6 | 
 7 | Accepted
 8 | 
 9 | ## Context
10 | 
11 | The SonarQube MCP server needs to be implemented in a technology stack that provides:
12 | 
13 | - Strong type safety to prevent runtime errors
14 | - Rich ecosystem for HTTP clients and server frameworks
15 | - Easy distribution mechanism for end users
16 | - Good developer experience with modern tooling
17 | - Cross-platform compatibility
18 | - Quick feedback loop during development
19 | 
20 | Alternative languages like Rust were considered but would introduce longer compilation times and a steeper learning curve.
21 | 
22 | ## Decision
23 | 
24 | We will implement the server using Node.js with TypeScript, managed by pnpm.
25 | 
26 | Key aspects of this decision:
27 | 
28 | - **Runtime**: Node.js provides a stable, well-supported JavaScript runtime
29 | - **Language**: TypeScript adds static typing, improving code reliability and developer experience
30 | - **Package Manager**: pnpm for efficient dependency management
31 | - **Distribution**: npm/npx for easy installation and execution by end users
32 | 
33 | ## Consequences
34 | 
35 | ### Positive
36 | 
37 | - **Quick Feedback Loop**: Fast compilation and hot-reloading enable rapid development iterations compared to compiled languages like Rust
38 | - **Type Safety**: TypeScript's static typing catches errors at compile time, reducing runtime bugs
39 | - **Developer Experience**: Modern IDE support, auto-completion, and refactoring tools
40 | - **Ecosystem**: Access to npm's vast collection of packages for HTTP clients, testing, and utilities
41 | - **Distribution**: Users can easily install and run the server via `npx @burgess01/sonarqube-mcp-server`
42 | - **Community**: Large community support for both Node.js and TypeScript
43 | - **Cross-platform**: Works on Windows, macOS, and Linux without modification
44 | 
45 | ### Negative
46 | 
47 | - **Build Step**: TypeScript requires compilation to JavaScript before execution
48 | - **Learning Curve**: Developers need familiarity with TypeScript's type system
49 | - **Bundle Size**: TypeScript adds some overhead compared to plain JavaScript
50 | 
51 | ### Neutral
52 | 
53 | - **Performance**: Node.js provides adequate performance for an MCP server that primarily makes HTTP requests
54 | - **Maintenance**: Regular updates needed for Node.js, TypeScript, and dependencies
55 | 
```

--------------------------------------------------------------------------------
/docs/architecture/decisions/0004-use-sonarqube-web-api-client-for-all-sonarqube-interactions.md:
--------------------------------------------------------------------------------

```markdown
 1 | # 4. Use SonarQube Web API Client for all SonarQube interactions
 2 | 
 3 | Date: 2025-06-13
 4 | 
 5 | ## Status
 6 | 
 7 | Accepted
 8 | 
 9 | ## Context
10 | 
11 | The SonarQube MCP server needs to interact with SonarQube's REST API to provide functionality for code quality analysis, issue management, and metrics retrieval. We need a reliable and maintainable approach for making these API calls that:
12 | 
13 | - Handles authentication consistently
14 | - Manages API versioning and endpoint changes
15 | - Provides type safety for API requests and responses
16 | - Reduces boilerplate code for common operations
17 | - Offers a clear abstraction layer between our server and SonarQube's API
18 | 
19 | ## Decision
20 | 
21 | We will use the `sonarqube-web-api-client` library for all interactions with SonarQube. A custom `SonarQubeClient` class will wrap this API client to:
22 | 
23 | 1. Encapsulate authentication logic (API tokens)
24 | 2. Provide higher-level methods for common operations (projects, issues, metrics, etc.)
25 | 3. Handle error responses consistently
26 | 4. Abstract away the complexity of the underlying API client
27 | 
28 | All SonarQube API interactions will go through this client wrapper rather than making direct HTTP requests or using the API client library directly in action implementations.
29 | 
30 | ## Consequences
31 | 
32 | ### Positive
33 | 
34 | - **Consistency**: All API interactions follow the same pattern and error handling approach
35 | - **Maintainability**: Changes to authentication or API endpoints can be handled in one place
36 | - **Type Safety**: The library provides TypeScript types for API requests and responses
37 | - **Reduced Complexity**: Action implementations focus on business logic rather than HTTP details
38 | - **Testability**: The client wrapper can be easily mocked for unit testing
39 | 
40 | ### Negative
41 | 
42 | - **Dependency**: We're tied to the `sonarqube-web-api-client` library's maintenance and updates
43 | - **Abstraction Overhead**: Adding another layer of abstraction may hide some API capabilities
44 | - **Learning Curve**: Developers need to understand both our wrapper and the underlying library
45 | 
46 | ### Neutral
47 | 
48 | - The client wrapper pattern is a common architectural approach that most developers will be familiar with
49 | - Future changes to the SonarQube API may require updates to both the library and our wrapper
50 | 
```

--------------------------------------------------------------------------------
/docs/architecture/decisions/0009-file-based-logging-to-avoid-stdio-conflicts.md:
--------------------------------------------------------------------------------

```markdown
 1 | # 9. File-based logging to avoid STDIO conflicts
 2 | 
 3 | Date: 2025-06-13
 4 | 
 5 | ## Status
 6 | 
 7 | Accepted
 8 | 
 9 | ## Context
10 | 
11 | The Model Context Protocol (MCP) uses standard input/output (STDIO) streams for communication between the client and server. This creates a fundamental conflict with traditional logging approaches that write to stdout or stderr.
12 | 
13 | When an MCP server writes anything to stdout, it must be valid JSON-RPC messages conforming to the MCP protocol. Any non-protocol output (such as log messages) written to stdout would corrupt the communication stream and cause protocol errors.
14 | 
15 | Similarly, stderr output could interfere with the client's ability to properly parse and handle MCP messages, potentially causing connection failures or undefined behavior.
16 | 
17 | ## Decision
18 | 
19 | We will implement file-based logging for all diagnostic and debugging output in the MCP server.
20 | 
21 | The server will:
22 | 
23 | - Write all log messages to a file specified by the `LOG_FILE` environment variable
24 | - Default to no logging if `LOG_FILE` is not set
25 | - Never write log messages to stdout or stderr
26 | - Ensure all stdout output consists only of valid MCP protocol messages
27 | 
28 | ## Consequences
29 | 
30 | ### Positive
31 | 
32 | - **Protocol integrity**: The MCP communication stream remains clean and uncorrupted
33 | - **Reliable communication**: Prevents protocol errors caused by interleaved log messages
34 | - **Debugging capability**: Developers can still access detailed logs for troubleshooting
35 | - **Configuration flexibility**: Log file location can be customized via environment variable
36 | 
37 | ### Negative
38 | 
39 | - **No console logging**: Developers cannot see logs in real-time in the console during development
40 | - **File management**: Log files need to be managed (rotation, cleanup) to prevent disk space issues
41 | - **Additional setup**: Developers must know where to find log files for debugging
42 | - **Potential permissions issues**: The server must have write permissions to the log file location
43 | 
44 | ### Mitigation
45 | 
46 | - Document the log file location clearly in README and error messages
47 | - Consider implementing log rotation to manage file size
48 | - Provide clear error messages if log file cannot be created
49 | - Include log file location in any error output that is part of the protocol
50 | 
```

--------------------------------------------------------------------------------
/docs/architecture/decisions/0010-use-stdio-transport-for-mcp-communication.md:
--------------------------------------------------------------------------------

```markdown
 1 | # 10. Use STDIO transport for MCP communication
 2 | 
 3 | Date: 2025-06-13
 4 | 
 5 | ## Status
 6 | 
 7 | Accepted
 8 | 
 9 | ## Context
10 | 
11 | The Model Context Protocol (MCP) server needs a transport mechanism to communicate with MCP clients. The transport layer determines how the server receives requests and sends responses.
12 | 
13 | Several transport options are available:
14 | 
15 | - STDIO (Standard Input/Output)
16 | - WebSocket
17 | - HTTP
18 | - Named pipes
19 | 
20 | The server needs to be easily invokable from various environments (node, npx, Docker) without requiring complex network configuration or exposing network ports.
21 | 
22 | ## Decision
23 | 
24 | We will use StdioServerTransport for MCP communication, with a dummy `connect()` method for compatibility with the MCP protocol requirements.
25 | 
26 | The server communicates with clients over standard input/output streams, making it a CLI tool that can be launched as a subprocess by MCP clients.
27 | 
28 | ## Consequences
29 | 
30 | ### Positive
31 | 
32 | - **Simple invocation**: The server can be launched via `node`, `npx`, or `docker run` commands without additional configuration
33 | - **No network exposure**: Eliminates security concerns about open network ports or unauthorized access
34 | - **Cross-platform compatibility**: STDIO works consistently across all operating systems
35 | - **Process isolation**: Each client gets its own server process with isolated state
36 | - **Easy debugging**: STDIO communication can be easily logged and inspected
37 | - **Container-friendly**: Works seamlessly in Docker containers without port mapping
38 | 
39 | ### Negative
40 | 
41 | - **Single client per process**: Each MCP client requires its own server process
42 | - **No remote access**: Clients must run on the same machine or use SSH/container forwarding
43 | - **Process lifecycle management**: The client is responsible for starting and stopping the server process
44 | - **Resource overhead**: Multiple clients mean multiple Node.js processes
45 | 
46 | ### Implementation Notes
47 | 
48 | - The dummy `connect()` method is required because the MCP protocol expects all transports to implement this interface, even though STDIO doesn't need an explicit connection step
49 | - The server reads JSON-RPC messages from stdin and writes responses to stdout
50 | - Error messages and logs should be written to stderr to avoid interfering with the JSON-RPC communication
51 | 
```

--------------------------------------------------------------------------------
/src/__tests__/dependency-injection.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
 2 | import { createDefaultClient } from '../index.js';
 3 | import { createSonarQubeClient } from '../sonarqube.js';
 4 | // Save original environment variables
 5 | const originalEnv = process.env;
 6 | describe('Client Configuration Tests', () => {
 7 |   beforeEach(() => {
 8 |     vi.resetModules();
 9 |     process.env = { ...originalEnv };
10 |     process.env.SONARQUBE_TOKEN = 'test-token';
11 |     process.env.SONARQUBE_URL = 'http://localhost:9000';
12 |     process.env.SONARQUBE_ORGANIZATION = 'test-org';
13 |   });
14 |   afterEach(() => {
15 |     process.env = originalEnv;
16 |     vi.clearAllMocks();
17 |   });
18 |   describe('Client Factory Functions', () => {
19 |     it('should create a client with default parameters', () => {
20 |       // Test the factory function
21 |       const client = createSonarQubeClient('test-token');
22 |       expect(client).toBeDefined();
23 |     });
24 |     it('should create a default client using environment variables', () => {
25 |       // Test the default client creation function
26 |       const client = createDefaultClient();
27 |       expect(client).toBeDefined();
28 |     });
29 |     it('should create a client with custom base URL and organization', () => {
30 |       const customUrl = 'https://custom-sonar.example.com';
31 |       const customOrg = 'custom-org';
32 |       const client = createSonarQubeClient('test-token', customUrl, customOrg);
33 |       expect(client).toBeDefined();
34 |     });
35 |     it('should handle null organization parameter', () => {
36 |       const client = createSonarQubeClient('test-token', undefined, null);
37 |       expect(client).toBeDefined();
38 |     });
39 |   });
40 |   describe('Environment Variable Configuration', () => {
41 |     it('should use environment variables for default client creation', () => {
42 |       process.env.SONARQUBE_TOKEN = 'env-token';
43 |       process.env.SONARQUBE_URL = 'https://env-sonar.example.com';
44 |       process.env.SONARQUBE_ORGANIZATION = 'env-org';
45 |       const client = createDefaultClient();
46 |       expect(client).toBeDefined();
47 |     });
48 |     it('should handle missing optional environment variables', () => {
49 |       process.env.SONARQUBE_TOKEN = 'env-token';
50 |       delete process.env.SONARQUBE_URL;
51 |       delete process.env.SONARQUBE_ORGANIZATION;
52 |       const client = createDefaultClient();
53 |       expect(client).toBeDefined();
54 |     });
55 |   });
56 | });
57 | 
```

--------------------------------------------------------------------------------
/src/types/measures.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import type { PaginationParams } from './common.js';
  2 | import type { SonarQubeMetric } from './metrics.js';
  3 | 
  4 | /**
  5 |  * Interface for component measures parameters
  6 |  */
  7 | export interface ComponentMeasuresParams {
  8 |   component: string;
  9 |   metricKeys: string[];
 10 |   additionalFields?: string[];
 11 |   branch?: string;
 12 |   pullRequest?: string;
 13 |   period?: string;
 14 | }
 15 | 
 16 | /**
 17 |  * Interface for components measures parameters
 18 |  */
 19 | export interface ComponentsMeasuresParams extends PaginationParams {
 20 |   componentKeys: string[] | string;
 21 |   metricKeys: string[] | string;
 22 |   additionalFields?: string[];
 23 |   branch?: string;
 24 |   pullRequest?: string;
 25 |   period?: string;
 26 | }
 27 | 
 28 | /**
 29 |  * Interface for measures history parameters
 30 |  */
 31 | export interface MeasuresHistoryParams extends PaginationParams {
 32 |   component: string;
 33 |   metrics: string[];
 34 |   from?: string;
 35 |   to?: string;
 36 |   branch?: string;
 37 |   pullRequest?: string;
 38 | }
 39 | 
 40 | /**
 41 |  * Interface for SonarQube measure
 42 |  */
 43 | export interface SonarQubeMeasure {
 44 |   metric: string;
 45 |   value?: string;
 46 |   period?: {
 47 |     index: number;
 48 |     value: string;
 49 |   };
 50 |   bestValue?: boolean;
 51 | }
 52 | 
 53 | /**
 54 |  * Interface for SonarQube measure component
 55 |  */
 56 | export interface SonarQubeMeasureComponent {
 57 |   key: string;
 58 |   name: string;
 59 |   qualifier: string;
 60 |   measures: SonarQubeMeasure[];
 61 |   periods?: Array<{
 62 |     index: number;
 63 |     mode: string;
 64 |     date: string;
 65 |     parameter?: string;
 66 |   }>;
 67 | }
 68 | 
 69 | /**
 70 |  * Interface for SonarQube component with measures result
 71 |  */
 72 | export interface SonarQubeComponentMeasuresResult {
 73 |   component: SonarQubeMeasureComponent;
 74 |   metrics: SonarQubeMetric[];
 75 |   period?: {
 76 |     index: number;
 77 |     mode: string;
 78 |     date: string;
 79 |     parameter?: string;
 80 |   };
 81 | }
 82 | 
 83 | /**
 84 |  * Interface for SonarQube components with measures result
 85 |  */
 86 | export interface SonarQubeComponentsMeasuresResult {
 87 |   components: SonarQubeMeasureComponent[];
 88 |   metrics: SonarQubeMetric[];
 89 |   paging: {
 90 |     pageIndex: number;
 91 |     pageSize: number;
 92 |     total: number;
 93 |   };
 94 |   period?: {
 95 |     index: number;
 96 |     mode: string;
 97 |     date: string;
 98 |     parameter?: string;
 99 |   };
100 | }
101 | 
102 | /**
103 |  * Interface for SonarQube measures history result
104 |  */
105 | export interface SonarQubeMeasuresHistoryResult {
106 |   paging: {
107 |     pageIndex: number;
108 |     pageSize: number;
109 |     total: number;
110 |   };
111 |   measures: {
112 |     metric: string;
113 |     history: {
114 |       date: string;
115 |       value?: string;
116 |     }[];
117 |   }[];
118 | }
119 | 
```

--------------------------------------------------------------------------------
/.github/workflows/codeql.yml:
--------------------------------------------------------------------------------

```yaml
 1 | # =============================================================================
 2 | # WORKFLOW: CodeQL Security Analysis
 3 | # PURPOSE: Continuous security analysis for the default branch and pull requests
 4 | # TRIGGERS: Push to main, Pull requests to main
 5 | # OUTPUTS: Security findings uploaded to GitHub Security tab
 6 | # =============================================================================
 7 | 
 8 | name: CodeQL
 9 | 
10 | on:
11 |   push:
12 |     branches: [main]
13 |   pull_request:
14 |     branches: [main]
15 |   schedule:
16 |     # Run daily at 00:00 UTC to catch new vulnerabilities
17 |     - cron: '0 0 * * *'
18 | 
19 | # SECURITY: Required permissions for CodeQL analysis
20 | permissions:
21 |   actions: read # Read workflow metadata
22 |   contents: read # Read source code
23 |   security-events: write # Upload security findings to Security tab
24 | 
25 | jobs:
26 |   analyze:
27 |     name: Analyze Code
28 |     runs-on: ubuntu-latest
29 | 
30 |     steps:
31 |       - name: Checkout code
32 |         uses: actions/checkout@v4
33 |         with:
34 |           fetch-depth: 0 # Full history for accurate analysis
35 | 
36 |       - name: Install pnpm
37 |         uses: pnpm/action-setup@v4
38 |         with:
39 |           version: 10.17.0
40 |           run_install: false
41 |           standalone: true
42 | 
43 |       - name: Setup Node.js
44 |         uses: actions/setup-node@v4
45 |         with:
46 |           node-version: 22
47 |           cache: pnpm
48 | 
49 |       - name: Install dependencies
50 |         # Dependencies needed for accurate CodeQL analysis
51 |         run: pnpm install --frozen-lockfile
52 | 
53 |       # =============================================================================
54 |       # CODEQL STATIC ANALYSIS
55 |       # Scans for security vulnerabilities in source code
56 |       # =============================================================================
57 | 
58 |       - name: Initialize CodeQL
59 |         # Setup CodeQL for JavaScript/TypeScript analysis
60 |         # Detects: XSS, SQL injection, path traversal, command injection, etc.
61 |         uses: github/codeql-action/init@v3
62 |         with:
63 |           languages: javascript-typescript
64 |           # Optionally specify additional queries to run
65 |           # queries: security-extended,security-and-quality
66 | 
67 |       - name: Perform CodeQL Analysis
68 |         # Analyze code and upload results to Security tab
69 |         # Results viewable at: Security > Code scanning alerts
70 |         uses: github/codeql-action/analyze@v3
71 |         with:
72 |           category: '/language:javascript-typescript'
73 | 
```

--------------------------------------------------------------------------------
/src/domains/system.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import type { SonarQubeHealthStatus, SonarQubeSystemStatus } from '../types/index.js';
 2 | import { BaseDomain } from './base.js';
 3 | 
 4 | /**
 5 |  * Domain module for system-related operations
 6 |  */
 7 | export class SystemDomain extends BaseDomain {
 8 |   /**
 9 |    * Gets the health status of the SonarQube instance using V2 API
10 |    *
11 |    * The V2 API response structure differs from V1:
12 |    * - V2 returns `status` field instead of `health`
13 |    * - V2 includes optional `nodes` array for clustered setups
14 |    * - Each node can have its own `causes` array for health issues
15 |    *
16 |    * This method transforms the V2 response to maintain backward compatibility
17 |    * with the existing SonarQubeHealthStatus interface.
18 |    *
19 |    * @returns Promise with the health status containing aggregated causes from all nodes
20 |    */
21 |   async getHealth(): Promise<SonarQubeHealthStatus> {
22 |     return this.tracedApiCall('system/health', async () => {
23 |       const response = await this.webApiClient.system.getHealthV2();
24 |       const causes = this.extractCausesFromNodes(response.nodes);
25 | 
26 |       return {
27 |         health: response.status,
28 |         causes,
29 |       };
30 |     });
31 |   }
32 | 
33 |   /**
34 |    * Extracts and aggregates causes from all nodes in a clustered SonarQube setup
35 |    *
36 |    * @param nodes - Optional array of nodes from V2 health API response
37 |    * @returns Array of all causes from all nodes, or empty array if no nodes/causes
38 |    * @private
39 |    */
40 |   private extractCausesFromNodes(nodes?: Array<{ causes?: string[] }>): string[] {
41 |     if (!nodes) {
42 |       return [];
43 |     }
44 | 
45 |     const causes: string[] = [];
46 |     for (const node of nodes) {
47 |       if (node.causes) {
48 |         causes.push(...node.causes);
49 |       }
50 |     }
51 | 
52 |     return causes;
53 |   }
54 | 
55 |   /**
56 |    * Gets the system status of the SonarQube instance
57 |    * @returns Promise with the system status
58 |    */
59 |   async getStatus(): Promise<SonarQubeSystemStatus> {
60 |     return this.tracedApiCall('system/status', async () => {
61 |       const response = await this.webApiClient.system.status();
62 |       return {
63 |         id: response.id,
64 |         version: response.version,
65 |         status: response.status,
66 |       };
67 |     });
68 |   }
69 | 
70 |   /**
71 |    * Pings the SonarQube instance
72 |    * @returns Promise with the ping response
73 |    */
74 |   async ping(): Promise<string> {
75 |     return this.tracedApiCall('system/ping', async () => {
76 |       const response = await this.webApiClient.system.ping();
77 |       return response;
78 |     });
79 |   }
80 | }
81 | 
```

--------------------------------------------------------------------------------
/docs/architecture/decisions/0007-support-multiple-authentication-methods-for-sonarqube.md:
--------------------------------------------------------------------------------

```markdown
 1 | # 7. Support Multiple Authentication Methods for SonarQube
 2 | 
 3 | Date: 2025-06-13
 4 | 
 5 | ## Status
 6 | 
 7 | Accepted
 8 | 
 9 | ## Context
10 | 
11 | SonarQube offers different deployment models (SonarQube Server and SonarCloud) that use different authentication mechanisms:
12 | 
13 | - SonarQube Server typically uses user tokens or username/password authentication
14 | - SonarCloud uses bearer tokens
15 | - Some installations may use system-level passcodes for authentication
16 | 
17 | To provide a flexible MCP server that works across all SonarQube deployment scenarios, we need to support multiple authentication methods.
18 | 
19 | ## Decision
20 | 
21 | We will support three authentication methods for SonarQube API access:
22 | 
23 | 1. **Bearer Token Authentication** (preferred)
24 |    - Uses the `SONARQUBE_TOKEN` environment variable
25 |    - Sent as `Authorization: Bearer <token>` header
26 |    - Primary method for SonarCloud and modern SonarQube Server installations
27 | 
28 | 2. **HTTP Basic Authentication**
29 |    - Uses `SONARQUBE_USERNAME` and `SONARQUBE_PASSWORD` environment variables
30 |    - Sent as `Authorization: Basic <base64(username:password)>` header
31 |    - Fallback for older SonarQube Server installations
32 | 
33 | 3. **System Passcode Authentication**
34 |    - Uses the `SONARQUBE_PASSCODE` environment variable
35 |    - Sent as `X-Sonar-Passcode` header
36 |    - For system-level operations on certain SonarQube Server configurations
37 | 
38 | The authentication method is automatically selected based on which environment variables are present, with bearer token authentication taking precedence.
39 | 
40 | ## Consequences
41 | 
42 | ### Positive
43 | 
44 | - **Broad compatibility**: The MCP server works with all SonarQube deployment models
45 | - **User flexibility**: Administrators can choose the authentication method that best fits their security policies
46 | - **Future-proof**: Supporting token-based authentication aligns with modern security practices
47 | - **Backward compatibility**: HTTP Basic auth ensures older installations remain supported
48 | 
49 | ### Negative
50 | 
51 | - **Configuration complexity**: Users need to understand which authentication method to use for their setup
52 | - **Security considerations**: Supporting multiple auth methods increases the attack surface
53 | - **Maintenance burden**: Each authentication method needs to be tested and maintained
54 | 
55 | ### Mitigation
56 | 
57 | - Clear documentation explaining which authentication method to use for different SonarQube deployments
58 | - Security warnings in documentation about the relative security of each method
59 | - Automated tests covering all authentication scenarios
60 | 
```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
 1 | # Build stage - compile TypeScript to JavaScript
 2 | FROM node:22-alpine AS builder
 3 | 
 4 | WORKDIR /app
 5 | 
 6 | # Copy package files
 7 | COPY package.json pnpm-lock.yaml ./
 8 | 
 9 | # Install pnpm
10 | RUN npm install -g [email protected]
11 | 
12 | # Install ALL dependencies (including dev) needed for build
13 | RUN pnpm install --frozen-lockfile --ignore-scripts
14 | 
15 | # Copy source code and build configuration
16 | COPY src/ ./src/
17 | COPY tsconfig.json tsconfig.build.json ./
18 | 
19 | # Build the application
20 | RUN pnpm run build
21 | 
22 | # Production dependencies stage - prepare clean node_modules
23 | FROM node:22-alpine AS deps
24 | 
25 | WORKDIR /app
26 | 
27 | # Copy package files
28 | COPY package.json pnpm-lock.yaml ./
29 | 
30 | # Install pnpm
31 | RUN npm install -g [email protected]
32 | 
33 | # Install ONLY production dependencies
34 | RUN pnpm install --prod --frozen-lockfile --ignore-scripts
35 | 
36 | # Production stage - minimal stdio-only runtime
37 | FROM node:22-alpine
38 | 
39 | # Update OpenSSL to fix CVE-2025-9230, CVE-2025-9231, CVE-2025-9232
40 | # Upgrade libcrypto3 and libssl3 from 3.5.1-r0 to 3.5.4-r0
41 | RUN apk update && \
42 |     apk upgrade --no-cache libcrypto3 libssl3 && \
43 |     rm -rf /var/cache/apk/*
44 | 
45 | # Create non-root user upfront
46 | RUN addgroup -g 1001 nodejs && \
47 |     adduser -S -u 1001 -G nodejs nodejs
48 | 
49 | WORKDIR /app
50 | 
51 | # Copy production dependencies from deps stage (clean, no dev deps)
52 | COPY --from=deps /app/node_modules ./node_modules
53 | 
54 | # Copy package.json for metadata
55 | COPY --from=deps /app/package.json ./package.json
56 | 
57 | # Copy built application from builder stage
58 | COPY --from=builder /app/dist ./dist
59 | 
60 | # Set default environment for production
61 | ENV NODE_ENV=production
62 | ENV LOG_LEVEL=INFO
63 | # Default to stdio transport, can be overridden at runtime
64 | ENV MCP_TRANSPORT=stdio
65 | 
66 | # Create logs directory with proper permissions
67 | RUN mkdir -p logs && \
68 |     chown -R nodejs:nodejs /app
69 | 
70 | # Switch to non-root user
71 | USER nodejs
72 | 
73 | # Expose port for HTTP transport (ignored when using stdio)
74 | EXPOSE 3000
75 | 
76 | # Health check for HTTP mode (no-op for stdio mode)
77 | # Uses node's built-in http module for more secure health checking
78 | HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
79 |   CMD if [ "$MCP_TRANSPORT" = "http" ]; then \
80 |         node -e "require('http').get('http://127.0.0.1:3000/health', (res) => { \
81 |           if (res.statusCode === 200) { process.exit(0); } else { process.exit(1); } \
82 |         }).on('error', () => { process.exit(1); });" || exit 1; \
83 |       else \
84 |         exit 0; \
85 |       fi
86 | 
87 | # Start the server with optimized flags
88 | CMD ["node", "--experimental-specifier-resolution=node", "--max-old-space-size=512", "dist/index.js"] 
```

--------------------------------------------------------------------------------
/docs/architecture/decisions/0008-use-environment-variables-for-configuration.md:
--------------------------------------------------------------------------------

```markdown
 1 | # 8. Use environment variables for configuration
 2 | 
 3 | Date: 2025-06-13
 4 | 
 5 | ## Status
 6 | 
 7 | Accepted
 8 | 
 9 | ## Context
10 | 
11 | The SonarQube MCP server needs to be configured with various settings including:
12 | 
13 | - SonarQube server URL
14 | - Authentication credentials (token or username/password)
15 | - Organization key (for SonarCloud)
16 | - Log file path
17 | - Other operational settings
18 | 
19 | These configuration values need to be:
20 | 
21 | - Easily changeable without modifying code
22 | - Secure (especially credentials)
23 | - Container-friendly for modern deployment scenarios
24 | - Simple to manage across different environments (development, staging, production)
25 | 
26 | ## Decision
27 | 
28 | We will use environment variables exclusively for all configuration settings. Key settings will include:
29 | 
30 | - `SONARQUBE_URL`: The base URL of the SonarQube server
31 | - `SONARQUBE_TOKEN`: Authentication token (preferred over username/password)
32 | - `SONARQUBE_USERNAME` and `SONARQUBE_PASSWORD`: Alternative authentication method
33 | - `SONARQUBE_ORGANIZATION`: Organization key for SonarCloud
34 | - `MCP_LOG_FILE`: Path for log file output
35 | 
36 | No configuration values will be hard-coded in the source code. Default values may be provided where appropriate, but all settings must be overridable via environment variables.
37 | 
38 | ## Consequences
39 | 
40 | ### Positive
41 | 
42 | - **Security**: Credentials are kept out of the codebase and can be managed through secure environment variable management systems
43 | - **Container-friendly**: Environment variables are the standard way to configure containerized applications
44 | - **Simplicity**: No need for configuration file parsing or management
45 | - **Flexibility**: Easy to change settings without rebuilding or modifying the application
46 | - **12-Factor App compliance**: Follows the third factor of the twelve-factor app methodology
47 | - **Platform agnostic**: Works consistently across different operating systems and deployment platforms
48 | 
49 | ### Negative
50 | 
51 | - **Limited structure**: Environment variables are flat key-value pairs, making complex nested configuration more challenging
52 | - **Type safety**: All environment variables are strings, requiring parsing and validation
53 | - **Discovery**: Users need documentation to know which environment variables are available
54 | - **No comments**: Unlike configuration files, environment variables cannot include inline documentation
55 | 
56 | ### Mitigation
57 | 
58 | - Provide comprehensive documentation of all environment variables
59 | - Implement robust validation and helpful error messages for missing or invalid configuration
60 | - Consider supporting a `.env` file for local development convenience
61 | - Log configuration values (excluding secrets) at startup for debugging
62 | 
```

--------------------------------------------------------------------------------
/src/types/hotspots.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import type { PaginationParams, SeverityLevel } from './common.js';
  2 | 
  3 | /**
  4 |  * Interface for hotspot search parameters
  5 |  */
  6 | export interface HotspotSearchParams extends PaginationParams {
  7 |   projectKey?: string;
  8 |   branch?: string;
  9 |   pullRequest?: string;
 10 |   status?: 'TO_REVIEW' | 'REVIEWED';
 11 |   resolution?: 'FIXED' | 'SAFE';
 12 |   files?: string[];
 13 |   assignedToMe?: boolean;
 14 |   sinceLeakPeriod?: boolean;
 15 |   inNewCodePeriod?: boolean;
 16 | }
 17 | 
 18 | /**
 19 |  * Interface for security hotspot
 20 |  */
 21 | export interface SonarQubeHotspot {
 22 |   key: string;
 23 |   component: string;
 24 |   project: string;
 25 |   securityCategory: string;
 26 |   vulnerabilityProbability: SeverityLevel;
 27 |   status: 'TO_REVIEW' | 'REVIEWED';
 28 |   resolution?: 'FIXED' | 'SAFE';
 29 |   line: number;
 30 |   message: string;
 31 |   assignee?: string;
 32 |   author?: string;
 33 |   creationDate: string;
 34 |   updateDate: string;
 35 |   textRange?: {
 36 |     startLine: number;
 37 |     endLine: number;
 38 |     startOffset: number;
 39 |     endOffset: number;
 40 |   };
 41 |   flows?: Array<{
 42 |     locations: Array<{
 43 |       component: string;
 44 |       textRange: {
 45 |         startLine: number;
 46 |         endLine: number;
 47 |         startOffset: number;
 48 |         endOffset: number;
 49 |       };
 50 |       msg: string;
 51 |     }>;
 52 |   }>;
 53 |   ruleKey?: string;
 54 | }
 55 | 
 56 | /**
 57 |  * Interface for hotspot search result
 58 |  */
 59 | export interface SonarQubeHotspotSearchResult {
 60 |   hotspots: SonarQubeHotspot[];
 61 |   components:
 62 |     | Array<{
 63 |         key: string;
 64 |         qualifier: string;
 65 |         name: string;
 66 |         longName: string | undefined;
 67 |         path: string | undefined;
 68 |       }>
 69 |     | undefined;
 70 |   paging: {
 71 |     pageIndex: number;
 72 |     pageSize: number;
 73 |     total: number;
 74 |   };
 75 | }
 76 | 
 77 | /**
 78 |  * Interface for hotspot details
 79 |  */
 80 | export interface SonarQubeHotspotDetails extends SonarQubeHotspot {
 81 |   rule: {
 82 |     key: string;
 83 |     name: string;
 84 |     securityCategory: string;
 85 |     vulnerabilityProbability: SeverityLevel;
 86 |   };
 87 |   changelog:
 88 |     | Array<{
 89 |         user: string;
 90 |         userName: string | undefined;
 91 |         creationDate: string;
 92 |         diffs: Array<{
 93 |           key: string;
 94 |           oldValue: string | undefined;
 95 |           newValue: string | undefined;
 96 |         }>;
 97 |       }>
 98 |     | undefined;
 99 |   comment:
100 |     | Array<{
101 |         key: string;
102 |         login: string;
103 |         htmlText: string;
104 |         markdown: string | undefined;
105 |         createdAt: string;
106 |       }>
107 |     | undefined;
108 |   users:
109 |     | Array<{
110 |         login: string;
111 |         name: string;
112 |         active: boolean;
113 |       }>
114 |     | undefined;
115 | }
116 | 
117 | /**
118 |  * Interface for hotspot status update parameters
119 |  */
120 | export interface HotspotStatusUpdateParams {
121 |   hotspot: string;
122 |   status: 'TO_REVIEW' | 'REVIEWED';
123 |   resolution?: 'FIXED' | 'SAFE';
124 |   comment?: string;
125 | }
126 | 
```

--------------------------------------------------------------------------------
/docs/architecture/decisions/0005-domain-driven-design-of-sonarqube-modules.md:
--------------------------------------------------------------------------------

```markdown
 1 | # 5. Domain-driven design of SonarQube modules
 2 | 
 3 | Date: 2025-06-13
 4 | 
 5 | ## Status
 6 | 
 7 | Accepted
 8 | 
 9 | ## Context
10 | 
11 | The SonarQube API surface is extensive, covering various aspects of code quality management including projects, issues, metrics, measures, quality gates, security hotspots, and system administration. Managing all these API endpoints in a single monolithic client class would lead to:
12 | 
13 | - A large, difficult-to-maintain codebase
14 | - Unclear separation of concerns
15 | - Difficulty in finding and understanding specific functionality
16 | - Challenges in testing individual API areas
17 | - Risk of naming conflicts and confusion between similar operations
18 | 
19 | ## Decision
20 | 
21 | We will organize the SonarQube client functionality using a domain-driven design approach, where each major API area is encapsulated in its own domain class:
22 | 
23 | - `ProjectsDomain`: Handles project listing and management
24 | - `IssuesDomain`: Manages code issues and their lifecycle (search, comment, assign, resolve, etc.)
25 | - `MetricsDomain`: Provides access to available metrics
26 | - `MeasuresDomain`: Retrieves component measures and their history
27 | - `QualityGatesDomain`: Manages quality gates and their status
28 | - `HotspotsDomain`: Handles security hotspots
29 | - `SourceDomain`: Provides access to source code and SCM information
30 | - `SystemDomain`: Handles system health and status checks
31 | 
32 | Each domain class:
33 | 
34 | - Encapsulates all API methods related to its specific area
35 | - Has its own dedicated test file
36 | - Can evolve independently without affecting other domains
37 | - Provides a clear, focused interface for its functionality
38 | 
39 | The main `SonarQubeClient` class acts as a facade, instantiating and providing access to all domain classes through properties.
40 | 
41 | ## Consequences
42 | 
43 | ### Positive
44 | 
45 | - **Clear separation of concerns**: Each domain has a well-defined responsibility
46 | - **Improved maintainability**: Changes to one API area don't affect others
47 | - **Better discoverability**: Developers can easily find functionality by domain
48 | - **Focused testing**: Each domain can be tested in isolation
49 | - **Scalability**: New domains can be added without modifying existing code
50 | - **Type safety**: Domain-specific types and interfaces can be co-located with their domain
51 | 
52 | ### Negative
53 | 
54 | - **Initial complexity**: More files and classes to manage
55 | - **Potential duplication**: Some common functionality might be duplicated across domains
56 | - **Navigation overhead**: Developers need to know which domain contains specific functionality
57 | 
58 | ### Mitigation
59 | 
60 | - Use clear, consistent naming conventions for domains
61 | - Document the responsibility of each domain in its class documentation
62 | - Share common functionality through utility functions or base classes where appropriate
63 | 
```

--------------------------------------------------------------------------------
/docs/architecture/decisions/0003-adopt-model-context-protocol-for-sonarqube-integration.md:
--------------------------------------------------------------------------------

```markdown
 1 | # 3. Adopt Model Context Protocol for SonarQube Integration
 2 | 
 3 | Date: 2025-06-13
 4 | 
 5 | ## Status
 6 | 
 7 | Accepted
 8 | 
 9 | ## Context
10 | 
11 | We need to integrate SonarQube's code quality and security analysis capabilities with AI assistants like Claude. This integration should allow AI clients to programmatically access SonarQube data and perform operations through a standardized interface.
12 | 
13 | The Model Context Protocol (MCP) is emerging as a standard protocol for AI assistants to interact with external tools and data sources. It provides:
14 | 
15 | - A standardized way to expose tools to AI assistants
16 | - Built-in support for bidirectional communication
17 | - A growing ecosystem of compatible AI clients
18 | - SDKs that simplify server implementation
19 | 
20 | ## Decision
21 | 
22 | We will adopt the Model Context Protocol (MCP) framework as the foundation for our SonarQube integration. Specifically:
23 | 
24 | 1. The application will be designed as an MCP server using the `@modelcontextprotocol/sdk` package
25 | 2. The MCP server (implemented in `index.ts`) will register a comprehensive suite of SonarQube tools
26 | 3. We will use the Stdio transport for communication between the MCP server and AI clients
27 | 4. Each SonarQube operation will be exposed as a distinct MCP tool with proper input validation and error handling
28 | 
29 | ## Consequences
30 | 
31 | ### Positive Consequences
32 | 
33 | - **Standardization**: Using MCP ensures compatibility with any AI client that supports the protocol, not just Claude
34 | - **Simplified Integration**: AI clients can discover and use SonarQube tools without custom integration code
35 | - **Tool Discovery**: MCP's tool registration system allows AI clients to automatically discover available SonarQube operations
36 | - **Type Safety**: The MCP SDK provides TypeScript support for defining tool inputs and outputs
37 | - **Future-Proof**: As MCP evolves and gains adoption, our integration will benefit from ecosystem improvements
38 | 
39 | ### Negative Consequences
40 | 
41 | - **Protocol Dependency**: We're tied to the MCP specification and any breaking changes it might introduce
42 | - **Limited Transport Options**: While Stdio transport works well for local integrations, it may not be suitable for all deployment scenarios
43 | - **Learning Curve**: Developers need to understand MCP concepts and patterns to maintain the integration
44 | - **Debugging Complexity**: Troubleshooting issues requires understanding both SonarQube API and MCP protocol layers
45 | 
46 | ### Mitigation Strategies
47 | 
48 | - Abstract the transport layer to allow future support for HTTP or WebSocket transports if needed
49 | - Maintain comprehensive documentation for MCP-specific implementation details
50 | - Implement robust error handling and logging to aid in debugging MCP communication issues
51 | - Monitor MCP protocol evolution and plan for version upgrades
52 | 
```

--------------------------------------------------------------------------------
/src/domains/quality-gates.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import type {
 2 |   SonarQubeQualityGatesResult,
 3 |   SonarQubeQualityGate,
 4 |   SonarQubeQualityGateStatus,
 5 |   ProjectQualityGateParams,
 6 | } from '../types/index.js';
 7 | import { BaseDomain } from './base.js';
 8 | 
 9 | /**
10 |  * Domain module for quality gates operations
11 |  */
12 | export class QualityGatesDomain extends BaseDomain {
13 |   /**
14 |    * Lists all quality gates
15 |    * @returns Promise with the list of quality gates
16 |    */
17 |   async listQualityGates(): Promise<SonarQubeQualityGatesResult> {
18 |     const response = await this.webApiClient.qualityGates.list();
19 |     return {
20 |       qualitygates: response.qualitygates.map((gate) => ({
21 |         id: gate.id,
22 |         name: gate.name,
23 |         isDefault: gate.isDefault,
24 |         isBuiltIn: gate.isBuiltIn,
25 |         conditions: gate.conditions?.map((condition) => ({
26 |           id: condition.id,
27 |           metric: condition.metric,
28 |           op: condition.operator ?? 'GT', // Default operator
29 |           error: condition.error ?? '',
30 |         })),
31 |       })),
32 |       default: response.default ?? '',
33 |       actions: (response as { actions?: { create?: boolean } }).actions
34 |         ? {
35 |             create: (response as { actions?: { create?: boolean } }).actions?.create,
36 |           }
37 |         : undefined, // actions might not be in the type definition
38 |     };
39 |   }
40 | 
41 |   /**
42 |    * Gets a specific quality gate by ID
43 |    * @param id The quality gate ID
44 |    * @returns Promise with the quality gate details
45 |    */
46 |   async getQualityGate(id: string): Promise<SonarQubeQualityGate> {
47 |     const response = await this.webApiClient.qualityGates.get({ id });
48 |     return {
49 |       id: response.id,
50 |       name: response.name,
51 |       isDefault: response.isDefault,
52 |       isBuiltIn: response.isBuiltIn,
53 |       conditions: response.conditions?.map((condition) => ({
54 |         id: condition.id,
55 |         metric: condition.metric,
56 |         op:
57 |           (condition as { op?: string; operator?: string }).op ??
58 |           (condition as { op?: string; operator?: string }).operator ??
59 |           'GT',
60 |         error: condition.error ?? '',
61 |       })),
62 |     };
63 |   }
64 | 
65 |   /**
66 |    * Gets the quality gate status for a project
67 |    * @param params Parameters including project key and optional branch/PR
68 |    * @returns Promise with the quality gate status
69 |    */
70 |   async getProjectQualityGateStatus(
71 |     params: ProjectQualityGateParams
72 |   ): Promise<SonarQubeQualityGateStatus> {
73 |     const { projectKey, branch, pullRequest } = params;
74 | 
75 |     const request: {
76 |       projectKey: string;
77 |       branch?: string;
78 |       pullRequest?: string;
79 |     } = {
80 |       projectKey,
81 |       ...(branch && { branch }),
82 |       ...(pullRequest && { pullRequest }),
83 |     };
84 | 
85 |     const response = await this.webApiClient.qualityGates.getProjectStatus(request);
86 | 
87 |     return response as unknown as SonarQubeQualityGateStatus;
88 |   }
89 | }
90 | 
```

--------------------------------------------------------------------------------
/src/handlers/projects.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import type { PaginationParams, ISonarQubeClient, SonarQubeProject } from '../types/index.js';
 2 | import { getDefaultClient } from '../utils/client-factory.js';
 3 | import { nullToUndefined } from '../utils/transforms.js';
 4 | import { createLogger } from '../utils/logger.js';
 5 | import { withErrorHandling } from '../errors.js';
 6 | import { withMCPErrorHandling } from '../utils/error-handler.js';
 7 | import { createStructuredResponse } from '../utils/structured-response.js';
 8 | 
 9 | const logger = createLogger('handlers/projects');
10 | 
11 | /**
12 |  * Fetches and returns a list of all SonarQube projects
13 |  * @param params Parameters for listing projects, including pagination and organization
14 |  * @param client Optional SonarQube client instance
15 |  * @returns A response containing the list of projects with their details
16 |  * @throws Error if no authentication environment variables are set (SONARQUBE_TOKEN, SONARQUBE_USERNAME/PASSWORD, or SONARQUBE_PASSCODE)
17 |  */
18 | export const handleSonarQubeProjects = withMCPErrorHandling(
19 |   async (
20 |     params: {
21 |       page?: number | null;
22 |       page_size?: number | null;
23 |     },
24 |     client: ISonarQubeClient = getDefaultClient()
25 |   ) => {
26 |     logger.debug('Handling SonarQube projects request', params);
27 | 
28 |     const projectsParams: PaginationParams = {
29 |       page: nullToUndefined(params.page),
30 |       pageSize: nullToUndefined(params.page_size),
31 |     };
32 | 
33 |     let result;
34 |     try {
35 |       result = await withErrorHandling('List SonarQube projects', () =>
36 |         client.listProjects(projectsParams)
37 |       );
38 |     } catch (error: unknown) {
39 |       // Check if this is an authorization error and provide helpful guidance
40 |       if (
41 |         error instanceof Error &&
42 |         (error.message.includes('Insufficient privileges') ||
43 |           error.message.includes('requires authentication') ||
44 |           error.message.includes('403') ||
45 |           error.message.includes('Administer System'))
46 |       ) {
47 |         throw new Error(
48 |           `${error.message}\n\nNote: The 'projects' tool requires admin permissions. ` +
49 |             `Non-admin users can use the 'components' tool instead:\n` +
50 |             `- To list all accessible projects: components with qualifiers: ['TRK']\n` +
51 |             `- To search projects: components with query: 'search-term', qualifiers: ['TRK']`
52 |         );
53 |       }
54 |       throw error;
55 |     }
56 |     logger.info('Successfully retrieved projects', { count: result.projects.length });
57 |     return createStructuredResponse({
58 |       projects: result.projects.map((project: SonarQubeProject) => ({
59 |         key: project.key,
60 |         name: project.name,
61 |         qualifier: project.qualifier,
62 |         visibility: project.visibility,
63 |         lastAnalysisDate: project.lastAnalysisDate,
64 |         revision: project.revision,
65 |         managed: project.managed,
66 |       })),
67 |       paging: result.paging,
68 |     });
69 |   }
70 | );
71 | 
```

--------------------------------------------------------------------------------
/src/__tests__/domains/components-domain.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { describe, it, expect, beforeEach, afterEach } from 'vitest';
 2 | import { ComponentsDomain } from '../../domains/components.js';
 3 | import { SonarQubeClient as WebApiClient } from 'sonarqube-web-api-client';
 4 | import { resetDefaultClient } from '../../utils/client-factory.js';
 5 | // Mock environment variables
 6 | process.env.SONARQUBE_TOKEN = 'test-token';
 7 | process.env.SONARQUBE_URL = 'http://localhost:9000';
 8 | process.env.SONARQUBE_ORGANIZATION = 'test-org';
 9 | describe('ComponentsDomain', () => {
10 |   let domain: ComponentsDomain;
11 |   const baseUrl = 'http://localhost:9000';
12 |   const token = 'test-token';
13 |   const organization = 'test-org';
14 |   beforeEach(() => {
15 |     resetDefaultClient();
16 |     const webApiClient = WebApiClient.withToken(baseUrl, token, { organization });
17 |     domain = new ComponentsDomain(webApiClient, organization);
18 |   });
19 |   afterEach(() => {
20 |     resetDefaultClient();
21 |   });
22 |   it('should be instantiated correctly', () => {
23 |     expect(domain).toBeDefined();
24 |     expect(domain).toBeInstanceOf(ComponentsDomain);
25 |   });
26 |   it('should have searchComponents method', () => {
27 |     expect(domain.searchComponents).toBeDefined();
28 |     expect(typeof domain.searchComponents).toBe('function');
29 |   });
30 |   it('should have getComponentTree method', () => {
31 |     expect(domain.getComponentTree).toBeDefined();
32 |     expect(typeof domain.getComponentTree).toBe('function');
33 |   });
34 |   it('should have showComponent method', () => {
35 |     expect(domain.showComponent).toBeDefined();
36 |     expect(typeof domain.showComponent).toBe('function');
37 |   });
38 |   describe('searchComponents', () => {
39 |     it('should accept search parameters', () => {
40 |       // Just verify the method exists and is callable
41 |       const method = domain.searchComponents;
42 |       expect(method).toBeDefined();
43 |       expect(typeof method).toBe('function');
44 |       // Don't actually call it as it would make HTTP requests
45 |       expect(method.length).toBeLessThanOrEqual(1); // Expects 0 or 1 parameter
46 |     });
47 |   });
48 |   describe('getComponentTree', () => {
49 |     it('should accept tree parameters', () => {
50 |       // Just verify the method exists and is callable
51 |       const method = domain.getComponentTree;
52 |       expect(method).toBeDefined();
53 |       expect(typeof method).toBe('function');
54 |       // Don't actually call it as it would make HTTP requests
55 |       expect(method.length).toBeLessThanOrEqual(1); // Expects 1 parameter
56 |     });
57 |   });
58 |   describe('showComponent', () => {
59 |     it('should accept component key and optional parameters', () => {
60 |       // Just verify the method exists and is callable
61 |       const method = domain.showComponent;
62 |       expect(method).toBeDefined();
63 |       expect(typeof method).toBe('function');
64 |       // Don't actually call it as it would make HTTP requests
65 |       expect(method.length).toBeLessThanOrEqual(3); // Expects up to 3 parameters
66 |     });
67 |   });
68 | });
69 | 
```

--------------------------------------------------------------------------------
/src/__tests__/zod-boolean-transform.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { describe, it, expect } from 'vitest';
 2 | import { z } from 'zod';
 3 | describe('Zod Boolean Transform Coverage', () => {
 4 |   // This explicitly tests the transform used in index.ts for boolean parameters
 5 |   // We're covering lines 705-731 in index.ts
 6 |   describe('resolved parameter transform', () => {
 7 |     // Recreate the exact schema used in index.ts
 8 |     const resolvedSchema = z
 9 |       .union([z.boolean(), z.string().transform((val: any) => val === 'true')])
10 |       .nullable()
11 |       .optional();
12 |     it('should handle boolean true value', () => {
13 |       expect(resolvedSchema.parse(true)).toBe(true);
14 |     });
15 |     it('should handle boolean false value', () => {
16 |       expect(resolvedSchema.parse(false)).toBe(false);
17 |     });
18 |     it('should transform string "true" to boolean true', () => {
19 |       expect(resolvedSchema.parse('true')).toBe(true);
20 |     });
21 |     it('should transform string "false" to boolean false', () => {
22 |       expect(resolvedSchema.parse('false')).toBe(false);
23 |     });
24 |     it('should pass null and undefined through', () => {
25 |       expect(resolvedSchema.parse(null)).toBeNull();
26 |       expect(resolvedSchema.parse(undefined)).toBeUndefined();
27 |     });
28 |   });
29 |   describe('on_component_only parameter transform', () => {
30 |     // Recreate the exact schema used in index.ts
31 |     const onComponentOnlySchema = z
32 |       .union([z.boolean(), z.string().transform((val: any) => val === 'true')])
33 |       .nullable()
34 |       .optional();
35 |     it('should transform valid values correctly', () => {
36 |       expect(onComponentOnlySchema.parse(true)).toBe(true);
37 |       expect(onComponentOnlySchema.parse('true')).toBe(true);
38 |       expect(onComponentOnlySchema.parse(false)).toBe(false);
39 |       expect(onComponentOnlySchema.parse('false')).toBe(false);
40 |     });
41 |   });
42 |   describe('since_leak_period parameter transform', () => {
43 |     // Recreate the exact schema used in index.ts
44 |     const sinceLeakPeriodSchema = z
45 |       .union([z.boolean(), z.string().transform((val: any) => val === 'true')])
46 |       .nullable()
47 |       .optional();
48 |     it('should transform valid values correctly', () => {
49 |       expect(sinceLeakPeriodSchema.parse(true)).toBe(true);
50 |       expect(sinceLeakPeriodSchema.parse('true')).toBe(true);
51 |       expect(sinceLeakPeriodSchema.parse(false)).toBe(false);
52 |       expect(sinceLeakPeriodSchema.parse('false')).toBe(false);
53 |     });
54 |   });
55 |   describe('in_new_code_period parameter transform', () => {
56 |     // Recreate the exact schema used in index.ts
57 |     const inNewCodePeriodSchema = z
58 |       .union([z.boolean(), z.string().transform((val: any) => val === 'true')])
59 |       .nullable()
60 |       .optional();
61 |     it('should transform valid values correctly', () => {
62 |       expect(inNewCodePeriodSchema.parse(true)).toBe(true);
63 |       expect(inNewCodePeriodSchema.parse('true')).toBe(true);
64 |       expect(inNewCodePeriodSchema.parse(false)).toBe(false);
65 |       expect(inNewCodePeriodSchema.parse('false')).toBe(false);
66 |     });
67 |   });
68 | });
69 | 
```

--------------------------------------------------------------------------------
/.github/workflows/reusable-security.yml:
--------------------------------------------------------------------------------

```yaml
 1 | # =============================================================================
 2 | # REUSABLE WORKFLOW: Security Scanning Suite
 3 | # PURPOSE: Run security scans (audit, OSV) and generate SBOM
 4 | # USAGE: Called by PR and main workflows for security validation
 5 | # OUTPUTS: Security findings uploaded to GitHub Security tab, SBOM artifact
 6 | # NOTE: CodeQL has its own dedicated workflow (codeql.yml) for better integration
 7 | # =============================================================================
 8 | 
 9 | name: Reusable Security
10 | 
11 | on:
12 |   workflow_call:
13 |     inputs:
14 |       node-version:
15 |         description: 'Node.js version (should match package.json engines.node)'
16 |         type: string
17 |         default: '22' # UPDATE: When upgrading Node.js
18 |       pnpm-version:
19 |         description: 'pnpm version (should match package.json packageManager)'
20 |         type: string
21 |         default: '10.17.0' # UPDATE: When upgrading pnpm
22 |       run-osv-scan:
23 |         description: 'Run OSV scanner for dependency vulnerabilities'
24 |         type: boolean
25 |         default: true
26 | 
27 | # SECURITY: Required permissions for security scanning
28 | permissions:
29 |   actions: read # Read workflow metadata
30 |   contents: read # Read source code
31 |   security-events: write # Upload security findings
32 | 
33 | # EXAMPLE USAGE:
34 | # jobs:
35 | #   security:
36 | #     uses: ./.github/workflows/reusable-security.yml
37 | #     with:
38 | #       run-osv-scan: true
39 | 
40 | jobs:
41 |   audit:
42 |     runs-on: ubuntu-latest
43 |     steps:
44 |       - name: Checkout code
45 |         uses: actions/checkout@v4
46 |         with:
47 |           fetch-depth: 0 # Full history for accurate analysis
48 | 
49 |       # =============================================================================
50 |       # ENVIRONMENT SETUP
51 |       # Required for SBOM generation and dependency analysis
52 |       # =============================================================================
53 | 
54 |       - name: Install pnpm
55 |         uses: pnpm/action-setup@v4
56 |         with:
57 |           version: ${{ inputs.pnpm-version }}
58 |           run_install: false
59 |           standalone: true
60 | 
61 |       - name: Setup Node.js
62 |         uses: actions/setup-node@v4
63 |         with:
64 |           node-version: ${{ inputs.node-version }}
65 |           cache: pnpm # Cache dependencies for speed
66 | 
67 |       - name: Install dependencies
68 |         # Dependencies needed for accurate SBOM generation
69 |         run: pnpm install --frozen-lockfile
70 | 
71 |       - name: Security audit
72 |         # Check for known vulnerabilities in dependencies
73 |         # FAILS IF: Critical vulnerabilities found
74 |         # To fix: Run 'pnpm update' or add overrides in package.json
75 |         run: pnpm audit --audit-level critical
76 | 
77 |   osv-scan:
78 |     if: inputs.run-osv-scan
79 |     uses: google/osv-scanner-action/.github/workflows/[email protected]
80 |     with:
81 |       # Scan entire project including all manifests (package.json, pnpm-lock.yaml)
82 |       scan-args: |-
83 |         ./
84 |     permissions:
85 |       security-events: write # Required to upload findings to Security tab
86 |       actions: read
87 |       contents: read
88 | 
```

--------------------------------------------------------------------------------
/COMPATIBILITY.md:
--------------------------------------------------------------------------------

```markdown
 1 | # MCP Protocol Compatibility
 2 | 
 3 | This document outlines the Model Context Protocol (MCP) version compatibility for the SonarQube MCP Server.
 4 | 
 5 | ## Protocol Version Support
 6 | 
 7 | The SonarQube MCP Server supports the following MCP protocol versions:
 8 | 
 9 | | Protocol Version | Status                 | SDK Version Required |
10 | | ---------------- | ---------------------- | -------------------- |
11 | | 2025-06-18       | ✅ Supported           | 1.13.0+              |
12 | | 2025-03-26       | ✅ Supported (Default) | 1.13.0+              |
13 | | 2024-11-05       | ✅ Supported           | 1.13.0+              |
14 | | 2024-10-07       | ✅ Supported           | 1.13.0+              |
15 | 
16 | ### Version Negotiation
17 | 
18 | - The server uses `@modelcontextprotocol/sdk` version `1.13.0` or higher
19 | - Protocol version is automatically negotiated during the client-server handshake
20 | - The server will use the highest protocol version supported by both client and server
21 | - If no common version is found, the connection will fail with a version mismatch error
22 | 
23 | ### Current SDK Version
24 | 
25 | The project currently uses `@modelcontextprotocol/sdk` version `1.13.0`, which supports all protocol versions listed above.
26 | 
27 | ## Feature Compatibility
28 | 
29 | ### Protocol Version 2025-06-18
30 | 
31 | - Latest protocol version
32 | - Full support for all server capabilities
33 | - Enhanced error handling
34 | 
35 | ### Protocol Version 2025-03-26
36 | 
37 | - Default negotiated version for most clients
38 | - Full support for elicitation capabilities (required for our implementation)
39 | - All standard MCP features
40 | 
41 | ### Protocol Versions 2024-11-05 and 2024-10-07
42 | 
43 | - Basic MCP functionality
44 | - May not support all advanced features
45 | - Provided for backward compatibility
46 | 
47 | ## Client Compatibility
48 | 
49 | This server is compatible with any MCP client that supports at least one of the protocol versions listed above. Common clients include:
50 | 
51 | - Claude Desktop App
52 | - Continue.dev
53 | - Other MCP-compliant clients
54 | 
55 | ## SDK Update Process
56 | 
57 | When updating the MCP SDK:
58 | 
59 | 1. Check the [MCP SDK releases](https://github.com/modelcontextprotocol/sdk/releases) for new versions
60 | 2. Review the changelog for breaking changes
61 | 3. Update the dependency in `package.json`
62 | 4. Run `pnpm install` to update the lock file
63 | 5. Test all server capabilities with multiple protocol versions
64 | 6. Update this compatibility document if new protocol versions are supported
65 | 7. Run the full test suite: `pnpm run precommit`
66 | 
67 | ## Monitoring Protocol Usage
68 | 
69 | To see which protocol version is being used during runtime, run the server with debug logging enabled:
70 | 
71 | ```bash
72 | DEBUG=* pnpm start
73 | ```
74 | 
75 | The server will log the negotiated protocol version during client connection.
76 | 
77 | ## Deprecated Features
78 | 
79 | As of MCP protocol version 2025-03-26:
80 | 
81 | - JSON-RPC batch support has been removed
82 | - Our server uses the SDK's built-in transport layer, which handles this automatically
83 | 
84 | ## References
85 | 
86 | - [MCP Specification](https://modelcontextprotocol.io)
87 | - [MCP SDK Documentation](https://github.com/modelcontextprotocol/sdk)
88 | - [Protocol Version History](https://modelcontextprotocol.io/docs/changelog)
89 | 
```

--------------------------------------------------------------------------------
/src/transports/factory.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { ITransport, ITransportConfig, IHttpTransportConfig } from './base.js';
 2 | import { StdioTransport } from './stdio.js';
 3 | import { HttpTransport } from './http.js';
 4 | import { createLogger } from '../utils/logger.js';
 5 | 
 6 | const logger = createLogger('transport-factory');
 7 | 
 8 | /**
 9 |  * Factory for creating transport instances.
10 |  * This factory creates transport instances for MCP communication based on configuration.
11 |  */
12 | export class TransportFactory {
13 |   /**
14 |    * Create a transport instance based on the provided configuration.
15 |    *
16 |    * @param config Transport configuration
17 |    * @returns A transport instance
18 |    * @throws Error if the transport type is not supported
19 |    */
20 |   static create(config: ITransportConfig): ITransport {
21 |     logger.debug(`Creating transport of type: ${config.type}`);
22 | 
23 |     switch (config.type) {
24 |       case 'stdio':
25 |         return new StdioTransport();
26 | 
27 |       case 'http': {
28 |         const httpConfig = config as IHttpTransportConfig;
29 |         return new HttpTransport(httpConfig.options);
30 |       }
31 | 
32 |       default:
33 |         throw new Error(`Unsupported transport type: ${config.type as string}`);
34 |     }
35 |   }
36 | 
37 |   /**
38 |    * Create a transport based on environment variables.
39 |    * Supports both STDIO and HTTP transports based on MCP_TRANSPORT_TYPE.
40 |    *
41 |    * @returns A transport instance
42 |    */
43 |   static createFromEnvironment(): ITransport {
44 |     const transportType = process.env.MCP_TRANSPORT_TYPE?.toLowerCase() || 'stdio';
45 | 
46 |     logger.info(`Creating transport from environment: ${transportType}`);
47 | 
48 |     if (transportType === 'http') {
49 |       // Parse HTTP configuration from environment
50 |       const options: IHttpTransportConfig['options'] = {};
51 | 
52 |       if (process.env.MCP_HTTP_PORT) {
53 |         options.port = Number.parseInt(process.env.MCP_HTTP_PORT, 10);
54 |       }
55 |       if (process.env.MCP_HTTP_ALLOWED_HOSTS) {
56 |         options.allowedHosts = process.env.MCP_HTTP_ALLOWED_HOSTS.split(',').map((h) => h.trim());
57 |       }
58 |       if (process.env.MCP_HTTP_ALLOWED_ORIGINS) {
59 |         options.allowedOrigins = process.env.MCP_HTTP_ALLOWED_ORIGINS.split(',').map((o) =>
60 |           o.trim()
61 |         );
62 |       }
63 |       if (process.env.MCP_HTTP_SESSION_TIMEOUT) {
64 |         options.sessionTimeout = Number.parseInt(process.env.MCP_HTTP_SESSION_TIMEOUT, 10);
65 |       }
66 |       if (process.env.MCP_HTTP_ENABLE_DNS_REBINDING_PROTECTION === 'true') {
67 |         options.enableDnsRebindingProtection = true;
68 |       }
69 | 
70 |       const config: IHttpTransportConfig = {
71 |         type: 'http',
72 |         options,
73 |       };
74 | 
75 |       // Log configuration (without sensitive data)
76 |       logger.debug('HTTP transport configuration:', {
77 |         port: config.options?.port,
78 |         allowedHosts: config.options?.allowedHosts,
79 |         allowedOrigins: config.options?.allowedOrigins,
80 |         sessionTimeout: config.options?.sessionTimeout,
81 |         enableDnsRebindingProtection: config.options?.enableDnsRebindingProtection,
82 |       });
83 | 
84 |       return TransportFactory.create(config);
85 |     }
86 | 
87 |     // Default to STDIO transport
88 |     return new StdioTransport();
89 |   }
90 | }
91 | 
```

--------------------------------------------------------------------------------
/src/utils/transforms.ts:
--------------------------------------------------------------------------------

```typescript
 1 | /**
 2 |  * Helper function to convert null to undefined
 3 |  * @param value Any value that might be null
 4 |  * @returns The original value or undefined if null
 5 |  */
 6 | export function nullToUndefined<T>(value: T | null | undefined): T | undefined {
 7 |   return value === null ? undefined : value;
 8 | }
 9 | 
10 | /**
11 |  * Helper function to transform string to number or null
12 |  * @param val String value to transform
13 |  * @returns Number or null if conversion fails
14 |  */
15 | export function stringToNumberTransform(val: string | null | undefined): number | null | undefined {
16 |   if (val === null || val === undefined) {
17 |     return val;
18 |   }
19 |   const parsed = Number.parseInt(val, 10);
20 |   return Number.isNaN(parsed) ? null : parsed;
21 | }
22 | 
23 | /**
24 |  * Ensures a value is an array
25 |  * @param value Single value, array, or undefined
26 |  * @returns Array containing the value(s), or empty array if undefined
27 |  */
28 | export function ensureArray<T>(value: T | T[] | undefined): T[] {
29 |   if (value === undefined) return [];
30 |   return Array.isArray(value) ? value : [value];
31 | }
32 | 
33 | /**
34 |  * Ensures a string value is an array of strings
35 |  * Handles comma-separated strings for backward compatibility
36 |  * @param value Single string, array of strings, or undefined
37 |  * @returns Array of strings, or empty array if undefined
38 |  */
39 | export function ensureStringArray(value: string | string[] | undefined): string[] {
40 |   if (value === undefined) return [];
41 |   if (Array.isArray(value)) return value;
42 |   // Check if the string contains commas and split if so
43 |   if (value.includes(',')) return value.split(',').map((s) => s.trim());
44 |   return [value];
45 | }
46 | 
47 | /**
48 |  * Converts a number or string to a string
49 |  * Useful for parameters that can be passed as either type but need to be strings for the API
50 |  * @param value Number, string, null, or undefined
51 |  * @returns String representation of the value, or the original null/undefined
52 |  */
53 | export function numberOrStringToString(
54 |   value: number | string | null | undefined
55 | ): string | null | undefined {
56 |   if (value === null || value === undefined) {
57 |     return value;
58 |   }
59 |   return String(value);
60 | }
61 | 
62 | /**
63 |  * Parses a JSON string array or returns the array as-is
64 |  * Useful for MCP parameters that might be sent as JSON strings
65 |  * @param value Array, JSON string array, null, or undefined
66 |  * @returns Array of strings, or null/undefined
67 |  */
68 | export function parseJsonStringArray(
69 |   value: string[] | string | null | undefined
70 | ): string[] | null | undefined {
71 |   if (value === null || value === undefined) {
72 |     return value;
73 |   }
74 | 
75 |   // If it's already an array, return it
76 |   if (Array.isArray(value)) {
77 |     return value;
78 |   }
79 | 
80 |   // If it's a string, try to parse it as JSON
81 |   if (typeof value === 'string') {
82 |     try {
83 |       const parsed: unknown = JSON.parse(value);
84 |       if (Array.isArray(parsed)) {
85 |         return parsed.map(String);
86 |       }
87 |       // If parsed but not an array, wrap it
88 |       return [String(parsed)];
89 |     } catch {
90 |       // If not valid JSON, treat as single value array
91 |       return [value];
92 |     }
93 |   }
94 | 
95 |   // Shouldn't reach here, but handle edge cases
96 |   return [String(value)];
97 | }
98 | 
```

--------------------------------------------------------------------------------
/docs/architecture/decisions/0014-current-security-model-and-future-oauth2-considerations.md:
--------------------------------------------------------------------------------

```markdown
 1 | # 14. Current Security Model and Future OAuth2 Considerations
 2 | 
 3 | Date: 2025-06-19
 4 | 
 5 | ## Status
 6 | 
 7 | Accepted
 8 | 
 9 | ## Context
10 | 
11 | The SonarQube MCP Server currently uses environment variables for authentication (token, basic auth, or admin passcode), which is appropriate for its design as a single-user local tool. However, the MCP specification positions MCP servers as OAuth 2.1 resource servers, which has implications for future development, especially if HTTP transport is added.
12 | 
13 | The MCP specification states that:
14 | 
15 | - MCP servers should act as OAuth 2.1 resource servers
16 | - HTTP-based connections require OAuth token validation
17 | - RFC8707 resource indicators should be used for token scoping
18 | - Multi-client scenarios need proper authorization mechanisms
19 | 
20 | Our current implementation:
21 | 
22 | - Uses stdio transport (not HTTP)
23 | - Designed for single-user local usage
24 | - Manages authentication via environment variables
25 | - Directly uses SonarQube's authentication mechanisms
26 | 
27 | ## Decision
28 | 
29 | We will maintain the current environment variable-based authentication approach for local single-user scenarios while documenting the limitations and future OAuth2 considerations.
30 | 
31 | Specifically:
32 | 
33 | 1. Continue using environment variables for authentication configuration
34 | 2. Document the current security model clearly in README.md
35 | 3. Document authentication best practices in SECURITY.md
36 | 4. Acknowledge OAuth2 requirements for potential future HTTP transport
37 | 5. Design the codebase to allow future OAuth2 implementation without breaking changes
38 | 
39 | The authentication priority remains:
40 | 
41 | 1. Token authentication (most secure, recommended)
42 | 2. Basic authentication (username/password)
43 | 3. System passcode (for admin scenarios)
44 | 
45 | ## Consequences
46 | 
47 | ### Positive Consequences
48 | 
49 | - **Simple Setup**: Users can quickly configure authentication without complex OAuth flows
50 | - **Appropriate Security**: The security model matches the single-user local tool use case
51 | - **Direct Integration**: Leverages SonarQube's existing authentication mechanisms
52 | - **Backward Compatible**: Future OAuth2 support can be added without breaking existing usage
53 | 
54 | ### Negative Consequences
55 | 
56 | - **Limited to Local Use**: Not suitable for multi-user or hosted scenarios without modifications
57 | - **No Token Validation**: The MCP server trusts the provided credentials without additional validation
58 | - **Future Migration**: Adding OAuth2 support will require significant changes if HTTP transport is implemented
59 | 
60 | ### Security Considerations
61 | 
62 | - Credentials are stored in the MCP client's configuration file (e.g., Claude Desktop config)
63 | - Users must ensure proper file permissions on configuration files
64 | - Token-based authentication is strongly recommended over passwords
65 | - Tokens should be scoped with minimal required permissions
66 | 
67 | ### Future Work
68 | 
69 | If HTTP transport is added:
70 | 
71 | 1. Implement OAuth 2.1 resource server capabilities
72 | 2. Add token validation middleware
73 | 3. Support RFC8707 resource indicators
74 | 4. Implement proper multi-client authorization
75 | 5. Maintain backward compatibility with environment variable auth for local usage
76 | 
```

--------------------------------------------------------------------------------
/scripts/setup.sh:
--------------------------------------------------------------------------------

```bash
  1 | #!/usr/bin/env bash
  2 | set -euo pipefail
  3 | 
  4 | # =============================================================================
  5 | # Project Setup Script
  6 | # Purpose: One-command setup for new developers
  7 | # Usage: ./scripts/setup.sh [project-name]
  8 | # =============================================================================
  9 | 
 10 | PROJECT_NAME="${1:-}"
 11 | CURRENT_DIR="$(pwd)"
 12 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
 13 | PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
 14 | 
 15 | # Colors for output
 16 | RED='\033[0;31m'
 17 | GREEN='\033[0;32m'
 18 | YELLOW='\033[1;33m'
 19 | BLUE='\033[0;34m'
 20 | NC='\033[0m' # No Color
 21 | 
 22 | log() {
 23 |     echo -e "${GREEN}[SETUP]${NC} $1"
 24 | }
 25 | 
 26 | warn() {
 27 |     echo -e "${YELLOW}[WARN]${NC} $1"
 28 | }
 29 | 
 30 | error() {
 31 |     echo -e "${RED}[ERROR]${NC} $1"
 32 |     exit 1
 33 | }
 34 | 
 35 | info() {
 36 |     echo -e "${BLUE}[INFO]${NC} $1"
 37 | }
 38 | 
 39 | # Check if we're in the right directory
 40 | if [[ ! -f "$PROJECT_ROOT/package.json" ]]; then
 41 |     error "Run this script from the project root directory"
 42 | fi
 43 | 
 44 | log "Starting project setup..."
 45 | 
 46 | # 1. Check prerequisites
 47 | log "Checking prerequisites..."
 48 | 
 49 | if ! command -v pnpm &> /dev/null; then
 50 |     if command -v mise &> /dev/null; then
 51 |         log "Installing pnpm via mise..."
 52 |         mise install [email protected]
 53 |     else
 54 |         error "pnpm not found. Please install pnpm 10.17.0 or install mise first."
 55 |     fi
 56 | fi
 57 | 
 58 | if ! command -v node &> /dev/null; then
 59 |     if command -v mise &> /dev/null; then
 60 |         log "Installing Node.js via mise..."
 61 |         mise install node@22
 62 |     else
 63 |         error "Node.js not found. Please install Node.js 22+ or install mise first."
 64 |     fi
 65 | fi
 66 | 
 67 | # 2. Install dependencies
 68 | log "Installing dependencies..."
 69 | pnpm install
 70 | 
 71 | # 3. Rename project if name provided
 72 | if [[ -n "$PROJECT_NAME" ]]; then
 73 |     log "Renaming project to '$PROJECT_NAME'..."
 74 |     
 75 |     # Update package.json
 76 |     pnpm pkg set name="$PROJECT_NAME"
 77 |     
 78 |     # Update README.md title
 79 |     sed -i.bak "s/# Agentic Node + TypeScript Starter/# $PROJECT_NAME/" README.md && rm README.md.bak
 80 |     
 81 |     info "Project renamed to '$PROJECT_NAME'. Update other references manually as needed."
 82 | fi
 83 | 
 84 | # 4. Initialize git hooks
 85 | log "Setting up git hooks..."
 86 | pnpm prepare
 87 | 
 88 | # 5. Run initial verification
 89 | log "Running initial verification..."
 90 | if pnpm verify; then
 91 |     log "✅ All checks passed!"
 92 | else
 93 |     warn "Some checks failed. Fix issues before committing."
 94 | fi
 95 | 
 96 | # 6. Create initial build
 97 | log "Creating initial build..."
 98 | pnpm build
 99 | 
100 | # 7. Show next steps
101 | echo
102 | log "🎉 Setup complete!"
103 | echo
104 | info "Next steps:"
105 | echo "1. Open the project in your IDE (VS Code recommended)"
106 | echo "2. Review and customize package.json, README.md, and other files"
107 | echo "3. Start developing with: pnpm test:watch"
108 | echo "4. Create your first feature with: pnpm changeset"
109 | echo
110 | info "Available commands:"
111 | echo "  pnpm dev           # Start development mode"
112 | echo "  pnpm test:watch    # Run tests in watch mode"
113 | echo "  pnpm verify        # Run all quality checks"
114 | echo "  pnpm changeset     # Create a changeset for your changes"
115 | echo
116 | info "Documentation:"
117 | echo "  docs/GETTING_STARTED.md - Full setup guide"
118 | echo "  docs/PROCESS.md         - Development workflow"
119 | echo "  CLAUDE.md              - AI development tips"
```

--------------------------------------------------------------------------------
/src/__tests__/error-handler.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { describe, it, expect, vi } from 'vitest';
 2 | import { withMCPErrorHandling } from '../utils/error-handler.js';
 3 | import { SonarQubeAPIError, SonarQubeErrorType } from '../errors.js';
 4 | 
 5 | // Mock the logger
 6 | vi.mock('../utils/logger.js', () => ({
 7 |   createLogger: () => ({
 8 |     error: vi.fn(),
 9 |   }),
10 | }));
11 | 
12 | describe('Error Handler Utilities', () => {
13 |   describe('withMCPErrorHandling', () => {
14 |     it('should return result on success', async () => {
15 |       const fn = vi
16 |         .fn<(arg1: string, arg2: string) => Promise<{ content: string }>>()
17 |         .mockResolvedValue({ content: 'success' });
18 |       const wrapped = withMCPErrorHandling(fn);
19 | 
20 |       const result = await wrapped('arg1', 'arg2');
21 | 
22 |       expect(result).toEqual({ content: 'success' });
23 |       expect(fn).toHaveBeenCalledWith('arg1', 'arg2');
24 |     });
25 | 
26 |     it('should convert SonarQubeAPIError to MCP format', async () => {
27 |       const apiError = new SonarQubeAPIError(
28 |         'Test error',
29 |         SonarQubeErrorType.AUTHENTICATION_FAILED,
30 |         {
31 |           operation: 'test-op',
32 |           solution: 'Test solution',
33 |         }
34 |       );
35 |       const fn = vi.fn<() => Promise<any>>().mockRejectedValue(apiError);
36 |       const wrapped = withMCPErrorHandling(fn);
37 | 
38 |       await expect(wrapped()).rejects.toMatchObject({
39 |         code: -32001,
40 |         message: expect.stringContaining('Test error'),
41 |       });
42 |     });
43 | 
44 |     it('should re-throw non-SonarQubeAPIError', async () => {
45 |       const error = new Error('Generic error');
46 |       const fn = vi.fn<() => Promise<any>>().mockRejectedValue(error);
47 |       const wrapped = withMCPErrorHandling(fn);
48 | 
49 |       await expect(wrapped()).rejects.toThrow(error);
50 |     });
51 | 
52 |     it('should preserve function signature', async () => {
53 |       const fn = vi.fn((a: string, b: number) => Promise.resolve({ result: a + b }));
54 |       const wrapped = withMCPErrorHandling(fn);
55 | 
56 |       const result = await wrapped('test', 123);
57 | 
58 |       expect(result).toEqual({ result: 'test123' });
59 |       expect(fn).toHaveBeenCalledWith('test', 123);
60 |     });
61 | 
62 |     it('should handle all error types correctly', async () => {
63 |       const errorTypes = [
64 |         { type: SonarQubeErrorType.AUTHENTICATION_FAILED, code: -32001 },
65 |         { type: SonarQubeErrorType.AUTHORIZATION_FAILED, code: -32002 },
66 |         { type: SonarQubeErrorType.RESOURCE_NOT_FOUND, code: -32003 },
67 |         { type: SonarQubeErrorType.RATE_LIMITED, code: -32004 },
68 |         { type: SonarQubeErrorType.NETWORK_ERROR, code: -32005 },
69 |         { type: SonarQubeErrorType.CONFIGURATION_ERROR, code: -32006 },
70 |         { type: SonarQubeErrorType.VALIDATION_ERROR, code: -32007 },
71 |         { type: SonarQubeErrorType.SERVER_ERROR, code: -32008 },
72 |         { type: SonarQubeErrorType.UNKNOWN_ERROR, code: -32000 },
73 |       ];
74 | 
75 |       for (const { type, code } of errorTypes) {
76 |         const error = new SonarQubeAPIError(`${type} error`, type);
77 |         const fn = vi.fn<() => Promise<any>>().mockRejectedValue(error);
78 |         const wrapped = withMCPErrorHandling(fn);
79 | 
80 |         await expect(wrapped()).rejects.toMatchObject({
81 |           code,
82 |           message: expect.stringContaining(`${type} error`),
83 |         });
84 |       }
85 |     });
86 |   });
87 | });
88 | 
```

--------------------------------------------------------------------------------
/scripts/actionlint.sh:
--------------------------------------------------------------------------------

```bash
 1 | #!/usr/bin/env bash
 2 | 
 3 | # Wrapper script for actionlint - GitHub Actions workflow linter
 4 | # Checks if actionlint is installed and provides installation instructions if not
 5 | 
 6 | set -uo pipefail
 7 | 
 8 | # Colors for output
 9 | RED='\033[0;31m'
10 | GREEN='\033[0;32m'
11 | YELLOW='\033[1;33m'
12 | BLUE='\033[0;34m'
13 | NC='\033[0m' # No Color
14 | 
15 | # Check if actionlint is installed
16 | if ! command -v actionlint &> /dev/null; then
17 |     # In CI environments during release, skip actionlint since workflows were already validated in PR
18 |     if [ "${CI:-false}" = "true" ] || [ "${GITHUB_ACTIONS:-false}" = "true" ]; then
19 |         echo -e "${YELLOW}Skipping actionlint in CI (already validated in PR workflow)${NC}"
20 |         exit 0
21 |     fi
22 |     
23 |     # In local development, provide installation instructions
24 |     echo -e "${RED}Error: actionlint is not installed${NC}"
25 |     echo ""
26 |     echo -e "${YELLOW}Please install actionlint using one of the following methods:${NC}"
27 |     echo ""
28 |     echo -e "${BLUE}macOS/Linux (Homebrew):${NC}"
29 |     echo "  brew install actionlint"
30 |     echo ""
31 |     echo -e "${BLUE}Go:${NC}"
32 |     echo "  go install github.com/rhysd/actionlint/cmd/actionlint@latest"
33 |     echo ""
34 |     echo -e "${BLUE}Download binary:${NC}"
35 |     echo "  https://github.com/rhysd/actionlint/releases"
36 |     echo ""
37 |     echo "After installation, run this script again."
38 |     exit 1
39 | fi
40 | 
41 | # Run actionlint with provided arguments or default to checking .github/workflows/
42 | if [ $# -eq 0 ]; then
43 |     echo -e "${GREEN}Running actionlint on .github/workflows/...${NC}"
44 |     # Run actionlint and capture output
45 |     OUTPUT=$(actionlint 2>&1)
46 |     EXIT_CODE=$?
47 |     
48 |     if [ -n "$OUTPUT" ]; then
49 |         # Always show the output for transparency
50 |         echo "$OUTPUT"
51 |         
52 |         # Store grep results in variables to avoid redundant processing
53 |         HAS_SHELLCHECK_INFO=0
54 |         HAS_SHELLCHECK_ERRORS=0
55 |         if echo "$OUTPUT" | grep -q "SC[0-9]*:info:"; then
56 |             HAS_SHELLCHECK_INFO=1
57 |         fi
58 |         if echo "$OUTPUT" | grep -qE "SC[0-9]*:(error|warning):"; then
59 |             HAS_SHELLCHECK_ERRORS=1
60 |         fi
61 | 
62 |         # Check if output only contains shellcheck info warnings
63 |         if [ "$HAS_SHELLCHECK_INFO" -eq 1 ]; then
64 |             # Has shellcheck info warnings - check if there are also errors
65 |             if [ "$HAS_SHELLCHECK_ERRORS" -eq 1 ]; then
66 |                 # There are actual errors or warnings beyond info level
67 |                 echo ""
68 |                 echo -e "${RED}✗ Workflow validation failed - errors or warnings found${NC}"
69 |                 exit 1
70 |             else
71 |                 # Only shellcheck info warnings
72 |                 echo ""
73 |                 echo -e "${YELLOW}ℹ Info: Minor shellcheck suggestions found (not blocking)${NC}"
74 |                 echo -e "${GREEN}✓ All workflow files are valid${NC}"
75 |                 exit 0
76 |             fi
77 |         else
78 |             # Output exists but no shellcheck warnings - must be errors
79 |             echo ""
80 |             echo -e "${RED}✗ Workflow validation failed${NC}"
81 |             exit 1
82 |         fi
83 |     else
84 |         # No output means success
85 |         echo -e "${GREEN}✓ All workflow files are valid - no issues found${NC}"
86 |         exit 0
87 |     fi
88 | else
89 |     actionlint "$@"
90 | fi
```

--------------------------------------------------------------------------------
/src/__tests__/schema-validators.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { describe, it, expect } from 'vitest';
 2 | import { z } from 'zod';
 3 | describe('Schema Validators and Transformers', () => {
 4 |   it('should transform page string to number or null', () => {
 5 |     const pageSchema = z
 6 |       .string()
 7 |       .optional()
 8 |       .transform((val: any) => (val ? parseInt(val, 10) || null : null));
 9 |     expect(pageSchema.parse('10')).toBe(10);
10 |     expect(pageSchema.parse('invalid')).toBe(null);
11 |     expect(pageSchema.parse('')).toBe(null);
12 |     expect(pageSchema.parse(undefined)).toBe(null);
13 |   });
14 |   it('should transform string to boolean', () => {
15 |     const booleanSchema = z
16 |       .union([z.boolean(), z.string().transform((val: any) => val === 'true')])
17 |       .nullable()
18 |       .optional();
19 |     expect(booleanSchema.parse('true')).toBe(true);
20 |     expect(booleanSchema.parse('false')).toBe(false);
21 |     expect(booleanSchema.parse(true)).toBe(true);
22 |     expect(booleanSchema.parse(false)).toBe(false);
23 |     expect(booleanSchema.parse(null)).toBe(null);
24 |     expect(booleanSchema.parse(undefined)).toBe(undefined);
25 |   });
26 |   it('should validate severity enum', () => {
27 |     const severitySchema = z
28 |       .enum(['INFO', 'MINOR', 'MAJOR', 'CRITICAL', 'BLOCKER'])
29 |       .nullable()
30 |       .optional();
31 |     expect(severitySchema.parse('INFO')).toBe('INFO');
32 |     expect(severitySchema.parse('MINOR')).toBe('MINOR');
33 |     expect(severitySchema.parse('MAJOR')).toBe('MAJOR');
34 |     expect(severitySchema.parse('CRITICAL')).toBe('CRITICAL');
35 |     expect(severitySchema.parse('BLOCKER')).toBe('BLOCKER');
36 |     expect(severitySchema.parse(null)).toBe(null);
37 |     expect(severitySchema.parse(undefined)).toBe(undefined);
38 |     expect(() => severitySchema.parse('INVALID')).toThrow();
39 |   });
40 |   it('should validate status array enum', () => {
41 |     const statusSchema = z
42 |       .array(
43 |         z.enum([
44 |           'OPEN',
45 |           'CONFIRMED',
46 |           'REOPENED',
47 |           'RESOLVED',
48 |           'CLOSED',
49 |           'TO_REVIEW',
50 |           'IN_REVIEW',
51 |           'REVIEWED',
52 |         ])
53 |       )
54 |       .nullable()
55 |       .optional();
56 |     expect(statusSchema.parse(['OPEN', 'CONFIRMED'])).toEqual(['OPEN', 'CONFIRMED']);
57 |     expect(statusSchema.parse(null)).toBe(null);
58 |     expect(statusSchema.parse(undefined)).toBe(undefined);
59 |     expect(() => statusSchema.parse(['INVALID'])).toThrow();
60 |   });
61 |   it('should validate resolution array enum', () => {
62 |     const resolutionSchema = z
63 |       .array(z.enum(['FALSE-POSITIVE', 'WONTFIX', 'FIXED', 'REMOVED']))
64 |       .nullable()
65 |       .optional();
66 |     expect(resolutionSchema.parse(['FALSE-POSITIVE', 'WONTFIX'])).toEqual([
67 |       'FALSE-POSITIVE',
68 |       'WONTFIX',
69 |     ]);
70 |     expect(resolutionSchema.parse(null)).toBe(null);
71 |     expect(resolutionSchema.parse(undefined)).toBe(undefined);
72 |     expect(() => resolutionSchema.parse(['INVALID'])).toThrow();
73 |   });
74 |   it('should validate type array enum', () => {
75 |     const typeSchema = z
76 |       .array(z.enum(['CODE_SMELL', 'BUG', 'VULNERABILITY', 'SECURITY_HOTSPOT']))
77 |       .nullable()
78 |       .optional();
79 |     expect(typeSchema.parse(['CODE_SMELL', 'BUG'])).toEqual(['CODE_SMELL', 'BUG']);
80 |     expect(typeSchema.parse(null)).toBe(null);
81 |     expect(typeSchema.parse(undefined)).toBe(undefined);
82 |     expect(() => typeSchema.parse(['INVALID'])).toThrow();
83 |   });
84 | });
85 | 
```

--------------------------------------------------------------------------------
/src/__tests__/boolean-string-transform.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { describe, it, expect } from 'vitest';
 2 | import { z } from 'zod';
 3 | describe('Boolean string transform', () => {
 4 |   // Test the boolean transform that's used in the tool registrations
 5 |   const booleanStringTransform = (val: string) => val === 'true';
 6 |   // Create a schema that matches the one in index.ts
 7 |   const booleanSchema = z
 8 |     .union([z.boolean(), z.string().transform(booleanStringTransform)])
 9 |     .nullable()
10 |     .optional();
11 |   describe('direct transform function', () => {
12 |     it('should transform "true" to true', () => {
13 |       expect(booleanStringTransform('true')).toBe(true);
14 |     });
15 |     it('should transform anything else to false', () => {
16 |       expect(booleanStringTransform('false')).toBe(false);
17 |       expect(booleanStringTransform('True')).toBe(false);
18 |       expect(booleanStringTransform('1')).toBe(false);
19 |       expect(booleanStringTransform('')).toBe(false);
20 |     });
21 |   });
22 |   describe('zod schema with boolean transform', () => {
23 |     it('should accept and pass through boolean values', () => {
24 |       expect(booleanSchema.parse(true)).toBe(true);
25 |       expect(booleanSchema.parse(false)).toBe(false);
26 |     });
27 |     it('should transform string "true" to boolean true', () => {
28 |       expect(booleanSchema.parse('true')).toBe(true);
29 |     });
30 |     it('should transform other string values to boolean false', () => {
31 |       expect(booleanSchema.parse('false')).toBe(false);
32 |       expect(booleanSchema.parse('1')).toBe(false);
33 |       expect(booleanSchema.parse('')).toBe(false);
34 |     });
35 |     it('should pass through null and undefined', () => {
36 |       expect(booleanSchema.parse(null)).toBeNull();
37 |       expect(booleanSchema.parse(undefined)).toBeUndefined();
38 |     });
39 |   });
40 |   // Test multiple boolean schema transformations in the same schema
41 |   describe('multiple boolean transforms in schema', () => {
42 |     // Create a schema with multiple boolean transforms
43 |     const complexSchema = z.object({
44 |       resolved: z
45 |         .union([z.boolean(), z.string().transform(booleanStringTransform)])
46 |         .nullable()
47 |         .optional(),
48 |       on_component_only: z
49 |         .union([z.boolean(), z.string().transform(booleanStringTransform)])
50 |         .nullable()
51 |         .optional(),
52 |       since_leak_period: z
53 |         .union([z.boolean(), z.string().transform(booleanStringTransform)])
54 |         .nullable()
55 |         .optional(),
56 |       in_new_code_period: z
57 |         .union([z.boolean(), z.string().transform(booleanStringTransform)])
58 |         .nullable()
59 |         .optional(),
60 |     });
61 |     it('should transform multiple boolean string values', () => {
62 |       const result = complexSchema.parse({
63 |         resolved: 'true',
64 |         on_component_only: 'false',
65 |         since_leak_period: true,
66 |         in_new_code_period: 'true',
67 |       });
68 |       expect(result).toEqual({
69 |         resolved: true,
70 |         on_component_only: false,
71 |         since_leak_period: true,
72 |         in_new_code_period: true,
73 |       });
74 |     });
75 |     it('should handle mix of boolean, string, null and undefined values', () => {
76 |       const result = complexSchema.parse({
77 |         resolved: true,
78 |         on_component_only: 'true',
79 |         since_leak_period: null,
80 |       });
81 |       expect(result).toEqual({
82 |         resolved: true,
83 |         on_component_only: true,
84 |         since_leak_period: null,
85 |       });
86 |     });
87 |   });
88 | });
89 | 
```

--------------------------------------------------------------------------------
/.claude/commands/implement-github-issue.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Implement GitHub Issue
  2 | 
  3 | You are about to implement GitHub issue: $ARGUMENTS
  4 | 
  5 | ## Implementation Workflow
  6 | 
  7 | ### 1. Analyze the Issue
  8 | 
  9 | ```bash
 10 | gh issue view $ARGUMENTS
 11 | ```
 12 | 
 13 | - Review the full issue description
 14 | - If it contains Gherkin specs, parse acceptance criteria carefully
 15 | - Identify non-goals and constraints
 16 | - Note any technical requirements
 17 | 
 18 | ### 2. Research Codebase
 19 | 
 20 | - Search for relevant existing code
 21 | - Identify files needing modification
 22 | - Look for similar patterns to maintain consistency
 23 | - Review existing tests for patterns
 24 | 
 25 | ### 3. Plan Implementation
 26 | 
 27 | Create a plan with:
 28 | 
 29 | - Core functionality breakdown
 30 | - Test strategy (unit + property-based)
 31 | - Files to create/modify
 32 | - Edge cases and risks
 33 | 
 34 | ### 4. Create Feature Branch
 35 | 
 36 | ```bash
 37 | # Follow branch naming from CLAUDE.md
 38 | git checkout -b <type>/<issue-number>-<description>
 39 | # Example: feat/42-user-authentication
 40 | ```
 41 | 
 42 | ### 5. Implement Solution
 43 | 
 44 | - Follow patterns in CLAUDE.md (validation, testing, imports)
 45 | - Write clean, focused functions
 46 | - Add TypeScript types and Zod validation
 47 | - Document public APIs with JSDoc
 48 | 
 49 | ### 6. Write Tests
 50 | 
 51 | Required test coverage:
 52 | 
 53 | - **Unit tests** in `tests/*.spec.ts`
 54 | - **Property-based tests** in `tests/*.property.spec.ts` for business logic
 55 | - Test both success and failure cases
 56 | - Verify edge cases
 57 | 
 58 | ### 7. Verify Quality
 59 | 
 60 | ```bash
 61 | pnpm verify  # Runs all checks
 62 | ```
 63 | 
 64 | ### 8. Create Changeset
 65 | 
 66 | **Changeset Guidance for Features:**
 67 | 
 68 | ```bash
 69 | # For bug fixes
 70 | pnpm changeset
 71 | # Select: patch
 72 | # Message: "Fix: [brief description of what was fixed]"
 73 | 
 74 | # For new features
 75 | pnpm changeset
 76 | # Select: minor
 77 | # Message: "Add [feature name]: [brief description]"
 78 | 
 79 | # For breaking changes
 80 | pnpm changeset
 81 | # Select: major
 82 | # Message: "BREAKING: [what changed and migration required]"
 83 | 
 84 | # For non-code changes (docs, tests, refactoring)
 85 | pnpm changeset --empty
 86 | # Message: "Internal: [what was changed]"
 87 | ```
 88 | 
 89 | **Decision Guide:**
 90 | 
 91 | - **patch**: Bug fixes, security patches, performance improvements
 92 | - **minor**: New features, new APIs, significant enhancements
 93 | - **major**: Breaking changes, API removals, incompatible updates
 94 | - **--empty**: Documentation, tests, CI/CD, internal refactoring
 95 | 
 96 | ### 9. Commit Changes
 97 | 
 98 | ```bash
 99 | git add .
100 | git commit -m "<type>: <description>
101 | 
102 | <body-if-needed>
103 | 
104 | Closes #<issue-number>"
105 | ```
106 | 
107 | ### 10. Create Pull Request
108 | 
109 | ```bash
110 | git push -u origin <branch-name>
111 | 
112 | gh pr create \
113 |   --title "<type>: <description>" \
114 |   --body "## Summary
115 |   <what-and-why>
116 | 
117 |   ## Changes
118 |   - <list-changes>
119 | 
120 |   ## Testing
121 |   - <how-tested>
122 | 
123 |   Closes #<issue-number>" \
124 |   --assignee @me
125 | ```
126 | 
127 | ### 11. Monitor CI
128 | 
129 | ```bash
130 | gh pr checks --watch
131 | ```
132 | 
133 | ### 12. Address Feedback
134 | 
135 | - Respond to review comments
136 | - Make requested changes
137 | - Re-verify after changes
138 | 
139 | ### 13. Merge PR
140 | 
141 | ```bash
142 | # After approval and passing checks
143 | gh pr merge --squash --delete-branch
144 | ```
145 | 
146 | ## Key Points
147 | 
148 | - **Follow coding standards** in CLAUDE.md
149 | - **Test thoroughly** - Unit + property-based tests required
150 | - **Use changesets** for version management
151 | - **Conventional commits** for clear history
152 | - **Quality first** - All checks must pass
153 | 
154 | ## Success Checklist
155 | 
156 | Before completing:
157 | 
158 | - [ ] All acceptance criteria met
159 | - [ ] Tests comprehensive (unit + property)
160 | - [ ] `pnpm verify` passes
161 | - [ ] Documentation updated
162 | - [ ] Changeset created and up to date
163 | - [ ] PR reviewed and approved
164 | 
165 | See CLAUDE.md for detailed patterns, troubleshooting, and coding standards.
166 | 
```

--------------------------------------------------------------------------------
/docs/architecture/decisions/0006-expose-sonarqube-features-as-mcp-tools.md:
--------------------------------------------------------------------------------

```markdown
 1 | # 6. Expose SonarQube Features as MCP Tools
 2 | 
 3 | Date: 2025-06-13
 4 | 
 5 | ## Status
 6 | 
 7 | Accepted
 8 | 
 9 | ## Context
10 | 
11 | The Model Context Protocol (MCP) defines a standard for exposing capabilities to AI clients through "tools". When integrating SonarQube functionality into an MCP server, we need to decide how to structure and expose the various SonarQube operations (projects, issues, metrics, quality gates, hotspots, etc.) to AI clients.
12 | 
13 | SonarQube provides a comprehensive REST API with numerous endpoints covering different aspects of code quality management. These operations have varying complexity, parameters, and return types. We need an architecture that:
14 | 
15 | 1. Makes SonarQube functionality easily discoverable by AI clients
16 | 2. Provides clear, single-purpose operations
17 | 3. Enables scriptable automation of SonarQube tasks
18 | 4. Maintains consistency with MCP patterns
19 | 5. Allows for future extensibility
20 | 
21 | ## Decision
22 | 
23 | We will expose each SonarQube operation as a separate MCP tool registered in index.ts. This tool-based architecture means:
24 | 
25 | 1. **One Tool Per Operation**: Each distinct SonarQube capability (e.g., `searchIssues`, `getProjects`, `getMetrics`, `markIssueFalsePositive`) is implemented as its own MCP tool with a dedicated handler.
26 | 
27 | 2. **Tool Registration**: All tools are registered in index.ts using the MCP server's tool registration mechanism, providing metadata about each tool's purpose, parameters, and schema.
28 | 
29 | 3. **Domain Organization**: Tools are organized by domain modules (projects, issues, metrics, quality gates, hotspots, etc.) but exposed individually to the AI client.
30 | 
31 | 4. **Consistent Naming**: Tools follow a consistent naming pattern that reflects their SonarQube domain and operation (e.g., `sonarqube.issues.search`, `sonarqube.projects.list`).
32 | 
33 | 5. **Parameter Validation**: Each tool defines its own parameter schema for validation, ensuring type safety and clear documentation of required/optional parameters.
34 | 
35 | ## Consequences
36 | 
37 | ### Positive Consequences
38 | 
39 | 1. **Discoverability**: AI clients can easily discover available SonarQube operations through MCP's tool listing mechanism.
40 | 
41 | 2. **Clear Purpose**: Each tool has a single, well-defined purpose, making it easier for AI clients to understand and use correctly.
42 | 
43 | 3. **Scriptability**: AI clients can compose multiple tool calls to create complex workflows (e.g., search for issues, then bulk mark them as false positives).
44 | 
45 | 4. **Documentation**: Each tool can have its own detailed documentation, examples, and parameter descriptions.
46 | 
47 | 5. **Extensibility**: New SonarQube operations can be added as new tools without modifying existing ones.
48 | 
49 | 6. **Type Safety**: Individual parameter schemas per tool provide better type checking and validation.
50 | 
51 | 7. **Testing**: Each tool can be tested independently with focused test cases.
52 | 
53 | ### Negative Consequences
54 | 
55 | 1. **Tool Proliferation**: Large number of tools may overwhelm AI clients or make tool selection more complex.
56 | 
57 | 2. **Granularity**: Some related operations might benefit from being combined, but the tool-per-operation approach enforces separation.
58 | 
59 | 3. **Registration Overhead**: Each new SonarQube feature requires tool registration boilerplate in index.ts.
60 | 
61 | 4. **Naming Consistency**: Maintaining consistent naming across many tools requires discipline and documentation.
62 | 
63 | 5. **Cross-Tool State**: Operations that might benefit from shared state or context must pass data explicitly between tool calls.
64 | 
```

--------------------------------------------------------------------------------
/LICENSES.md:
--------------------------------------------------------------------------------

```markdown
 1 | # License Information
 2 | 
 3 | This document describes the licenses used in this project and its dependencies.
 4 | 
 5 | ## Project License
 6 | 
 7 | This project is licensed under the MIT License. See [LICENSE](./LICENSE) for details.
 8 | 
 9 | ## Container Image Dependencies
10 | 
11 | The Docker container is based on Alpine Linux and includes the following system packages with their respective licenses:
12 | 
13 | ### Acceptable GPL/LGPL Licenses
14 | 
15 | The following packages are part of the Alpine Linux base system and use GPL/LGPL licenses. These are acceptable for containerized applications:
16 | 
17 | | Package                  | License                             | Purpose                  | Acceptability                   |
18 | | ------------------------ | ----------------------------------- | ------------------------ | ------------------------------- |
19 | | `alpine-baselayout`      | GPL-2.0-only                        | Base directory structure | ✅ Alpine base - acceptable     |
20 | | `alpine-baselayout-data` | GPL-2.0-only                        | Base data files          | ✅ Alpine base - acceptable     |
21 | | `apk-tools`              | GPL-2.0-only                        | Package manager          | ✅ Alpine base - acceptable     |
22 | | `busybox`                | GPL-2.0-only                        | Core utilities           | ✅ Alpine base - acceptable     |
23 | | `busybox-binsh`          | GPL-2.0-only                        | Shell                    | ✅ Alpine base - acceptable     |
24 | | `libapk2`                | GPL-2.0-only                        | APK library              | ✅ Alpine base - acceptable     |
25 | | `libgcc`                 | GPL-2.0-or-later, LGPL-2.1-or-later | GCC runtime              | ✅ Runtime library - acceptable |
26 | | `libstdc++`              | GPL-2.0-or-later, LGPL-2.1-or-later | C++ standard library     | ✅ Runtime library - acceptable |
27 | | `musl-utils`             | GPL-2.0-or-later                    | C library utilities      | ✅ Runtime library - acceptable |
28 | 
29 | ### Other Licenses
30 | 
31 | | Package                  | License          | Purpose           |
32 | | ------------------------ | ---------------- | ----------------- |
33 | | `ca-certificates-bundle` | MPL-2.0, MIT     | SSL certificates  |
34 | | All other packages       | MIT, ISC, BSD-\* | Various utilities |
35 | 
36 | ## License Compliance
37 | 
38 | ### GPL/LGPL in Container Images
39 | 
40 | The use of GPL and LGPL licensed system libraries in container images is standard practice and acceptable because:
41 | 
42 | 1. **Runtime Exception**: These are system libraries provided by Alpine Linux as part of the base operating system
43 | 2. **No Distribution of Modified Binaries**: We use these packages as-is from Alpine's official repositories
44 | 3. **Container Isolation**: The GPL components are part of the container runtime environment, not distributed as standalone software
45 | 4. **Industry Standard**: Major cloud providers and container registries (Docker Hub, GCR, ECR) all use Alpine Linux with these same packages
46 | 
47 | ### Node.js Dependencies
48 | 
49 | All Node.js packages used in this project are licensed under permissive licenses (MIT, ISC, Apache-2.0). See `package.json` for the complete list.
50 | 
51 | ## Trivy Security Scanning
52 | 
53 | This project uses Trivy to scan container images for:
54 | 
55 | - **Vulnerabilities** (CVEs)
56 | - **Secrets** (hardcoded credentials)
57 | - **Misconfigurations** (security best practices)
58 | - **Licenses** (open-source compliance)
59 | 
60 | The license scanner will report GPL/LGPL packages from Alpine Linux. These findings are documented in this file and are acceptable for the reasons stated above.
61 | 
62 | ## Questions?
63 | 
64 | If you have questions about licensing, please open an issue or contact the maintainers.
65 | 
```

--------------------------------------------------------------------------------
/src/domains/base.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { SonarQubeClient as WebApiClient } from 'sonarqube-web-api-client';
 2 | import { createLogger } from '../utils/logger.js';
 3 | import { trackSonarQubeRequest } from '../monitoring/metrics.js';
 4 | import { wrapWithCircuitBreaker } from '../monitoring/circuit-breaker.js';
 5 | import { withRetry } from '../utils/retry.js';
 6 | 
 7 | /**
 8 |  * Base class for all domain modules
 9 |  */
10 | export abstract class BaseDomain {
11 |   protected readonly logger = createLogger(this.constructor.name);
12 | 
13 |   constructor(
14 |     protected readonly webApiClient: WebApiClient,
15 |     protected readonly organization: string | null
16 |   ) {}
17 | 
18 |   /**
19 |    * Wrap a SonarQube API call with retry, metrics and circuit breaker
20 |    */
21 |   protected async tracedApiCall<T>(endpoint: string, operation: () => Promise<T>): Promise<T> {
22 |     const startTime = Date.now();
23 | 
24 |     // Wrap operation with retry logic
25 |     const retryableOperation = () =>
26 |       withRetry(operation, {
27 |         maxAttempts: 3,
28 |         initialDelay: 1000,
29 |         maxDelay: 5000,
30 |         shouldRetry: (error: Error) => {
31 |           const message = error.message.toLowerCase();
32 |           // Retry on network errors and 5xx server errors
33 |           return (
34 |             message.includes('econnrefused') ||
35 |             message.includes('etimedout') ||
36 |             message.includes('enotfound') ||
37 |             message.includes('econnreset') ||
38 |             message.includes('socket hang up') ||
39 |             message.includes('502') ||
40 |             message.includes('503') ||
41 |             message.includes('504') ||
42 |             (message.includes('50') && !message.includes('40')) // 5xx errors
43 |           );
44 |         },
45 |       });
46 | 
47 |     // Wrap operation with circuit breaker
48 |     const breakerName = `sonarqube.${endpoint.replaceAll('/', '.')}`;
49 |     const wrappedOperation = wrapWithCircuitBreaker(breakerName, retryableOperation, {
50 |       timeout: 30000, // 30 seconds
51 |       errorThresholdPercentage: 50,
52 |       resetTimeout: 60000, // 1 minute
53 |       volumeThreshold: 5,
54 |       errorFilter: (error: Error) => {
55 |         // Don't count 4xx errors toward circuit breaker threshold
56 |         // (except 429 rate limiting)
57 |         const message = error.message.toLowerCase();
58 |         if (message.includes('429') || message.includes('rate limit')) {
59 |           return true;
60 |         }
61 |         return !(message.includes('40') && !message.includes('408'));
62 |       },
63 |     });
64 | 
65 |     try {
66 |       const result = await wrappedOperation();
67 | 
68 |       // Track successful request metric
69 |       trackSonarQubeRequest(endpoint, true, (Date.now() - startTime) / 1000);
70 | 
71 |       return result;
72 |     } catch (error) {
73 |       // Track failed request metric
74 |       const errorType = this.categorizeError(error);
75 |       trackSonarQubeRequest(endpoint, false, (Date.now() - startTime) / 1000, errorType);
76 | 
77 |       throw error;
78 |     }
79 |   }
80 | 
81 |   /**
82 |    * Categorize error for metrics
83 |    */
84 |   private categorizeError(error: unknown): string {
85 |     if (error instanceof Error) {
86 |       if (error.message.includes('timeout')) return 'timeout';
87 |       if (error.message.includes('401') || error.message.includes('unauthorized')) return 'auth';
88 |       if (error.message.includes('403') || error.message.includes('forbidden')) return 'forbidden';
89 |       if (error.message.includes('404') || error.message.includes('not found')) return 'not_found';
90 |       if (error.message.includes('429') || error.message.includes('rate limit'))
91 |         return 'rate_limit';
92 |       if (error.message.includes('500') || error.message.includes('server error'))
93 |         return 'server_error';
94 |       if (error.message.includes('network')) return 'network';
95 |     }
96 |     return 'unknown';
97 |   }
98 | }
99 | 
```

--------------------------------------------------------------------------------
/src/__tests__/environment-validation.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
  2 | import { createDefaultClient } from '../index.js';
  3 | 
  4 | // Mock the sonarqube module
  5 | vi.mock('../sonarqube.js', () => ({
  6 |   createSonarQubeClientFromEnv: vi.fn(() => ({
  7 |     // Mock client implementation
  8 |     listProjects: vi.fn(),
  9 |     getIssues: vi.fn(),
 10 |   })),
 11 |   setSonarQubeElicitationManager: vi.fn(),
 12 |   createSonarQubeClientFromEnvWithElicitation: vi.fn(() =>
 13 |     Promise.resolve({
 14 |       // Mock client implementation
 15 |       listProjects: vi.fn(),
 16 |       getIssues: vi.fn(),
 17 |     })
 18 |   ),
 19 | }));
 20 | 
 21 | describe('Environment Validation', () => {
 22 |   // Save original env vars
 23 |   const originalEnv = process.env;
 24 | 
 25 |   beforeEach(() => {
 26 |     // Clear environment variables
 27 |     process.env = { ...originalEnv };
 28 |     delete process.env.SONARQUBE_TOKEN;
 29 |     delete process.env.SONARQUBE_USERNAME;
 30 |     delete process.env.SONARQUBE_PASSWORD;
 31 |     delete process.env.SONARQUBE_PASSCODE;
 32 |     delete process.env.SONARQUBE_URL;
 33 |     delete process.env.SONARQUBE_ORGANIZATION;
 34 |   });
 35 | 
 36 |   afterEach(() => {
 37 |     // Restore original env vars
 38 |     process.env = originalEnv;
 39 |   });
 40 | 
 41 |   describe('createDefaultClient', () => {
 42 |     it('should create client with token authentication', () => {
 43 |       process.env.SONARQUBE_TOKEN = 'test-token';
 44 | 
 45 |       const client = createDefaultClient();
 46 |       expect(client).toBeDefined();
 47 |     });
 48 | 
 49 |     it('should create client with basic authentication', () => {
 50 |       process.env.SONARQUBE_USERNAME = 'test-user';
 51 |       process.env.SONARQUBE_PASSWORD = 'test-pass';
 52 | 
 53 |       const client = createDefaultClient();
 54 |       expect(client).toBeDefined();
 55 |     });
 56 | 
 57 |     it('should create client with passcode authentication', () => {
 58 |       process.env.SONARQUBE_PASSCODE = 'test-passcode';
 59 | 
 60 |       const client = createDefaultClient();
 61 |       expect(client).toBeDefined();
 62 |     });
 63 | 
 64 |     it('should throw error when no authentication is provided', () => {
 65 |       expect(() => createDefaultClient()).toThrow('No SonarQube authentication configured');
 66 |     });
 67 | 
 68 |     it('should throw error with invalid URL', () => {
 69 |       process.env.SONARQUBE_TOKEN = 'test-token';
 70 |       process.env.SONARQUBE_URL = 'not-a-valid-url';
 71 | 
 72 |       expect(() => createDefaultClient()).toThrow('Invalid SONARQUBE_URL');
 73 |     });
 74 | 
 75 |     it('should accept valid URL', () => {
 76 |       process.env.SONARQUBE_TOKEN = 'test-token';
 77 |       process.env.SONARQUBE_URL = 'https://sonarqube.example.com';
 78 | 
 79 |       const client = createDefaultClient();
 80 |       expect(client).toBeDefined();
 81 |     });
 82 | 
 83 |     it('should accept organization parameter', () => {
 84 |       process.env.SONARQUBE_TOKEN = 'test-token';
 85 |       process.env.SONARQUBE_ORGANIZATION = 'my-org';
 86 | 
 87 |       const client = createDefaultClient();
 88 |       expect(client).toBeDefined();
 89 |     });
 90 | 
 91 |     it('should prioritize token over other auth methods', () => {
 92 |       process.env.SONARQUBE_TOKEN = 'test-token';
 93 |       process.env.SONARQUBE_USERNAME = 'test-user';
 94 |       process.env.SONARQUBE_PASSWORD = 'test-pass';
 95 |       process.env.SONARQUBE_PASSCODE = 'test-passcode';
 96 | 
 97 |       // Should not throw - uses token auth
 98 |       const client = createDefaultClient();
 99 |       expect(client).toBeDefined();
100 |     });
101 | 
102 |     it('should create client when only username is provided (legacy token auth)', () => {
103 |       process.env.SONARQUBE_USERNAME = 'test-user';
104 | 
105 |       const client = createDefaultClient();
106 |       expect(client).toBeDefined();
107 |     });
108 | 
109 |     it('should throw error when only password is provided', () => {
110 |       process.env.SONARQUBE_PASSWORD = 'test-pass';
111 | 
112 |       expect(() => createDefaultClient()).toThrow('No SonarQube authentication configured');
113 |     });
114 |   });
115 | });
116 | 
```

--------------------------------------------------------------------------------
/src/__tests__/schemas/components-schema.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { z } from 'zod';
  2 | import { componentsToolSchema } from '../../schemas/components.js';
  3 | 
  4 | describe('componentsToolSchema', () => {
  5 |   it('should validate minimal parameters', () => {
  6 |     const input = {};
  7 |     const result = z.object(componentsToolSchema).parse(input);
  8 |     expect(result).toEqual({});
  9 |   });
 10 | 
 11 |   it('should validate search parameters', () => {
 12 |     const input = {
 13 |       query: 'UserService',
 14 |       qualifiers: ['TRK', 'FIL'],
 15 |       language: 'java',
 16 |     };
 17 |     const result = z.object(componentsToolSchema).parse(input);
 18 |     expect(result.query).toBe('UserService');
 19 |     expect(result.qualifiers).toEqual(['TRK', 'FIL']);
 20 |     expect(result.language).toBe('java');
 21 |   });
 22 | 
 23 |   it('should validate tree navigation parameters', () => {
 24 |     const input = {
 25 |       component: 'com.example:project',
 26 |       strategy: 'children',
 27 |       qualifiers: ['DIR', 'FIL'],
 28 |     };
 29 |     const result = z.object(componentsToolSchema).parse(input);
 30 |     expect(result.component).toBe('com.example:project');
 31 |     expect(result.strategy).toBe('children');
 32 |     expect(result.qualifiers).toEqual(['DIR', 'FIL']);
 33 |   });
 34 | 
 35 |   it('should validate pagination parameters with transformation', () => {
 36 |     const input = {
 37 |       asc: 'true',
 38 |       ps: '50',
 39 |       p: '2',
 40 |     };
 41 |     const result = z.object(componentsToolSchema).parse(input);
 42 |     expect(result.asc).toBe(true);
 43 |     expect(result.ps).toBe(50);
 44 |     expect(result.p).toBe(2);
 45 |   });
 46 | 
 47 |   it('should validate branch and pull request parameters', () => {
 48 |     const input = {
 49 |       component: 'com.example:project',
 50 |       branch: 'feature-branch',
 51 |       pullRequest: '123',
 52 |     };
 53 |     const result = z.object(componentsToolSchema).parse(input);
 54 |     expect(result.component).toBe('com.example:project');
 55 |     expect(result.branch).toBe('feature-branch');
 56 |     expect(result.pullRequest).toBe('123');
 57 |   });
 58 | 
 59 |   it('should validate all parameters together', () => {
 60 |     const input = {
 61 |       query: 'test',
 62 |       qualifiers: ['TRK', 'DIR', 'FIL'],
 63 |       language: 'typescript',
 64 |       component: 'com.example:project',
 65 |       strategy: 'all',
 66 |       asc: 'false',
 67 |       ps: '100',
 68 |       p: '1',
 69 |       branch: 'main',
 70 |       pullRequest: '456',
 71 |     };
 72 |     const result = z.object(componentsToolSchema).parse(input);
 73 |     expect(result).toMatchObject({
 74 |       query: 'test',
 75 |       qualifiers: ['TRK', 'DIR', 'FIL'],
 76 |       language: 'typescript',
 77 |       component: 'com.example:project',
 78 |       strategy: 'all',
 79 |       asc: false,
 80 |       ps: 100,
 81 |       p: 1,
 82 |       branch: 'main',
 83 |       pullRequest: '456',
 84 |     });
 85 |   });
 86 | 
 87 |   it('should reject invalid qualifiers', () => {
 88 |     const input = {
 89 |       qualifiers: ['INVALID'],
 90 |     };
 91 |     expect(() => z.object(componentsToolSchema).parse(input)).toThrow();
 92 |   });
 93 | 
 94 |   it('should reject invalid strategy', () => {
 95 |     const input = {
 96 |       strategy: 'invalid',
 97 |     };
 98 |     expect(() => z.object(componentsToolSchema).parse(input)).toThrow();
 99 |   });
100 | 
101 |   it('should handle boolean string transformations', () => {
102 |     const testCases = [
103 |       { asc: 'true', expected: true },
104 |       { asc: 'false', expected: false },
105 |     ];
106 | 
107 |     testCases.forEach((testCase) => {
108 |       const result = z.object(componentsToolSchema).parse(testCase);
109 |       expect(result.asc).toBe(testCase.expected);
110 |     });
111 |   });
112 | 
113 |   it('should handle number string transformations', () => {
114 |     const input = {
115 |       ps: '25',
116 |       p: '3',
117 |     };
118 |     const result = z.object(componentsToolSchema).parse(input);
119 |     expect(result.ps).toBe(25);
120 |     expect(result.p).toBe(3);
121 |   });
122 | 
123 |   it('should handle null values for pagination', () => {
124 |     const input = {
125 |       ps: undefined,
126 |       p: undefined,
127 |     };
128 |     const result = z.object(componentsToolSchema).parse(input);
129 |     expect(result.ps).toBeUndefined();
130 |     expect(result.p).toBeUndefined();
131 |   });
132 | });
133 | 
```

--------------------------------------------------------------------------------
/docs/architecture/decisions/0015-transport-architecture-refactoring.md:
--------------------------------------------------------------------------------

```markdown
 1 | # 15. Transport Architecture Refactoring
 2 | 
 3 | Date: 2025-06-22
 4 | 
 5 | ## Status
 6 | 
 7 | Accepted
 8 | 
 9 | ## Context
10 | 
11 | The current SonarQube MCP server implementation is tightly coupled to STDIO transport, making it difficult to add support for alternative transport mechanisms like HTTP or WebSocket. As per ADR-0003, the transport layer should be abstracted to allow future support for different transport types.
12 | 
13 | ### Current Issues:
14 | 
15 | 1. Direct dependency on `StdioServerTransport` in the main entry point
16 | 2. No abstraction layer between the MCP server and transport implementation
17 | 3. Tight coupling makes testing and extending the transport layer difficult
18 | 4. The STDIO transport requires a workaround (adding a dummy `connect` method) for TypeScript compatibility
19 | 
20 | ### Requirements:
21 | 
22 | 1. Support multiple transport mechanisms (STDIO, HTTP, WebSocket)
23 | 2. Maintain backward compatibility with existing STDIO transport
24 | 3. Allow transport selection via environment variables
25 | 4. Enable easy addition of new transport types in the future
26 | 5. Follow existing architectural patterns in the codebase
27 | 
28 | ## Decision
29 | 
30 | We will refactor the transport architecture by introducing:
31 | 
32 | 1. **Transport Interface (`ITransport`)**: A common interface that all transport implementations must follow
33 | 2. **Transport Factory (`TransportFactory`)**: A factory pattern for creating transport instances based on configuration
34 | 3. **Environment-based Configuration**: Use `MCP_TRANSPORT` environment variable to select transport type
35 | 4. **Modular Transport Implementations**: Each transport type in its own module under `src/transports/`
36 | 
37 | ### Architecture Details:
38 | 
39 | ```typescript
40 | // Transport Interface
41 | interface ITransport {
42 |   connect(server: Server): Promise<void>;
43 |   getName(): string;
44 | }
45 | 
46 | // Transport Factory
47 | class TransportFactory {
48 |   static create(config: ITransportConfig): ITransport;
49 |   static createFromEnvironment(): ITransport;
50 | }
51 | ```
52 | 
53 | ### Environment Variables:
54 | 
55 | - `MCP_TRANSPORT`: Transport type selection (stdio|http), defaults to 'stdio'
56 | - `MCP_HTTP_PORT`: HTTP transport port (for future HTTP implementation)
57 | - `MCP_HTTP_HOST`: HTTP transport host (for future HTTP implementation)
58 | 
59 | ## Consequences
60 | 
61 | ### Positive:
62 | 
63 | 1. **Extensibility**: Easy to add new transport types without modifying core logic
64 | 2. **Testability**: Transport implementations can be tested in isolation
65 | 3. **Backward Compatibility**: STDIO remains the default transport, no breaking changes
66 | 4. **Clean Architecture**: Follows SOLID principles with clear separation of concerns
67 | 5. **Type Safety**: Proper TypeScript interfaces ensure type safety across transport implementations
68 | 6. **Future-Ready**: HTTP transport can be added in a future story without architectural changes
69 | 
70 | ### Negative:
71 | 
72 | 1. **Additional Abstraction**: Adds one more layer of abstraction (minimal overhead)
73 | 2. **More Files**: Transport logic is now spread across multiple files (better organization)
74 | 
75 | ### Neutral:
76 | 
77 | 1. **Configuration**: Environment variable approach aligns with existing patterns (ADR-0008)
78 | 2. **Testing**: Requires new test files for transport modules (improves coverage)
79 | 
80 | ## Implementation Notes
81 | 
82 | 1. The STDIO transport workaround (adding `connect` method) is now encapsulated within the `StdioTransport` class
83 | 2. HTTP transport throws a "not yet implemented" error, allowing the structure to be in place for future implementation
84 | 3. All transport modules follow the project's existing patterns for module organization and exports
85 | 4. Tests are implemented using Jest globals pattern consistent with other tests in the project
86 | 
87 | ## Related ADRs
88 | 
89 | - ADR-0003: Uses MCP SDK to Create Server (mentions future transport abstraction)
90 | - ADR-0008: Uses Environment Variables for Configuration (transport selection pattern)
91 | - ADR-0010: Uses STDIO Server Transport (current transport choice)
92 | 
```

--------------------------------------------------------------------------------
/src/utils/__tests__/pattern-matcher.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { describe, expect, it } from 'vitest';
 2 | import { PatternMatcher } from '../pattern-matcher.js';
 3 | 
 4 | describe('PatternMatcher', () => {
 5 |   describe('constructor', () => {
 6 |     it('should create a pattern matcher with a simple pattern', () => {
 7 |       const matcher = new PatternMatcher('test');
 8 |       expect(matcher.getPattern()).toBe('test');
 9 |     });
10 | 
11 |     it('should create a pattern matcher with wildcards', () => {
12 |       const matcher = new PatternMatcher('*@example.com');
13 |       expect(matcher.getPattern()).toBe('*@example.com');
14 |     });
15 |   });
16 | 
17 |   describe('test', () => {
18 |     it('should match exact strings', () => {
19 |       const matcher = new PatternMatcher('[email protected]');
20 |       expect(matcher.test('[email protected]')).toBe(true);
21 |       expect(matcher.test('[email protected]')).toBe(false);
22 |     });
23 | 
24 |     it('should match with * wildcard', () => {
25 |       const matcher = new PatternMatcher('*@example.com');
26 |       expect(matcher.test('[email protected]')).toBe(true);
27 |       expect(matcher.test('[email protected]')).toBe(true);
28 |       expect(matcher.test('[email protected]')).toBe(true);
29 |       expect(matcher.test('[email protected]')).toBe(false);
30 |     });
31 | 
32 |     it('should match with ? wildcard', () => {
33 |       const matcher = new PatternMatcher('user-?');
34 |       expect(matcher.test('user-1')).toBe(true);
35 |       expect(matcher.test('user-a')).toBe(true);
36 |       expect(matcher.test('user-10')).toBe(false);
37 |       expect(matcher.test('user-')).toBe(false);
38 |     });
39 | 
40 |     it('should match with multiple wildcards', () => {
41 |       const matcher = new PatternMatcher('*@*.example.com');
42 |       expect(matcher.test('[email protected]')).toBe(true);
43 |       expect(matcher.test('[email protected]')).toBe(true);
44 |       expect(matcher.test('[email protected]')).toBe(false);
45 |     });
46 | 
47 |     it('should escape regex special characters', () => {
48 |       const matcher = new PatternMatcher('[email protected]');
49 |       expect(matcher.test('[email protected]')).toBe(true);
50 |       expect(matcher.test('[email protected]')).toBe(false);
51 |     });
52 | 
53 |     it('should handle patterns with brackets', () => {
54 |       const matcher = new PatternMatcher('[test]@example.com');
55 |       expect(matcher.test('[test]@example.com')).toBe(true);
56 |       expect(matcher.test('[email protected]')).toBe(false);
57 |     });
58 | 
59 |     it('should handle patterns with parentheses', () => {
60 |       const matcher = new PatternMatcher('(test)*@example.com');
61 |       expect(matcher.test('(test)[email protected]')).toBe(true);
62 |       expect(matcher.test('[email protected]')).toBe(false);
63 |     });
64 | 
65 |     it('should handle URL patterns', () => {
66 |       const matcher = new PatternMatcher('https://*.auth.example.com');
67 |       expect(matcher.test('https://prod.auth.example.com')).toBe(true);
68 |       expect(matcher.test('https://dev.auth.example.com')).toBe(true);
69 |       expect(matcher.test('http://prod.auth.example.com')).toBe(false);
70 |     });
71 |   });
72 | 
73 |   describe('create', () => {
74 |     it('should create a pattern matcher successfully', () => {
75 |       const matcher = PatternMatcher.create('test@*.com', 'test-context');
76 |       expect(matcher).toBeDefined();
77 |       expect(matcher?.getPattern()).toBe('test@*.com');
78 |     });
79 | 
80 |     it('should handle creation errors gracefully', () => {
81 |       // Force an error by creating an invalid regex pattern
82 |       const invalidPattern = '['; // This will cause a SyntaxError in RegExp constructor
83 | 
84 |       const matcher = PatternMatcher.create(invalidPattern, 'test-context');
85 |       // The glob to regex conversion should not create invalid patterns
86 |       // But if it somehow does, the create method should handle it gracefully
87 |       // In this case, '[' becomes '\[' which is valid, so matcher is created
88 |       expect(matcher).toBeDefined();
89 |       expect(matcher?.getPattern()).toBe('[');
90 |     });
91 |   });
92 | });
93 | 
```

--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------

```javascript
  1 | // @ts-check
  2 | import tseslint from '@typescript-eslint/eslint-plugin';
  3 | import tsParser from '@typescript-eslint/parser';
  4 | import eslintConfigPrettier from 'eslint-config-prettier';
  5 | import * as jsonc from 'eslint-plugin-jsonc';
  6 | import jsoncParser from 'jsonc-eslint-parser';
  7 | import { fileURLToPath } from 'node:url';
  8 | import { dirname } from 'node:path';
  9 | 
 10 | const __dirname = dirname(fileURLToPath(import.meta.url));
 11 | 
 12 | /** @type {import('eslint').Linter.FlatConfig[]} */
 13 | export default [
 14 |   // Ignore patterns
 15 |   {
 16 |     ignores: ['dist/**', 'coverage/**', 'node_modules/**', 'sbom.cdx.json'],
 17 |   },
 18 |   // Base configuration for all JS/TS files
 19 |   {
 20 |     files: ['**/*.{ts,tsx,js}'],
 21 |     languageOptions: {
 22 |       parser: tsParser,
 23 |       parserOptions: {
 24 |         ecmaVersion: 2024,
 25 |         sourceType: 'module',
 26 |       },
 27 |     },
 28 |     plugins: { '@typescript-eslint': tseslint },
 29 |     rules: {
 30 |       ...tseslint.configs['recommended'].rules,
 31 |       'no-console': 'warn',
 32 |       'no-debugger': 'error',
 33 |     },
 34 |   },
 35 |   // Type-aware rules for TypeScript files only
 36 |   {
 37 |     files: ['src/**/*.ts', 'tests/**/*.ts'],
 38 |     languageOptions: {
 39 |       parser: tsParser,
 40 |       parserOptions: {
 41 |         ecmaVersion: 2024,
 42 |         sourceType: 'module',
 43 |         project: true,
 44 |         tsconfigRootDir: __dirname,
 45 |       },
 46 |     },
 47 |     rules: {
 48 |       ...tseslint.configs['recommended-type-checked'].rules,
 49 |     },
 50 |   },
 51 |   // JSON/JSONC/JSON5 linting configuration
 52 |   {
 53 |     files: ['**/*.json', '**/*.json5', '**/*.jsonc'],
 54 |     languageOptions: {
 55 |       parser: jsoncParser,
 56 |     },
 57 |     plugins: {
 58 |       jsonc,
 59 |     },
 60 |     rules: {
 61 |       ...jsonc.configs['recommended-with-json'].rules,
 62 |       'jsonc/sort-keys': 'off', // Keep keys in logical order, not alphabetical
 63 |       'jsonc/indent': ['error', 2], // Enforce 2-space indentation in JSON files
 64 |       'jsonc/key-spacing': 'error', // Enforce consistent spacing between keys and values
 65 |       'jsonc/comma-dangle': ['error', 'never'], // No trailing commas in JSON
 66 |       'jsonc/quotes': ['error', 'double'], // Enforce double quotes in JSON
 67 |       'jsonc/quote-props': ['error', 'always'], // Always quote property names
 68 |       'jsonc/no-comments': 'off', // Allow comments in JSONC files
 69 |     },
 70 |   },
 71 |   // Specific rules for package.json
 72 |   {
 73 |     files: ['**/package.json'],
 74 |     rules: {
 75 |       'jsonc/sort-keys': [
 76 |         'error',
 77 |         {
 78 |           pathPattern: '^$', // Root object
 79 |           order: [
 80 |             'name',
 81 |             'version',
 82 |             'description',
 83 |             'keywords',
 84 |             'author',
 85 |             'license',
 86 |             'repository',
 87 |             'bugs',
 88 |             'homepage',
 89 |             'private',
 90 |             'type',
 91 |             'main',
 92 |             'module',
 93 |             'exports',
 94 |             'files',
 95 |             'bin',
 96 |             'packageManager',
 97 |             'engines',
 98 |             'scripts',
 99 |             'lint-staged',
100 |             'dependencies',
101 |             'devDependencies',
102 |             'peerDependencies',
103 |             'optionalDependencies',
104 |           ],
105 |         },
106 |       ],
107 |     },
108 |   },
109 |   // Specific rules for tsconfig files
110 |   {
111 |     files: ['**/tsconfig*.json'],
112 |     rules: {
113 |       'jsonc/no-comments': 'off', // Allow comments in tsconfig files
114 |     },
115 |   },
116 |   // Relaxed rules for test files
117 |   {
118 |     files: ['**/__tests__/**/*.ts', '**/*.test.ts'],
119 |     rules: {
120 |       '@typescript-eslint/no-unsafe-assignment': 'off',
121 |       '@typescript-eslint/no-unsafe-call': 'off',
122 |       '@typescript-eslint/no-unsafe-member-access': 'off',
123 |       '@typescript-eslint/no-unsafe-argument': 'off',
124 |       '@typescript-eslint/no-unsafe-return': 'off',
125 |       '@typescript-eslint/no-explicit-any': 'off',
126 |       '@typescript-eslint/unbound-method': 'off',
127 |       '@typescript-eslint/restrict-template-expressions': 'off',
128 |     },
129 |   },
130 |   // Keep Prettier last
131 |   eslintConfigPrettier,
132 | ];
133 | 
```

--------------------------------------------------------------------------------
/.claude/commands/fix-sonarqube-issues.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Fix SonarQube Issues
  2 | 
  3 | Analyze and fix all open issues reported by SonarQube for this project.
  4 | 
  5 | ## Steps
  6 | 
  7 | 1. **Connect to SonarQube and Check for Issues**
  8 |    - Use the configured SonarQube MCP server
  9 |    - Identify the project key (usually matches the repository name)
 10 |    - Query all open issues for the project
 11 |    - Filter by status: OPEN, CONFIRMED, REOPENED
 12 |    - **If no issues found**: Report "✅ No SonarQube issues found!" and exit
 13 | 
 14 | 2. **Analyze Retrieved Issues**
 15 |    - Group by severity: BLOCKER, CRITICAL, MAJOR, MINOR, INFO
 16 |    - Categorize by type:
 17 |      - Code smells (maintainability issues)
 18 |      - Bugs (reliability issues)
 19 |      - Vulnerabilities (security issues)
 20 |      - Security hotspots (potential security risks)
 21 |      - Duplications (code duplication)
 22 |    - Report summary of issues found
 23 | 
 24 | 3. **Create Feature Branch** (only if issues exist)
 25 | 
 26 |    ```bash
 27 |    git checkout -b fix/sonarqube-issues
 28 |    ```
 29 | 
 30 | 4. **Fix Issues by Priority**
 31 |    - Start with BLOCKER severity
 32 |    - Then CRITICAL
 33 |    - Then MAJOR
 34 |    - Then MINOR
 35 |    - Finally INFO
 36 | 
 37 | 5. **For Each Issue**
 38 |    - Read the affected file
 39 |    - Understand the issue context
 40 |    - Apply the recommended fix
 41 |    - Verify the fix doesn't break existing functionality
 42 | 
 43 | 6. **Common Issue Types and Fixes**
 44 |    - **Unused variables/imports**: Remove them
 45 |    - **Complex functions**: Split into smaller functions
 46 |    - **Missing error handling**: Add try-catch blocks
 47 |    - **Type safety issues**: Add proper TypeScript types
 48 |    - **Security issues**: Sanitize inputs, use secure functions
 49 |    - **Code duplication**: Extract common code into functions
 50 |    - **Cognitive complexity**: Simplify logic, reduce nesting
 51 | 
 52 | 7. **Validation**
 53 |    - Run `pnpm verify` to ensure all tests pass
 54 |    - Run `pnpm lint` to check for linting issues
 55 |    - Run `pnpm typecheck` to verify TypeScript
 56 | 
 57 | 8. **Create Changeset**
 58 | 
 59 |    ```bash
 60 |    pnpm changeset
 61 |    ```
 62 | 
 63 |    - Describe the fixes made
 64 |    - Use patch version for bug fixes
 65 |    - Use minor version for improvements
 66 | 
 67 | 9. **Commit Changes**
 68 | 
 69 |    ```bash
 70 |    git add -A
 71 |    git commit -m "fix: resolve SonarQube issues
 72 | 
 73 |    - Fix [number] code smells
 74 |    - Fix [number] bugs
 75 |    - Fix [number] vulnerabilities
 76 |    - Improve code maintainability and reliability"
 77 |    ```
 78 | 
 79 | 10. **Push Branch**
 80 | 
 81 |     ```bash
 82 |     git push origin fix/sonarqube-issues
 83 |     ```
 84 | 
 85 | 11. **Create Pull Request**
 86 | 
 87 |     ```bash
 88 |     gh pr create --title "fix: resolve SonarQube issues" \
 89 |       --body "## Summary
 90 |     - Fixed all open SonarQube issues for the project
 91 |     - Improved code quality, security, and maintainability
 92 | 
 93 |     ## Changes
 94 |     - ✅ Fixed [X] BLOCKER issues
 95 |     - ✅ Fixed [X] CRITICAL issues
 96 |     - ✅ Fixed [X] MAJOR issues
 97 |     - ✅ Fixed [X] MINOR issues
 98 |     - ✅ Fixed [X] INFO issues
 99 | 
100 |     ## Issue Categories
101 |     - 🐛 Bugs: [number] fixed
102 |     - 🔒 Vulnerabilities: [number] fixed
103 |     - 🧹 Code Smells: [number] fixed
104 |     - 📋 Duplications: [number] fixed
105 | 
106 |     ## Testing
107 |     - All tests passing
108 |     - Linting checks pass
109 |     - TypeScript compilation successful"
110 |     ```
111 | 
112 | ## Example Usage
113 | 
114 | ```bash
115 | # First, ensure SonarQube MCP server is configured
116 | # Then run this command to fix all issues
117 | 
118 | # The command will:
119 | # 1. Connect to SonarQube and check for issues
120 | # 2. If no issues: exit early with success message
121 | # 3. If issues exist:
122 | #    - Create a new branch
123 | #    - Fix them in priority order
124 | #    - Create a changeset
125 | #    - Commit all changes
126 | #    - Push the branch
127 | #    - Create a PR with all fixes
128 | ```
129 | 
130 | ## Notes
131 | 
132 | - Some issues may be false positives - mark them as such in SonarQube
133 | - Complex refactoring should be done carefully to avoid breaking changes
134 | - Always run tests after fixing issues
135 | - Consider fixing related issues together for better code organization
136 | - The PR will need review before merging to main
137 | - If no issues are found, no branch or PR will be created
138 | 
```

--------------------------------------------------------------------------------
/src/__tests__/function-tests.test.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { describe, it, expect, vi } from 'vitest';
 2 | import { nullToUndefined, mapToSonarQubeParams } from '../index.js';
 3 | vi.mock('../sonarqube.js');
 4 | describe('Utility Function Tests', () => {
 5 |   describe('nullToUndefined function', () => {
 6 |     it('should convert null to undefined but preserve other values', () => {
 7 |       expect(nullToUndefined(null)).toBeUndefined();
 8 |       expect(nullToUndefined(undefined)).toBeUndefined();
 9 |       // Other values should remain the same
10 |       expect(nullToUndefined(0)).toBe(0);
11 |       expect(nullToUndefined('')).toBe('');
12 |       expect(nullToUndefined('test')).toBe('test');
13 |       expect(nullToUndefined(123)).toBe(123);
14 |       expect(nullToUndefined(false)).toBe(false);
15 |       expect(nullToUndefined(true)).toBe(true);
16 |       // Objects and arrays should be passed through
17 |       const obj = { test: 'value' };
18 |       const arr = [1, 2, 3];
19 |       expect(nullToUndefined(obj)).toBe(obj);
20 |       expect(nullToUndefined(arr)).toBe(arr);
21 |     });
22 |   });
23 |   describe('mapToSonarQubeParams function', () => {
24 |     it('should map MCP tool parameters to SonarQube client parameters', () => {
25 |       const result = mapToSonarQubeParams({
26 |         project_key: 'my-project',
27 |         severity: 'MAJOR',
28 |         page: '10',
29 |         page_size: '25',
30 |         statuses: ['OPEN', 'CONFIRMED'],
31 |         resolutions: ['FALSE-POSITIVE'],
32 |         resolved: 'true',
33 |         types: ['BUG', 'VULNERABILITY'],
34 |         rules: ['rule1', 'rule2'],
35 |         tags: ['tag1', 'tag2'],
36 |         created_after: '2023-01-01',
37 |         created_before: '2023-12-31',
38 |         created_at: '2023-06-15',
39 |         created_in_last: '30d',
40 |         assignees: ['user1', 'user2'],
41 |         authors: ['author1', 'author2'],
42 |         cwe: ['cwe1', 'cwe2'],
43 |         languages: ['java', 'js'],
44 |         owasp_top10: ['a1', 'a2'],
45 |         sans_top25: ['sans1', 'sans2'],
46 |         sonarsource_security: ['ss1', 'ss2'],
47 |         on_component_only: 'true',
48 |         facets: ['facet1', 'facet2'],
49 |         since_leak_period: 'true',
50 |         in_new_code_period: 'true',
51 |       });
52 |       // Check key mappings
53 |       expect(result.projectKey).toBe('my-project');
54 |       expect(result.severity).toBe('MAJOR');
55 |       expect(result.page).toBe('10');
56 |       expect(result.pageSize).toBe('25');
57 |       expect(result.statuses).toEqual(['OPEN', 'CONFIRMED']);
58 |       expect(result.resolutions).toEqual(['FALSE-POSITIVE']);
59 |       expect(result.resolved).toBe('true');
60 |       expect(result.types).toEqual(['BUG', 'VULNERABILITY']);
61 |       expect(result.rules).toEqual(['rule1', 'rule2']);
62 |       expect(result.tags).toEqual(['tag1', 'tag2']);
63 |       expect(result.createdAfter).toBe('2023-01-01');
64 |       expect(result.createdBefore).toBe('2023-12-31');
65 |       expect(result.createdAt).toBe('2023-06-15');
66 |       expect(result.createdInLast).toBe('30d');
67 |       expect(result.assignees).toEqual(['user1', 'user2']);
68 |       expect(result.authors).toEqual(['author1', 'author2']);
69 |       expect(result.cwe).toEqual(['cwe1', 'cwe2']);
70 |       expect(result.languages).toEqual(['java', 'js']);
71 |       expect(result.owaspTop10).toEqual(['a1', 'a2']);
72 |       expect(result.sansTop25).toEqual(['sans1', 'sans2']);
73 |       expect(result.sonarsourceSecurity).toEqual(['ss1', 'ss2']);
74 |       expect(result.onComponentOnly).toBe('true');
75 |       expect(result.facets).toEqual(['facet1', 'facet2']);
76 |       expect(result.sinceLeakPeriod).toBe('true');
77 |       expect(result.inNewCodePeriod).toBe('true');
78 |     });
79 |     it('should handle null and undefined values correctly', () => {
80 |       const result = mapToSonarQubeParams({
81 |         project_key: 'my-project',
82 |         severity: null,
83 |         statuses: null,
84 |         resolved: null,
85 |       });
86 |       expect(result.projectKey).toBe('my-project');
87 |       expect(result.severity).toBeUndefined();
88 |       expect(result.statuses).toBeUndefined();
89 |       expect(result.resolved).toBeUndefined();
90 |     });
91 |   });
92 | });
93 | 
```

--------------------------------------------------------------------------------
/src/utils/client-factory.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { createLogger } from './logger.js';
  2 | import type { ISonarQubeClient } from '../types/index.js';
  3 | import { createSonarQubeClientFromEnv } from '../sonarqube.js';
  4 | import { SonarQubeAPIError, SonarQubeErrorType } from '../errors.js';
  5 | 
  6 | const logger = createLogger('client-factory');
  7 | 
  8 | /**
  9 |  * Validates environment variables for SonarQube authentication
 10 |  * @throws Error if no authentication method is configured or if invalid values are provided
 11 |  */
 12 | export const validateEnvironmentVariables = () => {
 13 |   logger.debug('Validating environment variables');
 14 | 
 15 |   // Check if any authentication method is configured
 16 |   const hasToken = !!process.env.SONARQUBE_TOKEN;
 17 |   const hasBasicAuth = !!process.env.SONARQUBE_USERNAME;
 18 |   const hasPasscode = !!process.env.SONARQUBE_PASSCODE;
 19 | 
 20 |   if (!hasToken && !hasBasicAuth && !hasPasscode) {
 21 |     const error = new SonarQubeAPIError(
 22 |       'No SonarQube authentication configured',
 23 |       SonarQubeErrorType.CONFIGURATION_ERROR,
 24 |       {
 25 |         operation: 'validateEnvironmentVariables',
 26 |         solution:
 27 |           'Set one of the following authentication methods:\n' +
 28 |           '• SONARQUBE_TOKEN for token-based authentication (recommended)\n' +
 29 |           '• SONARQUBE_USERNAME and SONARQUBE_PASSWORD for basic authentication\n' +
 30 |           '• SONARQUBE_PASSCODE for system passcode authentication',
 31 |         context: {
 32 |           hasToken,
 33 |           hasBasicAuth,
 34 |           hasPasscode,
 35 |         },
 36 |       }
 37 |     );
 38 |     logger.error('Missing authentication environment variables', error);
 39 |     throw error;
 40 |   }
 41 | 
 42 |   // Validate URL if provided
 43 |   if (process.env.SONARQUBE_URL) {
 44 |     try {
 45 |       new URL(process.env.SONARQUBE_URL);
 46 |       logger.debug('Valid SONARQUBE_URL provided', { url: process.env.SONARQUBE_URL });
 47 |     } catch {
 48 |       const error = new SonarQubeAPIError(
 49 |         `Invalid SONARQUBE_URL: "${process.env.SONARQUBE_URL}"`,
 50 |         SonarQubeErrorType.CONFIGURATION_ERROR,
 51 |         {
 52 |           operation: 'validateEnvironmentVariables',
 53 |           solution:
 54 |             'Provide a valid URL including protocol (e.g., https://sonarcloud.io or https://your-sonarqube.com)\n' +
 55 |             'Note: URL should not have a trailing slash',
 56 |           context: {
 57 |             providedUrl: process.env.SONARQUBE_URL,
 58 |           },
 59 |         }
 60 |       );
 61 |       logger.error('Invalid SONARQUBE_URL', error);
 62 |       throw error;
 63 |     }
 64 |   }
 65 | 
 66 |   // Validate organization if provided
 67 |   if (process.env.SONARQUBE_ORGANIZATION && process.env.SONARQUBE_ORGANIZATION.trim() === '') {
 68 |     const error = new SonarQubeAPIError(
 69 |       'Empty SONARQUBE_ORGANIZATION',
 70 |       SonarQubeErrorType.CONFIGURATION_ERROR,
 71 |       {
 72 |         operation: 'validateEnvironmentVariables',
 73 |         solution:
 74 |           'Provide a valid organization key (e.g., "my-org") or remove the environment variable',
 75 |         context: {
 76 |           providedValue: '(empty string)',
 77 |         },
 78 |       }
 79 |     );
 80 |     logger.error('Empty SONARQUBE_ORGANIZATION', error);
 81 |     throw error;
 82 |   }
 83 | 
 84 |   // Log which authentication method is being used
 85 |   if (hasToken) {
 86 |     logger.info('Using token authentication');
 87 |   } else if (hasBasicAuth) {
 88 |     logger.info('Using basic authentication');
 89 |   } else if (hasPasscode) {
 90 |     logger.info('Using passcode authentication');
 91 |   }
 92 | 
 93 |   logger.info('Environment variables validated successfully');
 94 | };
 95 | 
 96 | // Create the SonarQube client
 97 | const createDefaultClient = (): ISonarQubeClient => {
 98 |   // Validate environment variables
 99 |   validateEnvironmentVariables();
100 | 
101 |   // Create and return client
102 |   return createSonarQubeClientFromEnv();
103 | };
104 | 
105 | // Default client instance for backward compatibility
106 | // Created lazily to allow environment variable validation at runtime
107 | let defaultClient: ISonarQubeClient | null = null;
108 | 
109 | export const getDefaultClient = (): ISonarQubeClient => {
110 |   defaultClient ??= createDefaultClient();
111 |   return defaultClient;
112 | };
113 | 
114 | // Export for testing purposes
115 | export const resetDefaultClient = () => {
116 |   defaultClient = null;
117 | };
118 | 
```

--------------------------------------------------------------------------------
/src/schemas/common.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { z } from 'zod';
  2 | import { numberOrStringToString, parseJsonStringArray } from '../utils/transforms.js';
  3 | 
  4 | /**
  5 |  * Common schemas used across multiple domains
  6 |  */
  7 | 
  8 | // Severity schemas
  9 | export const severitySchema = z
 10 |   .enum(['INFO', 'MINOR', 'MAJOR', 'CRITICAL', 'BLOCKER'])
 11 |   .nullable()
 12 |   .optional();
 13 | 
 14 | export const severitiesSchema = z
 15 |   .union([z.array(z.enum(['INFO', 'MINOR', 'MAJOR', 'CRITICAL', 'BLOCKER'])), z.string()])
 16 |   .transform((val) => {
 17 |     const parsed = parseJsonStringArray(val);
 18 |     // Validate that all values are valid severities
 19 |     if (parsed && Array.isArray(parsed)) {
 20 |       return parsed.filter((v) => ['INFO', 'MINOR', 'MAJOR', 'CRITICAL', 'BLOCKER'].includes(v));
 21 |     }
 22 |     return parsed;
 23 |   })
 24 |   .nullable()
 25 |   .optional();
 26 | 
 27 | // Status schemas
 28 | export const statusSchema = z
 29 |   .union([z.array(z.enum(['OPEN', 'CONFIRMED', 'REOPENED', 'RESOLVED', 'CLOSED'])), z.string()])
 30 |   .transform((val) => {
 31 |     const parsed = parseJsonStringArray(val);
 32 |     // Validate that all values are valid statuses
 33 |     if (parsed && Array.isArray(parsed)) {
 34 |       return parsed.filter((v) =>
 35 |         ['OPEN', 'CONFIRMED', 'REOPENED', 'RESOLVED', 'CLOSED'].includes(v)
 36 |       );
 37 |     }
 38 |     return parsed;
 39 |   })
 40 |   .nullable()
 41 |   .optional();
 42 | 
 43 | // Resolution schemas
 44 | export const resolutionSchema = z
 45 |   .union([z.array(z.enum(['FALSE-POSITIVE', 'WONTFIX', 'FIXED', 'REMOVED'])), z.string()])
 46 |   .transform((val) => {
 47 |     const parsed = parseJsonStringArray(val);
 48 |     // Validate that all values are valid resolutions
 49 |     if (parsed && Array.isArray(parsed)) {
 50 |       return parsed.filter((v) => ['FALSE-POSITIVE', 'WONTFIX', 'FIXED', 'REMOVED'].includes(v));
 51 |     }
 52 |     return parsed;
 53 |   })
 54 |   .nullable()
 55 |   .optional();
 56 | 
 57 | // Type schemas
 58 | export const typeSchema = z
 59 |   .union([z.array(z.enum(['CODE_SMELL', 'BUG', 'VULNERABILITY', 'SECURITY_HOTSPOT'])), z.string()])
 60 |   .transform((val) => {
 61 |     const parsed = parseJsonStringArray(val);
 62 |     // Validate that all values are valid types
 63 |     if (parsed && Array.isArray(parsed)) {
 64 |       return parsed.filter((v) =>
 65 |         ['CODE_SMELL', 'BUG', 'VULNERABILITY', 'SECURITY_HOTSPOT'].includes(v)
 66 |       );
 67 |     }
 68 |     return parsed;
 69 |   })
 70 |   .nullable()
 71 |   .optional();
 72 | 
 73 | // Clean Code taxonomy schemas
 74 | export const cleanCodeAttributeCategoriesSchema = z
 75 |   .union([z.array(z.enum(['ADAPTABLE', 'CONSISTENT', 'INTENTIONAL', 'RESPONSIBLE'])), z.string()])
 76 |   .transform((val) => {
 77 |     const parsed = parseJsonStringArray(val);
 78 |     // Validate that all values are valid categories
 79 |     if (parsed && Array.isArray(parsed)) {
 80 |       return parsed.filter((v) =>
 81 |         ['ADAPTABLE', 'CONSISTENT', 'INTENTIONAL', 'RESPONSIBLE'].includes(v)
 82 |       );
 83 |     }
 84 |     return parsed;
 85 |   })
 86 |   .nullable()
 87 |   .optional();
 88 | 
 89 | export const impactSeveritiesSchema = z
 90 |   .union([z.array(z.enum(['HIGH', 'MEDIUM', 'LOW'])), z.string()])
 91 |   .transform((val) => {
 92 |     const parsed = parseJsonStringArray(val);
 93 |     // Validate that all values are valid impact severities
 94 |     if (parsed && Array.isArray(parsed)) {
 95 |       return parsed.filter((v) => ['HIGH', 'MEDIUM', 'LOW'].includes(v));
 96 |     }
 97 |     return parsed;
 98 |   })
 99 |   .nullable()
100 |   .optional();
101 | 
102 | export const impactSoftwareQualitiesSchema = z
103 |   .union([z.array(z.enum(['MAINTAINABILITY', 'RELIABILITY', 'SECURITY'])), z.string()])
104 |   .transform((val) => {
105 |     const parsed = parseJsonStringArray(val);
106 |     // Validate that all values are valid software qualities
107 |     if (parsed && Array.isArray(parsed)) {
108 |       return parsed.filter((v) => ['MAINTAINABILITY', 'RELIABILITY', 'SECURITY'].includes(v));
109 |     }
110 |     return parsed;
111 |   })
112 |   .nullable()
113 |   .optional();
114 | 
115 | // Pull request schema - accepts either string or number and converts to string
116 | export const pullRequestSchema = z
117 |   .union([z.string(), z.number()])
118 |   .optional()
119 |   .transform(numberOrStringToString);
120 | 
121 | // Pull request schema with nullable - for schemas that allow null values
122 | export const pullRequestNullableSchema = z
123 |   .union([z.string(), z.number()])
124 |   .nullable()
125 |   .optional()
126 |   .transform(numberOrStringToString);
127 | 
```

--------------------------------------------------------------------------------
/src/monitoring/metrics.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { createLogger } from '../utils/logger.js';
  2 | 
  3 | const logger = createLogger('MetricsService');
  4 | 
  5 | /**
  6 |  * Simplified metrics service for stdio-only transport
  7 |  * Tracks metrics in memory for circuit breaker and logging purposes
  8 |  */
  9 | export class MetricsService {
 10 |   private readonly metrics = new Map<string, number>();
 11 | 
 12 |   constructor() {
 13 |     logger.info('Metrics service initialized (stdio mode)');
 14 |   }
 15 | 
 16 |   /**
 17 |    * Record a SonarQube API request
 18 |    */
 19 |   recordSonarQubeRequest(endpoint: string, status: 'success' | 'error', duration: number): void {
 20 |     const key = `sonarqube.${endpoint}.${status}`;
 21 |     this.metrics.set(key, (this.metrics.get(key) || 0) + 1);
 22 | 
 23 |     logger.debug(`SonarQube API call to ${endpoint}: ${status} (${duration}s)`);
 24 |   }
 25 | 
 26 |   /**
 27 |    * Record a SonarQube API error
 28 |    */
 29 |   recordSonarQubeError(type: string, endpoint: string): void {
 30 |     const key = `sonarqube.error.${type}.${endpoint}`;
 31 |     this.metrics.set(key, (this.metrics.get(key) || 0) + 1);
 32 | 
 33 |     logger.warn(`SonarQube API error: ${type} on ${endpoint}`);
 34 |   }
 35 | 
 36 |   /**
 37 |    * Update circuit breaker state
 38 |    */
 39 |   updateCircuitBreakerState(service: string, state: 'closed' | 'open' | 'half-open'): void {
 40 |     let stateValue: number;
 41 |     if (state === 'open') {
 42 |       stateValue = 1;
 43 |     } else if (state === 'half-open') {
 44 |       stateValue = 0.5;
 45 |     } else {
 46 |       stateValue = 0;
 47 |     }
 48 |     this.metrics.set(`circuit-breaker.state.${service}`, stateValue);
 49 |     logger.info(`Circuit breaker ${service}: ${state}`);
 50 |   }
 51 | 
 52 |   /**
 53 |    * Record a circuit breaker failure
 54 |    */
 55 |   recordCircuitBreakerFailure(service: string): void {
 56 |     const key = `circuit-breaker.failure.${service}`;
 57 |     this.metrics.set(key, (this.metrics.get(key) || 0) + 1);
 58 | 
 59 |     logger.warn(`Circuit breaker failure: ${service}`);
 60 |   }
 61 | 
 62 |   /**
 63 |    * Get metrics in Prometheus format (for testing compatibility)
 64 |    */
 65 |   getMetrics(): string {
 66 |     const lines: string[] = [];
 67 | 
 68 |     for (const [key, value] of Array.from(this.metrics.entries())) {
 69 |       // Convert internal format to Prometheus format for tests
 70 |       if (key.startsWith('circuit-breaker.failure.')) {
 71 |         const service = key.replace('circuit-breaker.failure.', '');
 72 |         lines.push(`mcp_circuit_breaker_failures_total{service="${service}"} ${value}`);
 73 |       } else if (key.startsWith('circuit-breaker.state.')) {
 74 |         const service = key.replace('circuit-breaker.state.', '');
 75 |         lines.push(`mcp_circuit_breaker_state{service="${service}"} ${value}`);
 76 |       } else if (key.includes('sonarqube.')) {
 77 |         // Convert other metrics as needed
 78 |         lines.push(`# Internal metric: ${key} = ${value}`);
 79 |       }
 80 |     }
 81 | 
 82 |     return lines.join('\n');
 83 |   }
 84 | 
 85 |   /**
 86 |    * Stop monitoring (cleanup)
 87 |    */
 88 |   stopMonitoring(): void {
 89 |     // No-op for stdio mode
 90 |   }
 91 | }
 92 | 
 93 | // Singleton instance
 94 | let metricsService: MetricsService | null = null;
 95 | 
 96 | /**
 97 |  * Get or create the metrics service instance
 98 |  */
 99 | export function getMetricsService(): MetricsService {
100 |   metricsService ??= new MetricsService();
101 |   return metricsService;
102 | }
103 | 
104 | /**
105 |  * Cleanup the metrics service (for testing)
106 |  */
107 | export function cleanupMetricsService(): void {
108 |   if (metricsService) {
109 |     metricsService.stopMonitoring();
110 |     metricsService = null;
111 |   }
112 | }
113 | 
114 | /**
115 |  * Track a SonarQube API request
116 |  */
117 | export function trackSonarQubeRequest(
118 |   endpoint: string,
119 |   success: boolean,
120 |   duration: number,
121 |   errorType?: string
122 | ): void {
123 |   const metrics = getMetricsService();
124 |   metrics.recordSonarQubeRequest(endpoint, success ? 'success' : 'error', duration);
125 |   if (!success && errorType) {
126 |     metrics.recordSonarQubeError(errorType, endpoint);
127 |   }
128 | }
129 | 
130 | /**
131 |  * Update circuit breaker metrics
132 |  */
133 | export function updateCircuitBreakerMetrics(
134 |   service: string,
135 |   state: 'closed' | 'open' | 'half-open'
136 | ): void {
137 |   const metrics = getMetricsService();
138 |   metrics.updateCircuitBreakerState(service, state);
139 | }
140 | 
141 | /**
142 |  * Track circuit breaker failure
143 |  */
144 | export function trackCircuitBreakerFailure(service: string): void {
145 |   const metrics = getMetricsService();
146 |   metrics.recordCircuitBreakerFailure(service);
147 | }
148 | 
```
Page 2/11FirstPrevNextLast