sonarqube-issue-mcp 0.0.1

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.
@@ -0,0 +1,411 @@
1
+ import { Buffer } from "node:buffer";
2
+ import { ProxyAgent } from "undici";
3
+ import { SonarQubeMcpError } from "../errors.js";
4
+ import { withProjectContext } from "../project-ref.js";
5
+ /**
6
+ * 按代理地址缓存 `ProxyAgent`,避免重复构造连接器。
7
+ *
8
+ * @remarks
9
+ * 同一个进程内多个项目查询可复用同一代理配置。
10
+ */
11
+ const proxyAgentCache = new Map();
12
+ /**
13
+ * SonarQube Web API 访问层。
14
+ *
15
+ * @remarks
16
+ * 这一层只做“请求、分页、鉴权、错误映射、轻量缓存”,不承担业务标准化职责。
17
+ */
18
+ export class SonarQubeClient {
19
+ projectRef;
20
+ options;
21
+ /** 规则详情缓存,避免列表和详情阶段重复拉取同一 rule。 */
22
+ ruleCache = new Map();
23
+ dispatcher;
24
+ constructor(projectRef, options) {
25
+ this.projectRef = projectRef;
26
+ this.options = options;
27
+ this.dispatcher = getDispatcher(options.httpProxy);
28
+ }
29
+ /**
30
+ * 获取 SonarQube 服务版本,用于预检和调试输出。
31
+ *
32
+ * @returns SonarQube 版本字符串。
33
+ * @throws {SonarQubeMcpError} 当请求失败时抛出。
34
+ */
35
+ async getServerVersion() {
36
+ return this.requestText("/api/server/version");
37
+ }
38
+ /**
39
+ * 校验并读取项目组件基础信息。
40
+ *
41
+ * @returns 项目组件信息。
42
+ * @throws {SonarQubeMcpError} 当项目不存在或无法访问时抛出。
43
+ */
44
+ async getProjectComponent() {
45
+ const searchParams = withProjectContext(new URLSearchParams({
46
+ component: this.projectRef.projectKey
47
+ }), this.projectRef);
48
+ const response = await this.requestJson("/api/components/show", searchParams);
49
+ return response.component;
50
+ }
51
+ /**
52
+ * 拉取指定 issue 类型的全部“当前未解决”问题。
53
+ *
54
+ * @param type - issue 类型,仅支持 `BUG` 和 `VULNERABILITY`。
55
+ * @returns 问题列表以及列表接口内嵌的规则摘要缓存。
56
+ *
57
+ * @remarks
58
+ * 当前 MCP 口径固定为:
59
+ * - Security: `VULNERABILITY + resolved=false`
60
+ * - Reliability: `BUG + resolved=false`
61
+ *
62
+ * @throws {SonarQubeMcpError} 当请求失败或响应异常时抛出。
63
+ */
64
+ async searchOpenIssues(type) {
65
+ const issues = [];
66
+ const embeddedRules = new Map();
67
+ let page = 1;
68
+ while (true) {
69
+ const searchParams = withProjectContext(new URLSearchParams({
70
+ components: this.projectRef.projectKey,
71
+ types: type,
72
+ resolved: "false",
73
+ additionalFields: "_all",
74
+ ps: "500",
75
+ p: String(page)
76
+ }), this.projectRef);
77
+ const response = await this.requestJson("/api/issues/search", searchParams);
78
+ issues.push(...response.issues);
79
+ // 列表接口会内嵌一部分 rule 摘要,先缓存起来,后续如缺详细描述再补全。
80
+ for (const rule of response.rules ?? []) {
81
+ embeddedRules.set(rule.key, rule);
82
+ this.ruleCache.set(rule.key, rule);
83
+ }
84
+ if (issues.length >= response.paging.total) {
85
+ break;
86
+ }
87
+ page += 1;
88
+ }
89
+ return { issues, embeddedRules };
90
+ }
91
+ /**
92
+ * 通过 issue key 获取单条 issue。
93
+ *
94
+ * @param issueKey - SonarQube issue key。
95
+ * @returns 单条 issue 及其可能内嵌的规则摘要。
96
+ * @throws {SonarQubeMcpError} 当 issue 不存在或请求失败时抛出。
97
+ */
98
+ async getIssueByKey(issueKey) {
99
+ const searchParams = withProjectContext(new URLSearchParams({
100
+ issues: issueKey,
101
+ additionalFields: "_all"
102
+ }), this.projectRef);
103
+ const response = await this.requestJson("/api/issues/search", searchParams);
104
+ const issue = response.issues[0];
105
+ if (!issue) {
106
+ throw new SonarQubeMcpError("NOT_FOUND", `未找到 issue: ${issueKey}`, {
107
+ status: 404
108
+ });
109
+ }
110
+ const embeddedRule = response.rules?.find((rule) => rule.key === issue.rule) ?? null;
111
+ if (embeddedRule) {
112
+ this.ruleCache.set(embeddedRule.key, embeddedRule);
113
+ }
114
+ return { issue, embeddedRule };
115
+ }
116
+ /**
117
+ * 获取单条 issue 的 changelog。
118
+ *
119
+ * @param issueKey - SonarQube issue key。
120
+ * @returns changelog 数组。
121
+ * @throws {SonarQubeMcpError} 当请求失败时抛出。
122
+ */
123
+ async getIssueChangelog(issueKey) {
124
+ const response = await this.requestJson("/api/issues/changelog", new URLSearchParams({ issue: issueKey }));
125
+ return response.changelog;
126
+ }
127
+ /**
128
+ * 拉取全部待复核的 Security Hotspots。
129
+ *
130
+ * @returns hotspot 摘要列表。
131
+ * @throws {SonarQubeMcpError} 当请求失败或响应异常时抛出。
132
+ */
133
+ async searchOpenHotspots() {
134
+ const hotspots = [];
135
+ let page = 1;
136
+ while (true) {
137
+ const searchParams = withProjectContext(new URLSearchParams({
138
+ project: this.projectRef.projectKey,
139
+ status: "TO_REVIEW",
140
+ ps: "500",
141
+ p: String(page)
142
+ }), this.projectRef);
143
+ const response = await this.requestJson("/api/hotspots/search", searchParams);
144
+ hotspots.push(...response.hotspots);
145
+ if (hotspots.length >= response.paging.total) {
146
+ break;
147
+ }
148
+ page += 1;
149
+ }
150
+ return hotspots;
151
+ }
152
+ /**
153
+ * 按 key 获取单条 hotspot 详情。
154
+ *
155
+ * @param hotspotKey - SonarQube hotspot key。
156
+ * @returns hotspot 详情。
157
+ * @throws {SonarQubeMcpError} 当请求失败时抛出。
158
+ */
159
+ async getHotspotDetail(hotspotKey) {
160
+ return this.requestJson("/api/hotspots/show", new URLSearchParams({ hotspot: hotspotKey }));
161
+ }
162
+ /**
163
+ * 获取规则详情。
164
+ *
165
+ * @param ruleKey - SonarQube 规则 key。
166
+ * @returns 规则详情或合并后的完整规则摘要。
167
+ *
168
+ * @remarks
169
+ * 如果缓存里已有完整描述,则直接复用;否则继续调用 `/api/rules/show` 补全。
170
+ *
171
+ * @throws {SonarQubeMcpError} 当请求失败时抛出。
172
+ */
173
+ async getRule(ruleKey) {
174
+ const cached = this.ruleCache.get(ruleKey);
175
+ if (cached?.name && (cached.mdDesc || cached.htmlDesc)) {
176
+ return cached;
177
+ }
178
+ const response = await this.requestJson("/api/rules/show", new URLSearchParams({ key: ruleKey }));
179
+ const mergedRule = {
180
+ ...cached,
181
+ ...response.rule
182
+ };
183
+ this.ruleCache.set(ruleKey, mergedRule);
184
+ return mergedRule;
185
+ }
186
+ /**
187
+ * 请求 JSON 接口并完成解析。
188
+ *
189
+ * @param path - API 路径。
190
+ * @param searchParams - 可选查询参数。
191
+ * @returns 解析后的 JSON 结构。
192
+ */
193
+ async requestJson(path, searchParams) {
194
+ const response = await this.request(path, searchParams);
195
+ return this.parseJson(response, path);
196
+ }
197
+ /**
198
+ * 请求纯文本接口。
199
+ *
200
+ * @param path - API 路径。
201
+ * @param searchParams - 可选查询参数。
202
+ * @returns 去首尾空白后的文本响应。
203
+ */
204
+ async requestText(path, searchParams) {
205
+ const response = await this.request(path, searchParams);
206
+ return (await response.text()).trim();
207
+ }
208
+ /**
209
+ * 底层 HTTP 请求封装。
210
+ *
211
+ * @param path - API 路径。
212
+ * @param searchParams - 可选查询参数。
213
+ * @returns 成功的原始 `Response` 对象。
214
+ *
215
+ * @remarks
216
+ * 这里统一处理:
217
+ * - Basic token 鉴权
218
+ * - 可选 HTTP 代理
219
+ * - 超时控制
220
+ * - 可重试状态码
221
+ * - SonarQube 错误映射
222
+ *
223
+ * @throws {SonarQubeMcpError} 当请求最终失败时抛出。
224
+ */
225
+ async request(path, searchParams) {
226
+ const url = new URL(path, this.projectRef.origin);
227
+ if (searchParams) {
228
+ url.search = searchParams.toString();
229
+ }
230
+ let attempt = 0;
231
+ while (true) {
232
+ const controller = new AbortController();
233
+ const timeout = setTimeout(() => controller.abort(), this.options.requestTimeoutMs);
234
+ try {
235
+ const requestInit = {
236
+ method: "GET",
237
+ headers: {
238
+ Accept: "application/json",
239
+ Authorization: `Basic ${Buffer.from(`${this.options.token}:`).toString("base64")}`
240
+ },
241
+ signal: controller.signal
242
+ };
243
+ // Node 内置 fetch 的 dispatcher 类型与直接引入的 undici 类型并不完全一致,
244
+ // 这里在调用点做收口,避免把类型耦合扩散到外部代码。
245
+ const response = await fetch(url, (this.dispatcher
246
+ ? {
247
+ ...requestInit,
248
+ dispatcher: this.dispatcher
249
+ }
250
+ : requestInit));
251
+ clearTimeout(timeout);
252
+ if (response.ok) {
253
+ return response;
254
+ }
255
+ const error = await this.buildHttpError(response, url.toString());
256
+ if (this.shouldRetry(response.status, attempt)) {
257
+ attempt += 1;
258
+ continue;
259
+ }
260
+ throw error;
261
+ }
262
+ catch (error) {
263
+ clearTimeout(timeout);
264
+ if (error instanceof SonarQubeMcpError) {
265
+ throw error;
266
+ }
267
+ if (this.shouldRetryForException(error, attempt)) {
268
+ attempt += 1;
269
+ continue;
270
+ }
271
+ throw new SonarQubeMcpError("NETWORK", `请求 SonarQube 失败: ${url}`, {
272
+ cause: error
273
+ });
274
+ }
275
+ }
276
+ }
277
+ /**
278
+ * 判断当前 HTTP 状态是否值得重试。
279
+ *
280
+ * @param status - HTTP 状态码。
281
+ * @param attempt - 当前已重试次数。
282
+ * @returns 是否继续重试。
283
+ */
284
+ shouldRetry(status, attempt) {
285
+ if (attempt >= this.options.retryCount) {
286
+ return false;
287
+ }
288
+ return status === 408 || status === 429 || status === 502 || status === 504;
289
+ }
290
+ /**
291
+ * 判断当前异常是否属于可重试的网络层失败。
292
+ *
293
+ * @param error - 原始异常。
294
+ * @param attempt - 当前已重试次数。
295
+ * @returns 是否继续重试。
296
+ */
297
+ shouldRetryForException(error, attempt) {
298
+ if (attempt >= this.options.retryCount) {
299
+ return false;
300
+ }
301
+ return error instanceof Error && error.name !== "AbortError";
302
+ }
303
+ /**
304
+ * 将 HTTP 错误响应映射为统一的 MCP 错误类型。
305
+ *
306
+ * @param response - 失败的 HTTP 响应。
307
+ * @param requestUrl - 便于排查的完整请求地址。
308
+ * @returns 统一错误对象。
309
+ */
310
+ async buildHttpError(response, requestUrl) {
311
+ const payload = await this.parseErrorPayload(response);
312
+ const message = payload.message || `SonarQube 返回了错误响应: ${response.status}`;
313
+ if (response.status === 401) {
314
+ return new SonarQubeMcpError("AUTH", message, {
315
+ status: response.status,
316
+ details: payload.details
317
+ });
318
+ }
319
+ if (response.status === 403) {
320
+ return new SonarQubeMcpError("FORBIDDEN", message, {
321
+ status: response.status,
322
+ details: payload.details
323
+ });
324
+ }
325
+ if (response.status === 404) {
326
+ return new SonarQubeMcpError("NOT_FOUND", message, {
327
+ status: response.status,
328
+ details: payload.details
329
+ });
330
+ }
331
+ if (response.status === 503) {
332
+ return new SonarQubeMcpError("INDEXING", `SonarQube 当前不可用或仍在重建索引,请稍后重试。请求地址: ${requestUrl}`, {
333
+ status: response.status,
334
+ details: payload.details
335
+ });
336
+ }
337
+ return new SonarQubeMcpError("REMOTE", message, {
338
+ status: response.status,
339
+ details: payload.details
340
+ });
341
+ }
342
+ /**
343
+ * 解析 SonarQube JSON 响应,并在结构不合法时抛出统一错误。
344
+ *
345
+ * @param response - 原始 HTTP 响应。
346
+ * @param path - 当前请求路径,仅用于错误提示。
347
+ * @returns 解析后的 JSON 结构。
348
+ * @throws {SonarQubeMcpError} 当响应不是合法 JSON 时抛出。
349
+ */
350
+ async parseJson(response, path) {
351
+ try {
352
+ return (await response.json());
353
+ }
354
+ catch (error) {
355
+ throw new SonarQubeMcpError("REMOTE", `SonarQube 返回了无法解析的 JSON: ${path}`, {
356
+ cause: error
357
+ });
358
+ }
359
+ }
360
+ /**
361
+ * 尽量把 SonarQube 的错误体提炼成稳定 message + details 结构。
362
+ *
363
+ * @param response - 原始 HTTP 响应。
364
+ * @returns 规范化后的错误 message 与 details。
365
+ */
366
+ async parseErrorPayload(response) {
367
+ const contentType = response.headers.get("content-type") ?? "";
368
+ if (contentType.includes("application/json")) {
369
+ try {
370
+ const json = (await response.json());
371
+ const message = json.errors?.map((item) => item.msg).filter(Boolean).join("; ") ||
372
+ response.statusText ||
373
+ "请求失败";
374
+ return {
375
+ message,
376
+ details: json
377
+ };
378
+ }
379
+ catch {
380
+ return {
381
+ message: response.statusText || "请求失败",
382
+ details: null
383
+ };
384
+ }
385
+ }
386
+ const text = (await response.text()).trim();
387
+ return {
388
+ message: text || response.statusText || "请求失败",
389
+ details: text || null
390
+ };
391
+ }
392
+ }
393
+ /**
394
+ * 复用同一代理地址的 ProxyAgent,减少连接开销。
395
+ *
396
+ * @param httpProxy - 代理地址。
397
+ * @returns 对应的 `ProxyAgent`;如果未配置代理则返回 `null`。
398
+ */
399
+ function getDispatcher(httpProxy) {
400
+ if (!httpProxy) {
401
+ return null;
402
+ }
403
+ const cached = proxyAgentCache.get(httpProxy);
404
+ if (cached) {
405
+ return cached;
406
+ }
407
+ const dispatcher = new ProxyAgent(httpProxy);
408
+ proxyAgentCache.set(httpProxy, dispatcher);
409
+ return dispatcher;
410
+ }
411
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/sonarqube/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,106 @@
1
+ import type { AppConfig } from "../config.js";
2
+ import type { DetailLevel, FindingDetail, ProjectFindingsResult } from "../types.js";
3
+ /**
4
+ * SonarQube 业务服务层。
5
+ *
6
+ * @remarks
7
+ * 这一层负责把原始 SonarQube API 数据重新组装为 MCP 对外返回结构,
8
+ * 包括查询口径、规则补全、排序规则、详情标准化和项目归属校验。
9
+ */
10
+ export declare class SonarQubeFindingService {
11
+ private readonly config;
12
+ constructor(config: AppConfig);
13
+ /**
14
+ * 获取项目级三类问题汇总。
15
+ *
16
+ * @param projectUrl - SonarQube 项目 dashboard URL。
17
+ * @param detailLevel - 返回详情级别,默认 `standard`。
18
+ * @returns 已标准化并排序的项目问题汇总。
19
+ *
20
+ * @remarks
21
+ * 这里会并行完成版本检查、项目检查、issue 列表与 hotspot 列表拉取,
22
+ * 然后统一做规则补充和结果标准化。
23
+ *
24
+ * @throws {SonarQubeMcpError} 当 URL 非法、项目不可访问或任一 API 请求失败时抛出。
25
+ */
26
+ getProjectFindings(projectUrl: string, detailLevel?: DetailLevel): Promise<ProjectFindingsResult>;
27
+ /**
28
+ * 获取单条问题详情。
29
+ *
30
+ * @param projectUrl - SonarQube 项目 dashboard URL。
31
+ * @param kind - 详情类型,`issue` 或 `hotspot`。
32
+ * @param key - SonarQube 侧的问题 key。
33
+ * @returns 单条问题的标准化详情。
34
+ *
35
+ * @remarks
36
+ * 详情接口会再次校验问题是否属于当前项目,避免误传别的项目 key 时串数据。
37
+ *
38
+ * @throws {SonarQubeMcpError}
39
+ * 当项目不匹配、问题类型不在支持范围内或底层请求失败时抛出。
40
+ */
41
+ getFindingDetail(projectUrl: string, kind: "issue" | "hotspot", key: string): Promise<FindingDetail>;
42
+ /**
43
+ * 基于全局配置为当前项目创建隔离的 SonarQube client。
44
+ *
45
+ * @param projectRef - 当前项目上下文。
46
+ * @returns 已绑定项目上下文的 SonarQube client。
47
+ */
48
+ private createClient;
49
+ /**
50
+ * 组装对外暴露的项目基础信息。
51
+ *
52
+ * @param projectRef - 当前项目上下文。
53
+ * @param projectName - SonarQube 返回的项目名。
54
+ * @param qualifier - SonarQube 组件限定符。
55
+ * @param serverVersion - SonarQube 服务版本。
56
+ * @returns 对外返回的项目基础信息。
57
+ */
58
+ private buildProjectInfo;
59
+ /**
60
+ * 批量获取并缓存规则详情。
61
+ *
62
+ * @param client - SonarQube client。
63
+ * @param keys - 待查询的规则 key 列表。
64
+ * @returns 规则 key 到规则详情的映射。
65
+ */
66
+ private getRulesByKey;
67
+ /**
68
+ * 规则详情补充失败时,尽量回落到列表接口内嵌的规则摘要。
69
+ *
70
+ * @param client - SonarQube client。
71
+ * @param ruleKey - 规则 key。
72
+ * @param embeddedRule - 列表接口中已拿到的规则摘要。
73
+ * @returns 完整规则详情或回退摘要。
74
+ */
75
+ private getRuleOrFallback;
76
+ /**
77
+ * 将 SonarQube issue 标准化为统一 `FindingSummary`。
78
+ *
79
+ * @param issue - 原始 issue。
80
+ * @param rule - 已补全的规则信息。
81
+ * @param projectRef - 当前项目上下文。
82
+ * @param category - 目标分类。
83
+ * @param detailLevel - 返回详情级别。
84
+ * @returns 统一结构的问题摘要。
85
+ */
86
+ private normalizeIssue;
87
+ /**
88
+ * 将 hotspot 列表/详情合并后标准化为统一 `FindingSummary`。
89
+ *
90
+ * @param hotspotDetail - hotspot 详情;如果调用方尚未获取详情则可为 `null`。
91
+ * @param hotspotSearch - hotspot 列表摘要;如果调用方只持有详情则可为 `null`。
92
+ * @param rule - 已补全的规则信息。
93
+ * @param projectRef - 当前项目上下文。
94
+ * @param detailLevel - 返回详情级别。
95
+ * @returns 统一结构的热点摘要。
96
+ * @throws {SonarQubeMcpError} 当关键字段缺失时抛出。
97
+ */
98
+ private normalizeHotspot;
99
+ /**
100
+ * issue 优先使用 impacts 推导的业务严重度,没有时再回退到旧 severity。
101
+ *
102
+ * @param issue - 原始 issue。
103
+ * @returns 统一后的严重度字符串。
104
+ */
105
+ private pickIssueSeverity;
106
+ }