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.
package/README.md ADDED
@@ -0,0 +1,246 @@
1
+ # SonarQube Issue MCP
2
+
3
+ 基于 SonarQube Web API 的 MCP 服务。你只需要传入一个 SonarQube 项目 dashboard URL,它就会返回当前待处理的:
4
+
5
+ - `Security` 问题
6
+ - `Reliability` 问题
7
+ - `Security Hotspots`
8
+
9
+ ## MCP 配置示例
10
+
11
+ 这是一个 `stdio` MCP server,不是终端交互式 CLI。
12
+
13
+ - 正常使用方式是让 MCP 客户端拉起它
14
+ - 手工执行命令只适合调试,不会直接返回 issue 列表
15
+ - 运行环境需要 Node.js `>= 20`
16
+ - 机器需要满足以下任一条件:能直连公司内网 SonarQube、已经连上 VPN,或已通过 `SONAR_HTTP_PROXY` 配置可用代理
17
+
18
+ ### 方式一:使用 `npx`
19
+
20
+ 适合:
21
+
22
+ - 想直接接入,不想全局安装
23
+ - 希望开箱即用
24
+ - 可以接受首次启动会从 npm 拉包
25
+
26
+ ```json
27
+ {
28
+ "mcpServers": {
29
+ "sonarqube-issue": {
30
+ "command": "npx",
31
+ "args": [
32
+ "-y",
33
+ "sonarqube-issue-mcp@0.1.0"
34
+ ],
35
+ "env": {
36
+ "SONAR_TOKEN": "your_sonar_token",
37
+ "SONAR_HTTP_PROXY": "http://127.0.0.1:7890"
38
+ }
39
+ }
40
+ }
41
+ }
42
+ ```
43
+
44
+ 说明:
45
+
46
+ - 推荐固定版本,不要长期使用裸 `latest`
47
+ - `SONAR_HTTP_PROXY` 是可选的,只在 SonarQube API 需要走代理时配置
48
+ - `npx` 首次启动会更慢,也依赖 npm registry 可用性
49
+
50
+ ### 方式二:全局安装后的命令
51
+
52
+ 适合:
53
+
54
+ - 长期稳定在本机使用
55
+ - 希望启动更快
56
+ - 想避免每次启动都解析 `npx`
57
+
58
+ 先安装:
59
+
60
+ ```bash
61
+ npm install -g sonarqube-issue-mcp
62
+ ```
63
+
64
+ 再在 MCP 客户端中配置:
65
+
66
+ ```json
67
+ {
68
+ "mcpServers": {
69
+ "sonarqube-issue": {
70
+ "command": "sonarqube-issue-mcp",
71
+ "env": {
72
+ "SONAR_TOKEN": "your_sonar_token",
73
+ "SONAR_HTTP_PROXY": "http://127.0.0.1:7890"
74
+ }
75
+ }
76
+ }
77
+ }
78
+ ```
79
+
80
+ ### 方式三:本地源码构建后的 `node dist/index.js`
81
+
82
+ 适合:
83
+
84
+ - 你正在本地开发这个项目
85
+ - 想用本地修改后的代码直接接入 MCP 客户端
86
+
87
+ ```json
88
+ {
89
+ "mcpServers": {
90
+ "sonarqube-issue": {
91
+ "command": "node",
92
+ "args": [
93
+ "/Users/zzy/Project/Personal/farm-mcp/sonarqube-mcp/dist/index.js"
94
+ ],
95
+ "env": {
96
+ "SONAR_TOKEN": "your_sonar_token",
97
+ "SONAR_HTTP_PROXY": "http://127.0.0.1:7890"
98
+ }
99
+ }
100
+ }
101
+ }
102
+ ```
103
+
104
+ 说明:
105
+
106
+ - 这里不要写 `pnpm start`
107
+ - `stdio` 类型的 MCP 一旦有额外 `stdout` 输出,就会在握手阶段失败
108
+
109
+ ## 环境变量
110
+
111
+ 必须:
112
+
113
+ - `SONAR_TOKEN`: SonarQube token
114
+
115
+ 可选:
116
+
117
+ - `SONAR_REQUEST_TIMEOUT_MS`: SonarQube 请求超时,默认 `20000`
118
+ - `SONAR_RETRY_COUNT`: 请求重试次数,默认 `2`
119
+ - `SONAR_HTTP_PROXY`: SonarQube Web API 可选代理,例如 `http://127.0.0.1:7890`
120
+
121
+ ## 能力范围
122
+
123
+ - 输入固定为 SonarQube 项目链接,例如:
124
+
125
+ ```text
126
+ https://sonarqube-uat.894577.com/dashboard?id=cold-wallet-frontend-0313&codeScope=overall
127
+ ```
128
+
129
+ - 自动解析:
130
+ - `origin`
131
+ - `projectKey(id)`
132
+ - `branch`
133
+ - `pullRequest`
134
+ - 默认只返回当前待处理问题:
135
+ - Security = `VULNERABILITY + resolved=false`
136
+ - Reliability = `BUG + resolved=false`
137
+ - Security Hotspots = `status=TO_REVIEW`
138
+ - 支持单条问题详情查询
139
+
140
+ ## MCP Tools
141
+
142
+ ### `sonarqube_get_project_findings`
143
+
144
+ 输入:
145
+
146
+ ```json
147
+ {
148
+ "projectUrl": "https://sonarqube-uat.894577.com/dashboard?id=cold-wallet-frontend-0313&codeScope=overall",
149
+ "detailLevel": "standard"
150
+ }
151
+ ```
152
+
153
+ 返回:
154
+
155
+ - 项目元信息
156
+ - 总数汇总
157
+ - `securityIssues`
158
+ - `reliabilityIssues`
159
+ - `securityHotspots`
160
+
161
+ ### `sonarqube_get_finding_detail`
162
+
163
+ 输入:
164
+
165
+ ```json
166
+ {
167
+ "projectUrl": "https://sonarqube-uat.894577.com/dashboard?id=cold-wallet-frontend-0313&codeScope=overall",
168
+ "kind": "issue",
169
+ "key": "ISSUE-KEY"
170
+ }
171
+ ```
172
+
173
+ 返回:
174
+
175
+ - 单条问题标准化详情
176
+ - 规则说明
177
+ - comment / changelog / flows
178
+ - SonarQube 深链
179
+
180
+ ## 源码开发与调试
181
+
182
+ 源码开发额外要求:
183
+
184
+ - `pnpm`
185
+
186
+ 安装依赖:
187
+
188
+ ```bash
189
+ pnpm install
190
+ ```
191
+
192
+ 开发:
193
+
194
+ ```bash
195
+ pnpm dev
196
+ ```
197
+
198
+ 构建:
199
+
200
+ ```bash
201
+ pnpm build
202
+ ```
203
+
204
+ 运行:
205
+
206
+ ```bash
207
+ SONAR_TOKEN=your_token pnpm start
208
+ ```
209
+
210
+ 如果 SonarQube API 需要走代理:
211
+
212
+ ```bash
213
+ SONAR_TOKEN=your_token \
214
+ SONAR_HTTP_PROXY=http://127.0.0.1:7890 \
215
+ pnpm start
216
+ ```
217
+
218
+ 源码方式说明:
219
+
220
+ - 这条命令只适合本地烟雾测试,确认服务能启动
221
+ - 这是一个 `stdio` MCP server,不是终端交互式 CLI;手工执行后不会直接返回 issue 列表
222
+ - 不要在 MCP 客户端配置里使用 `pnpm start`
223
+ - 如果要接入本地源码,请在 MCP 客户端里直接执行 `node dist/index.js`
224
+ - `SONAR_HTTP_PROXY` 只作用于 SonarQube Web API 主链路
225
+
226
+ ## 测试
227
+
228
+ 运行所有测试:
229
+
230
+ ```bash
231
+ pnpm test
232
+ ```
233
+
234
+ 当前测试覆盖:
235
+
236
+ - URL 解析
237
+ - 配置校验
238
+ - 503 索引错误映射
239
+ - 项目问题聚合
240
+ - 单条 issue / hotspot 详情查询
241
+
242
+ ## 设计说明
243
+
244
+ - 主链路只走 SonarQube Web API,不依赖前端页面结构
245
+ - 对缺失 `SONAR_TOKEN`、401/403/404、503 索引中状态都会明确报错
246
+ - 不做多版本 SonarQube 向后兼容层
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * CLI 启动包装器。
5
+ *
6
+ * @remarks
7
+ * npm / pnpm / yarn 在安装 `bin` 命令时会直接执行这个文件。
8
+ * 这里保持最薄的一层,只负责转交到已构建的 MCP 服务入口。
9
+ */
10
+ import "../dist/index.js";
@@ -0,0 +1,28 @@
1
+ /**
2
+ * 运行期配置,统一由环境变量解析得到。
3
+ *
4
+ * @remarks
5
+ * 当前实现只保留 SonarQube Web API 主链路配置,不再维护浏览器兜底相关选项。
6
+ */
7
+ export interface AppConfig {
8
+ /** SonarQube 用户 token。 */
9
+ sonarToken: string;
10
+ /** 单次 SonarQube API 请求超时时间,单位毫秒。 */
11
+ sonarRequestTimeoutMs: number;
12
+ /** 可重试请求的最大重试次数。 */
13
+ sonarRetryCount: number;
14
+ /** SonarQube Web API 可选代理。 */
15
+ sonarHttpProxy: string | null;
16
+ }
17
+ /**
18
+ * 从环境变量加载完整配置。
19
+ *
20
+ * @param env - 运行时环境变量对象,默认使用 `process.env`。
21
+ * @returns 经过校验和规范化后的应用配置。
22
+ *
23
+ * 这里会在启动时完成校验,避免把配置错误拖到真正处理请求时才暴露。
24
+ *
25
+ * @throws {SonarQubeMcpError}
26
+ * 当必填项缺失、数值非法或代理地址格式错误时抛出。
27
+ */
28
+ export declare function loadConfig(env?: NodeJS.ProcessEnv): AppConfig;
package/dist/config.js ADDED
@@ -0,0 +1,90 @@
1
+ import { SonarQubeMcpError } from "./errors.js";
2
+ /**
3
+ * 从环境变量加载完整配置。
4
+ *
5
+ * @param env - 运行时环境变量对象,默认使用 `process.env`。
6
+ * @returns 经过校验和规范化后的应用配置。
7
+ *
8
+ * 这里会在启动时完成校验,避免把配置错误拖到真正处理请求时才暴露。
9
+ *
10
+ * @throws {SonarQubeMcpError}
11
+ * 当必填项缺失、数值非法或代理地址格式错误时抛出。
12
+ */
13
+ export function loadConfig(env = process.env) {
14
+ const sonarToken = env.SONAR_TOKEN?.trim();
15
+ if (!sonarToken) {
16
+ throw new SonarQubeMcpError("CONFIG", "缺少 SONAR_TOKEN 环境变量,无法访问 SonarQube Web API。");
17
+ }
18
+ return {
19
+ sonarToken,
20
+ sonarRequestTimeoutMs: parsePositiveInteger(env.SONAR_REQUEST_TIMEOUT_MS, 20_000, "SONAR_REQUEST_TIMEOUT_MS"),
21
+ sonarRetryCount: parseNonNegativeInteger(env.SONAR_RETRY_COUNT, 2, "SONAR_RETRY_COUNT"),
22
+ sonarHttpProxy: parseOptionalProxyUrl(env.SONAR_HTTP_PROXY, "SONAR_HTTP_PROXY")
23
+ };
24
+ }
25
+ /**
26
+ * 解析正整数配置项。
27
+ *
28
+ * @param value - 原始环境变量值。
29
+ * @param fallback - 缺失时使用的默认值。
30
+ * @param key - 配置键名,用于错误提示。
31
+ * @returns 解析后的正整数。
32
+ * @throws {SonarQubeMcpError} 当值存在但不是正整数时抛出。
33
+ */
34
+ function parsePositiveInteger(value, fallback, key) {
35
+ if (!value?.trim()) {
36
+ return fallback;
37
+ }
38
+ const parsed = Number.parseInt(value, 10);
39
+ if (!Number.isInteger(parsed) || parsed <= 0) {
40
+ throw new SonarQubeMcpError("CONFIG", `${key} 必须是正整数,当前值为 "${value}"。`);
41
+ }
42
+ return parsed;
43
+ }
44
+ /**
45
+ * 解析非负整数配置项。
46
+ *
47
+ * @param value - 原始环境变量值。
48
+ * @param fallback - 缺失时使用的默认值。
49
+ * @param key - 配置键名,用于错误提示。
50
+ * @returns 解析后的非负整数。
51
+ * @throws {SonarQubeMcpError} 当值存在但不是非负整数时抛出。
52
+ */
53
+ function parseNonNegativeInteger(value, fallback, key) {
54
+ if (!value?.trim()) {
55
+ return fallback;
56
+ }
57
+ const parsed = Number.parseInt(value, 10);
58
+ if (!Number.isInteger(parsed) || parsed < 0) {
59
+ throw new SonarQubeMcpError("CONFIG", `${key} 必须是非负整数,当前值为 "${value}"。`);
60
+ }
61
+ return parsed;
62
+ }
63
+ /**
64
+ * 校验可选代理地址,目前仅支持 http/https。
65
+ *
66
+ * @param value - 原始环境变量值。
67
+ * @param key - 配置键名,用于错误提示。
68
+ * @returns 规范化后的代理地址;如果未配置则返回 `null`。
69
+ * @throws {SonarQubeMcpError} 当代理地址不是合法的 http/https URL 时抛出。
70
+ */
71
+ function parseOptionalProxyUrl(value, key) {
72
+ const trimmed = value?.trim();
73
+ if (!trimmed) {
74
+ return null;
75
+ }
76
+ let url;
77
+ try {
78
+ url = new URL(trimmed);
79
+ }
80
+ catch (error) {
81
+ throw new SonarQubeMcpError("CONFIG", `${key} 不是合法 URL: "${trimmed}"。`, {
82
+ cause: error
83
+ });
84
+ }
85
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
86
+ throw new SonarQubeMcpError("CONFIG", `${key} 目前只支持 http/https 代理,当前协议为 "${url.protocol}"。`);
87
+ }
88
+ return url.toString();
89
+ }
90
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAmBhD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,UAAU,CAAC,MAAyB,OAAO,CAAC,GAAG;IAC7D,MAAM,UAAU,GAAG,GAAG,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC;IAC3C,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,MAAM,IAAI,iBAAiB,CACzB,QAAQ,EACR,6CAA6C,CAC9C,CAAC;IACJ,CAAC;IAED,OAAO;QACL,UAAU;QACV,qBAAqB,EAAE,oBAAoB,CACzC,GAAG,CAAC,wBAAwB,EAC5B,MAAM,EACN,0BAA0B,CAC3B;QACD,eAAe,EAAE,uBAAuB,CACtC,GAAG,CAAC,iBAAiB,EACrB,CAAC,EACD,mBAAmB,CACpB;QACD,cAAc,EAAE,qBAAqB,CAAC,GAAG,CAAC,gBAAgB,EAAE,kBAAkB,CAAC;KAChF,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,oBAAoB,CAC3B,KAAyB,EACzB,QAAgB,EAChB,GAAW;IAEX,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC;QACnB,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAC1C,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC,EAAE,CAAC;QAC7C,MAAM,IAAI,iBAAiB,CAAC,QAAQ,EAAE,GAAG,GAAG,iBAAiB,KAAK,IAAI,CAAC,CAAC;IAC1E,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,uBAAuB,CAC9B,KAAyB,EACzB,QAAgB,EAChB,GAAW;IAEX,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC;QACnB,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAC1C,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5C,MAAM,IAAI,iBAAiB,CAAC,QAAQ,EAAE,GAAG,GAAG,kBAAkB,KAAK,IAAI,CAAC,CAAC;IAC3E,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,qBAAqB,CAC5B,KAAyB,EACzB,GAAW;IAEX,MAAM,OAAO,GAAG,KAAK,EAAE,IAAI,EAAE,CAAC;IAC9B,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;IACzB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,iBAAiB,CAAC,QAAQ,EAAE,GAAG,GAAG,eAAe,OAAO,IAAI,EAAE;YACtE,KAAK,EAAE,KAAK;SACb,CAAC,CAAC;IACL,CAAC;IAED,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC1D,MAAM,IAAI,iBAAiB,CACzB,QAAQ,EACR,GAAG,GAAG,+BAA+B,GAAG,CAAC,QAAQ,IAAI,CACtD,CAAC;IACJ,CAAC;IAED,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC;AACxB,CAAC"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * 统一错误码,便于 MCP 客户端按类别处理异常。
3
+ *
4
+ * @remarks
5
+ * 这些错误码覆盖配置、鉴权、网络、远端服务和输入校验等主要失败路径。
6
+ */
7
+ export type SonarQubeMcpErrorCode = "CONFIG" | "VALIDATION" | "AUTH" | "FORBIDDEN" | "NOT_FOUND" | "INDEXING" | "REMOTE" | "NETWORK";
8
+ /**
9
+ * MCP 内部统一错误对象。
10
+ *
11
+ * @remarks
12
+ * 所有底层异常最终都会被归一为这个类型,再转换成对用户可读的文本输出。
13
+ */
14
+ export declare class SonarQubeMcpError extends Error {
15
+ readonly code: SonarQubeMcpErrorCode;
16
+ readonly status: number | null;
17
+ readonly details: unknown;
18
+ constructor(code: SonarQubeMcpErrorCode, message: string, options?: {
19
+ status?: number | null;
20
+ details?: unknown;
21
+ cause?: unknown;
22
+ });
23
+ }
24
+ /**
25
+ * 将任意异常收口为统一错误类型,避免上层反复判断。
26
+ *
27
+ * @param error - 原始异常对象。
28
+ * @returns 统一后的 `SonarQubeMcpError`。
29
+ */
30
+ export declare function normalizeError(error: unknown): SonarQubeMcpError;
31
+ /**
32
+ * 将统一错误渲染为 MCP 文本内容,方便客户端直接展示。
33
+ *
34
+ * @param error - 统一错误对象。
35
+ * @returns 适合在聊天窗口直接显示的多行文本。
36
+ */
37
+ export declare function formatErrorForText(error: SonarQubeMcpError): string;
package/dist/errors.js ADDED
@@ -0,0 +1,67 @@
1
+ /**
2
+ * MCP 内部统一错误对象。
3
+ *
4
+ * @remarks
5
+ * 所有底层异常最终都会被归一为这个类型,再转换成对用户可读的文本输出。
6
+ */
7
+ export class SonarQubeMcpError extends Error {
8
+ code;
9
+ status;
10
+ details;
11
+ constructor(code, message, options) {
12
+ super(message, options?.cause ? { cause: options.cause } : undefined);
13
+ this.name = "SonarQubeMcpError";
14
+ this.code = code;
15
+ this.status = options?.status ?? null;
16
+ this.details = options?.details ?? null;
17
+ }
18
+ }
19
+ /**
20
+ * 将任意异常收口为统一错误类型,避免上层反复判断。
21
+ *
22
+ * @param error - 原始异常对象。
23
+ * @returns 统一后的 `SonarQubeMcpError`。
24
+ */
25
+ export function normalizeError(error) {
26
+ if (error instanceof SonarQubeMcpError) {
27
+ return error;
28
+ }
29
+ if (error instanceof Error) {
30
+ return new SonarQubeMcpError("REMOTE", error.message, { cause: error });
31
+ }
32
+ return new SonarQubeMcpError("REMOTE", typeof error === "string" ? error : "发生了未知错误", { details: error });
33
+ }
34
+ /**
35
+ * 将统一错误渲染为 MCP 文本内容,方便客户端直接展示。
36
+ *
37
+ * @param error - 统一错误对象。
38
+ * @returns 适合在聊天窗口直接显示的多行文本。
39
+ */
40
+ export function formatErrorForText(error) {
41
+ const lines = [
42
+ `错误代码: ${error.code}`,
43
+ `错误信息: ${error.message}`
44
+ ];
45
+ if (error.status !== null) {
46
+ lines.push(`HTTP 状态: ${error.status}`);
47
+ }
48
+ if (error.details !== null) {
49
+ lines.push(`错误详情: ${safeJson(error.details)}`);
50
+ }
51
+ return lines.join("\n");
52
+ }
53
+ /**
54
+ * 错误详情可能包含循环引用,这里做一个安全序列化兜底。
55
+ *
56
+ * @param value - 需要输出的详情对象。
57
+ * @returns 可安全展示的字符串。
58
+ */
59
+ function safeJson(value) {
60
+ try {
61
+ return JSON.stringify(value, null, 2);
62
+ }
63
+ catch {
64
+ return String(value);
65
+ }
66
+ }
67
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAgBA;;;;;GAKG;AACH,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IACjC,IAAI,CAAwB;IAC5B,MAAM,CAAgB;IACtB,OAAO,CAAU;IAE1B,YACE,IAA2B,EAC3B,OAAe,EACf,OAIC;QAED,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QACtE,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;QAChC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,MAAM,GAAG,OAAO,EAAE,MAAM,IAAI,IAAI,CAAC;QACtC,IAAI,CAAC,OAAO,GAAG,OAAO,EAAE,OAAO,IAAI,IAAI,CAAC;IAC1C,CAAC;CACF;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,IAAI,KAAK,YAAY,iBAAiB,EAAE,CAAC;QACvC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;QAC3B,OAAO,IAAI,iBAAiB,CAAC,QAAQ,EAAE,KAAK,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IAC1E,CAAC;IAED,OAAO,IAAI,iBAAiB,CAC1B,QAAQ,EACR,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,EAC7C,EAAE,OAAO,EAAE,KAAK,EAAE,CACnB,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAwB;IACzD,MAAM,KAAK,GAAG;QACZ,SAAS,KAAK,CAAC,IAAI,EAAE;QACrB,SAAS,KAAK,CAAC,OAAO,EAAE;KACzB,CAAC;IAEF,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;QAC1B,KAAK,CAAC,IAAI,CAAC,YAAY,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IACzC,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;QAC3B,KAAK,CAAC,IAAI,CAAC,SAAS,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACjD,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;;;;GAKG;AACH,SAAS,QAAQ,CAAC,KAAc;IAC9B,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACxC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;AACH,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,36 @@
1
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2
+ import { loadConfig } from "./config.js";
3
+ import { createServer } from "./server.js";
4
+ /**
5
+ * 进程入口:加载配置、挂接 stdio transport,并处理优雅退出。
6
+ *
7
+ * @returns 启动完成后的异步流程。
8
+ * @throws {SonarQubeMcpError | Error} 当配置或服务器初始化失败时抛出。
9
+ */
10
+ async function main() {
11
+ const config = loadConfig();
12
+ const { server } = createServer(config);
13
+ const transport = new StdioServerTransport();
14
+ await server.connect(transport);
15
+ /**
16
+ * 统一资源清理逻辑。
17
+ *
18
+ * @remarks
19
+ * 当前仅需关闭 MCP 服务本身。
20
+ */
21
+ const cleanup = async () => {
22
+ await server.close();
23
+ };
24
+ process.on("SIGINT", () => {
25
+ void cleanup().finally(() => process.exit(0));
26
+ });
27
+ process.on("SIGTERM", () => {
28
+ void cleanup().finally(() => process.exit(0));
29
+ });
30
+ console.error("SonarQube Issue MCP server is running on stdio");
31
+ }
32
+ main().catch((error) => {
33
+ console.error("Failed to start SonarQube Issue MCP server:", error);
34
+ process.exit(1);
35
+ });
36
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C;;;;;GAKG;AACH,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,MAAM,EAAE,MAAM,EAAE,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IAExC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEhC;;;;;OAKG;IACH,MAAM,OAAO,GAAG,KAAK,IAAmB,EAAE;QACxC,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC,CAAC;IAEF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;QACxB,KAAK,OAAO,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;QACzB,KAAK,OAAO,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,KAAK,CAAC,gDAAgD,CAAC,CAAC;AAClE,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,6CAA6C,EAAE,KAAK,CAAC,CAAC;IACpE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,44 @@
1
+ import type { ProjectRef } from "./types.js";
2
+ /**
3
+ * 从 SonarQube dashboard URL 提取项目定位信息。
4
+ *
5
+ * @param projectUrl - 用户传入的 SonarQube 项目链接。
6
+ * @returns 规范化后的项目上下文。
7
+ *
8
+ * 这是整个 MCP 的唯一输入入口,因此这里会做严格校验,确保后续 API 请求有稳定上下文。
9
+ *
10
+ * @throws {SonarQubeMcpError}
11
+ * 当 URL 非法、协议不支持、路径不支持或缺少 `id` 参数时抛出。
12
+ */
13
+ export declare function parseProjectUrl(projectUrl: string): ProjectRef;
14
+ /**
15
+ * 将 branch / pullRequest 上下文透传到任意 SonarQube API 查询参数中。
16
+ *
17
+ * @param searchParams - 已有查询参数。
18
+ * @param projectRef - 解析后的项目上下文。
19
+ * @returns 追加了分支/PR 上下文的新查询参数对象。
20
+ */
21
+ export declare function withProjectContext(searchParams: URLSearchParams, projectRef: ProjectRef): URLSearchParams;
22
+ /**
23
+ * 构造项目级 SonarQube 浏览深链。
24
+ *
25
+ * @param projectRef - 解析后的项目上下文。
26
+ * @returns 指向项目 dashboard 的可点击链接。
27
+ */
28
+ export declare function buildProjectBrowseUrl(projectRef: ProjectRef): string;
29
+ /**
30
+ * 构造单条 issue 的 SonarQube 页面深链。
31
+ *
32
+ * @param projectRef - 解析后的项目上下文。
33
+ * @param issueKey - SonarQube issue key。
34
+ * @returns 指向 issue 详情面板的可点击链接。
35
+ */
36
+ export declare function buildIssueBrowseUrl(projectRef: ProjectRef, issueKey: string): string;
37
+ /**
38
+ * 构造单条 security hotspot 的 SonarQube 页面深链。
39
+ *
40
+ * @param projectRef - 解析后的项目上下文。
41
+ * @param hotspotKey - SonarQube hotspot key。
42
+ * @returns 指向 hotspot 详情面板的可点击链接。
43
+ */
44
+ export declare function buildHotspotBrowseUrl(projectRef: ProjectRef, hotspotKey: string): string;