@miocid152/sonarqube-mcp-server 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/PUBLISH.md ADDED
@@ -0,0 +1,49 @@
1
+ # Guía para Publicar en npm
2
+
3
+ ## 1. Iniciar sesión en npm
4
+ ```bash
5
+ npm login
6
+ ```
7
+ Te pedirá:
8
+ - Username
9
+ - Password
10
+ - Email
11
+ - OTP (si tienes 2FA activado)
12
+
13
+ ## 2. Verificar que estás logueado
14
+ ```bash
15
+ npm whoami
16
+ ```
17
+
18
+ ## 3. Verificar el package.json
19
+ ```bash
20
+ cat package.json
21
+ ```
22
+
23
+ ## 4. Publicar el paquete
24
+ ```bash
25
+ cd /Volumes/erick/Erick/proyectos/ProyectosEKT/sonarqube-mcp-server
26
+ npm publish
27
+ ```
28
+
29
+ ## 5. Verificar la publicación
30
+ ```bash
31
+ npm view sonarqube-mcp-server
32
+ ```
33
+
34
+ ## 6. Instalar globalmente desde npm
35
+ ```bash
36
+ npm install -g sonarqube-mcp-server
37
+ ```
38
+
39
+ ## Notas importantes:
40
+ - El nombre "sonarqube-mcp-server" debe estar disponible en npm
41
+ - Si ya existe, deberás cambiar el nombre en package.json
42
+ - La versión actual es 1.0.0
43
+ - Para actualizar: cambia la versión en package.json y ejecuta `npm publish` de nuevo
44
+
45
+ ## Si el nombre está ocupado:
46
+ Cambia el nombre en package.json a algo como:
47
+ - @tu-usuario/sonarqube-mcp-server (scoped package)
48
+ - sonarqube-mcp-server-ekt
49
+ - mcp-sonarqube-server
package/README.md ADDED
@@ -0,0 +1,82 @@
1
+ # SonarQube MCP Server
2
+
3
+ Servidor MCP (Model Context Protocol) para integración con SonarQube que permite obtener reglas de calidad y generar código conforme.
4
+
5
+ ## Instalación
6
+
7
+ ```bash
8
+ # Instalar globalmente
9
+ npm install -g sonarqube-mcp-server
10
+
11
+ # O instalar desde el directorio local
12
+ cd sonarqube-mcp-server
13
+ npm install -g .
14
+ ```
15
+
16
+ ## Configuración
17
+
18
+ Configurar variables de entorno:
19
+
20
+ ```bash
21
+ export SONAR_URL="https://sonar.tu-empresa.com"
22
+ export SONAR_TOKEN="tu_token_de_sonar"
23
+ ```
24
+
25
+ ## Uso
26
+
27
+ ### Ejecutar el servidor MCP
28
+
29
+ ```bash
30
+ sonarqube-mcp
31
+ ```
32
+
33
+ ### Herramientas disponibles
34
+
35
+ 1. **get_sonar_rules** - Obtener reglas activas
36
+ - `language`: Lenguaje de programación (java, javascript, python, etc.)
37
+ - `projectKey`: Clave del proyecto (opcional)
38
+
39
+ 2. **get_quality_profile** - Obtener perfil de calidad
40
+ - `projectKey`: Clave del proyecto
41
+
42
+ 3. **get_project_issues** - Obtener issues del proyecto
43
+ - `projectKey`: Clave del proyecto
44
+ - `resolved`: Incluir issues resueltos (default: false)
45
+
46
+ 4. **validate_code_rules** - Obtener reglas para validación
47
+ - `language`: Lenguaje de programación
48
+ - `severity`: Severidad mínima (INFO, MINOR, MAJOR, CRITICAL, BLOCKER)
49
+
50
+ ## Ejemplo de configuración en Claude Desktop
51
+
52
+ Agregar al archivo de configuración de Claude Desktop:
53
+
54
+ ```json
55
+ {
56
+ "mcpServers": {
57
+ "sonarqube": {
58
+ "command": "sonarqube-mcp",
59
+ "env": {
60
+ "SONAR_URL": "https://sonar.tu-empresa.com",
61
+ "SONAR_TOKEN": "tu_token_aqui"
62
+ }
63
+ }
64
+ }
65
+ }
66
+ ```
67
+
68
+ ## Obtener Token de SonarQube
69
+
70
+ 1. Ir a SonarQube → User → My Account → Security
71
+ 2. Generar nuevo token
72
+ 3. Copiar el token generado
73
+
74
+ ## Desarrollo
75
+
76
+ ```bash
77
+ # Instalar dependencias
78
+ npm install
79
+
80
+ # Ejecutar en modo desarrollo
81
+ npm start
82
+ ```
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { SonarQubeMCPServer } = require('../index.js');
4
+
5
+ async function main() {
6
+ const sonarUrl = process.env.SONAR_URL;
7
+ const sonarToken = process.env.SONAR_TOKEN;
8
+
9
+ if (!sonarUrl || !sonarToken) {
10
+ console.error('Error: SONAR_URL and SONAR_TOKEN environment variables are required');
11
+ console.error('Usage: SONAR_URL=https://sonar.company.com SONAR_TOKEN=your_token sonarqube-mcp');
12
+ process.exit(1);
13
+ }
14
+
15
+ const server = new SonarQubeMCPServer(sonarUrl, sonarToken);
16
+ await server.start();
17
+ }
18
+
19
+ main().catch(console.error);
package/index.js ADDED
@@ -0,0 +1,327 @@
1
+ const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
2
+ const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
3
+ const {
4
+ CallToolRequestSchema,
5
+ ListToolsRequestSchema,
6
+ } = require('@modelcontextprotocol/sdk/types.js');
7
+
8
+ class SonarQubeMCPServer {
9
+ constructor(sonarUrl, sonarToken) {
10
+ this.sonarUrl = sonarUrl;
11
+ this.sonarToken = sonarToken;
12
+ this.server = new Server(
13
+ {
14
+ name: 'sonarqube-mcp-server',
15
+ version: '1.0.0',
16
+ },
17
+ {
18
+ capabilities: {
19
+ tools: {},
20
+ },
21
+ }
22
+ );
23
+
24
+ this.setupHandlers();
25
+ }
26
+
27
+ setupHandlers() {
28
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
29
+ tools: [
30
+ {
31
+ name: 'get_sonar_rules',
32
+ description: 'Get SonarQube rules with pagination. Use multiple calls with different page numbers to get all rules and avoid character limits. Returns page info to guide next calls.',
33
+ inputSchema: {
34
+ type: 'object',
35
+ properties: {
36
+ language: {
37
+ type: 'string',
38
+ description: 'Programming language (java, javascript, python, etc.)',
39
+ default: 'java'
40
+ },
41
+ page: {
42
+ type: 'number',
43
+ description: 'Page number (1-based)',
44
+ default: 1
45
+ },
46
+ pageSize: {
47
+ type: 'number',
48
+ description: 'Number of rules per page (max 100)',
49
+ default: 50
50
+ },
51
+ projectKey: {
52
+ type: 'string',
53
+ description: 'SonarQube project key (optional)'
54
+ }
55
+ },
56
+ required: ['language']
57
+ }
58
+ },
59
+ {
60
+ name: 'get_quality_profile',
61
+ description: 'Get quality profile for a project',
62
+ inputSchema: {
63
+ type: 'object',
64
+ properties: {
65
+ projectKey: {
66
+ type: 'string',
67
+ description: 'SonarQube project key'
68
+ }
69
+ },
70
+ required: ['projectKey']
71
+ }
72
+ },
73
+ {
74
+ name: 'get_project_issues',
75
+ description: 'Get issues for a project',
76
+ inputSchema: {
77
+ type: 'object',
78
+ properties: {
79
+ projectKey: {
80
+ type: 'string',
81
+ description: 'SonarQube project key'
82
+ },
83
+ resolved: {
84
+ type: 'boolean',
85
+ description: 'Include resolved issues',
86
+ default: false
87
+ }
88
+ },
89
+ required: ['projectKey']
90
+ }
91
+ },
92
+ {
93
+ name: 'validate_code_rules',
94
+ description: 'Get rule violations for code validation',
95
+ inputSchema: {
96
+ type: 'object',
97
+ properties: {
98
+ language: {
99
+ type: 'string',
100
+ description: 'Programming language',
101
+ default: 'java'
102
+ },
103
+ severity: {
104
+ type: 'string',
105
+ description: 'Minimum severity (INFO, MINOR, MAJOR, CRITICAL, BLOCKER)',
106
+ default: 'MAJOR'
107
+ }
108
+ },
109
+ required: ['language']
110
+ }
111
+ }
112
+ ]
113
+ }));
114
+
115
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
116
+ const { name, arguments: args } = request.params;
117
+
118
+ try {
119
+ switch (name) {
120
+ case 'get_sonar_rules':
121
+ return await this.getSonarRulesPage(args.language, args.page || 1, args.pageSize || 50, args.projectKey);
122
+
123
+ case 'get_quality_profile':
124
+ return await this.getQualityProfile(args.projectKey);
125
+
126
+ case 'get_project_issues':
127
+ return await this.getProjectIssues(args.projectKey, args.resolved);
128
+
129
+ case 'validate_code_rules':
130
+ return await this.getValidationRules(args.language, args.severity);
131
+
132
+ default:
133
+ throw new Error(`Unknown tool: ${name}`);
134
+ }
135
+ } catch (error) {
136
+ return {
137
+ content: [
138
+ {
139
+ type: 'text',
140
+ text: `Error: ${error.message}`
141
+ }
142
+ ]
143
+ };
144
+ }
145
+ });
146
+ }
147
+
148
+ async makeRequest(endpoint, params = {}) {
149
+ const url = new URL(`${this.sonarUrl}/api/${endpoint}`);
150
+ Object.entries(params).forEach(([key, value]) => {
151
+ if (value !== undefined) url.searchParams.append(key, value);
152
+ });
153
+
154
+ console.error(`Making request to: ${url.toString()}`);
155
+
156
+ const response = await fetch(url, {
157
+ headers: {
158
+ 'Authorization': `Bearer ${this.sonarToken}`,
159
+ 'Accept': 'application/json'
160
+ }
161
+ });
162
+
163
+ if (!response.ok) {
164
+ throw new Error(`SonarQube API error: ${response.status} ${response.statusText}`);
165
+ }
166
+
167
+ return await response.json();
168
+ }
169
+
170
+ async getQualityProfile(projectKey) {
171
+ const profile = await this.makeRequest('qualityprofiles/search', { project: projectKey });
172
+
173
+ return {
174
+ content: [
175
+ {
176
+ type: 'text',
177
+ text: JSON.stringify(profile, null, 2)
178
+ }
179
+ ]
180
+ };
181
+ }
182
+
183
+ async getSonarRulesPage(language, page = 1, pageSize = 50, projectKey) {
184
+ const languageMap = {
185
+ 'python': 'py',
186
+ 'javascript': 'js',
187
+ 'typescript': 'ts',
188
+ 'java': 'java',
189
+ 'csharp': 'cs',
190
+ 'cpp': 'cpp',
191
+ 'c': 'c'
192
+ };
193
+
194
+ const sonarLanguage = languageMap[language] || language;
195
+ const actualPageSize = Math.min(pageSize, 100);
196
+
197
+ const params = {
198
+ activation: 'true',
199
+ languages: sonarLanguage,
200
+ ps: actualPageSize,
201
+ p: page
202
+ };
203
+
204
+ if (projectKey) {
205
+ const profile = await this.makeRequest('qualityprofiles/search', { project: projectKey });
206
+ if (profile.profiles?.length > 0) {
207
+ const langProfile = profile.profiles.find(p => p.language === sonarLanguage);
208
+ if (langProfile) {
209
+ params.qprofile = langProfile.key;
210
+ }
211
+ }
212
+ }
213
+
214
+ const rules = await this.makeRequest('rules/search', params);
215
+ const totalPages = Math.ceil(rules.total / actualPageSize);
216
+
217
+ return {
218
+ content: [
219
+ {
220
+ type: 'text',
221
+ text: JSON.stringify({
222
+ total: rules.total,
223
+ language: language,
224
+ projectKey: projectKey,
225
+ page: page,
226
+ pageSize: actualPageSize,
227
+ totalPages: totalPages,
228
+ hasMore: page < totalPages,
229
+ rules: rules.rules.map(rule => ({
230
+ key: rule.key,
231
+ name: rule.name,
232
+ severity: rule.severity,
233
+ type: rule.type,
234
+ tags: rule.tags
235
+ }))
236
+ }, null, 2)
237
+ }
238
+ ]
239
+ };
240
+ }
241
+
242
+ async getProjectIssues(projectKey, resolved = false) {
243
+ const issues = await this.makeRequest('issues/search', {
244
+ componentKeys: projectKey,
245
+ resolved: resolved.toString(),
246
+ ps: 100
247
+ });
248
+
249
+ return {
250
+ content: [
251
+ {
252
+ type: 'text',
253
+ text: JSON.stringify({
254
+ total: issues.total,
255
+ issues: issues.issues.map(issue => ({
256
+ key: issue.key,
257
+ rule: issue.rule,
258
+ severity: issue.severity,
259
+ component: issue.component,
260
+ line: issue.line,
261
+ message: issue.message,
262
+ status: issue.status,
263
+ type: issue.type
264
+ }))
265
+ }, null, 2)
266
+ }
267
+ ]
268
+ };
269
+ }
270
+
271
+ async getValidationRules(language, severity = 'MAJOR') {
272
+ const severityLevels = ['INFO', 'MINOR', 'MAJOR', 'CRITICAL', 'BLOCKER'];
273
+ const minSeverityIndex = severityLevels.indexOf(severity);
274
+
275
+ // Mapear lenguajes a códigos de SonarQube
276
+ const languageMap = {
277
+ 'python': 'py',
278
+ 'javascript': 'js',
279
+ 'typescript': 'ts',
280
+ 'java': 'java',
281
+ 'csharp': 'cs',
282
+ 'cpp': 'cpp',
283
+ 'c': 'c'
284
+ };
285
+
286
+ const sonarLanguage = languageMap[language] || language;
287
+
288
+ const rules = await this.makeRequest('rules/search', {
289
+ activation: 'true',
290
+ languages: sonarLanguage,
291
+ ps: 500
292
+ });
293
+
294
+ const filteredRules = rules.rules
295
+ .filter(rule => severityLevels.indexOf(rule.severity) >= minSeverityIndex)
296
+ .map(rule => ({
297
+ key: rule.key,
298
+ name: rule.name,
299
+ severity: rule.severity,
300
+ type: rule.type,
301
+ description: rule.htmlDesc || rule.mdDesc,
302
+ tags: rule.tags
303
+ }));
304
+
305
+ return {
306
+ content: [
307
+ {
308
+ type: 'text',
309
+ text: JSON.stringify({
310
+ language: language,
311
+ minimumSeverity: severity,
312
+ totalRules: filteredRules.length,
313
+ rules: filteredRules
314
+ }, null, 2)
315
+ }
316
+ ]
317
+ };
318
+ }
319
+
320
+ async start() {
321
+ const transport = new StdioServerTransport();
322
+ await this.server.connect(transport);
323
+ console.error('SonarQube MCP Server started');
324
+ }
325
+ }
326
+
327
+ module.exports = { SonarQubeMCPServer };
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@miocid152/sonarqube-mcp-server",
3
+ "version": "1.0.0",
4
+ "description": "MCP Server for SonarQube integration",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "sonarqube-mcp": "./bin/sonarqube-mcp.js"
8
+ },
9
+ "scripts": {
10
+ "start": "node index.js",
11
+ "test": "echo \"Error: no test specified\" && exit 1"
12
+ },
13
+ "keywords": ["mcp", "sonarqube", "code-quality"],
14
+ "author": "Your Name",
15
+ "license": "MIT",
16
+ "dependencies": {
17
+ "@modelcontextprotocol/sdk": "^0.4.0",
18
+ "node-fetch": "^3.3.2"
19
+ },
20
+ "engines": {
21
+ "node": ">=18.0.0"
22
+ },
23
+ "preferGlobal": true
24
+ }