luvv-mcp-server 1.1.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/README.md +283 -0
- package/dist/lib/executor.d.ts +48 -0
- package/dist/lib/executor.d.ts.map +1 -0
- package/dist/lib/executor.js +123 -0
- package/dist/lib/executor.js.map +1 -0
- package/dist/lib/logger.d.ts +13 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +36 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/scanner.d.ts +26 -0
- package/dist/lib/scanner.d.ts.map +1 -0
- package/dist/lib/scanner.js +161 -0
- package/dist/lib/scanner.js.map +1 -0
- package/dist/main.d.ts +14 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +35 -0
- package/dist/main.js.map +1 -0
- package/dist/server.d.ts +21 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +46 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/index.d.ts +15 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +17 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/run-security-scan.d.ts +40 -0
- package/dist/tools/run-security-scan.d.ts.map +1 -0
- package/dist/tools/run-security-scan.js +172 -0
- package/dist/tools/run-security-scan.js.map +1 -0
- package/dist/transports/stdio.d.ts +17 -0
- package/dist/transports/stdio.d.ts.map +1 -0
- package/dist/transports/stdio.js +54 -0
- package/dist/transports/stdio.js.map +1 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# Luvv MCPServer — 生产级安全审计 MCP 工具
|
|
2
|
+
|
|
3
|
+
封装 **semgrep** (SAST) + **gitleaks** (密钥检测),通过 **MCP stdio 协议** 为 Claude Desktop 提供一键代码安全扫描能力。
|
|
4
|
+
|
|
5
|
+
## 功能特性
|
|
6
|
+
|
|
7
|
+
- `run_security_scan` 工具:传入目标目录路径,自动并行执行 semgrep + gitleaks
|
|
8
|
+
- 返回合并的结构化 JSON 审计报告(含摘要统计)
|
|
9
|
+
- 依赖工具未安装时,报告中自动附带各平台的安装指引
|
|
10
|
+
- 所有异常/日志通过 stderr 输出,绝不污染 MCP 协议通道(stdout)
|
|
11
|
+
- 内置路径安全校验,拒绝扫描系统敏感目录
|
|
12
|
+
- Docker 环境隔离支持,预装全部依赖
|
|
13
|
+
|
|
14
|
+
## 快速开始
|
|
15
|
+
|
|
16
|
+
### 1. 环境要求
|
|
17
|
+
|
|
18
|
+
| 组件 | 版本要求 |
|
|
19
|
+
|------|---------|
|
|
20
|
+
| Node.js | >= 20.0.0 LTS |
|
|
21
|
+
| npm | >= 10.0.0 |
|
|
22
|
+
| semgrep | >= 1.0(可选,未安装在报告中提示) |
|
|
23
|
+
| gitleaks | >= 8.0(可选,未安装在报告中提示) |
|
|
24
|
+
|
|
25
|
+
### 2. 安装依赖与编译
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# 克隆项目(或直接进入目录)
|
|
29
|
+
cd luvv-mcp-server
|
|
30
|
+
|
|
31
|
+
# 安装 npm 依赖
|
|
32
|
+
npm install
|
|
33
|
+
|
|
34
|
+
# 编译 TypeScript
|
|
35
|
+
npm run build
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
编译产物输出到 `dist/` 目录,入口文件 `dist/main.js`。
|
|
39
|
+
|
|
40
|
+
### 3. 安装扫描工具(推荐)
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# --- semgrep ---
|
|
44
|
+
# macOS
|
|
45
|
+
brew install semgrep
|
|
46
|
+
|
|
47
|
+
# Linux
|
|
48
|
+
pipx install semgrep
|
|
49
|
+
# 或: pip install semgrep
|
|
50
|
+
|
|
51
|
+
# --- gitleaks ---
|
|
52
|
+
# macOS
|
|
53
|
+
brew install gitleaks
|
|
54
|
+
|
|
55
|
+
# Linux
|
|
56
|
+
wget https://github.com/gitleaks/gitleaks/releases/latest/download/gitleaks_8.18.4_linux_x64.tar.gz
|
|
57
|
+
tar -xzf gitleaks_*.tar.gz -C /usr/local/bin/ gitleaks
|
|
58
|
+
chmod +x /usr/local/bin/gitleaks
|
|
59
|
+
|
|
60
|
+
# --- 验证安装 ---
|
|
61
|
+
semgrep --version
|
|
62
|
+
gitleaks version
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
> 如果未安装 semgrep 或 gitleaks,扫描仍可运行,但对应工具的结果中会包含 `available: false` 及安装提示。
|
|
66
|
+
|
|
67
|
+
### 4. 在 Claude Desktop 中配置
|
|
68
|
+
|
|
69
|
+
打开 Claude Desktop 配置文件:
|
|
70
|
+
|
|
71
|
+
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
72
|
+
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
73
|
+
- **Linux**: `~/.config/Claude/claude_desktop_config.json`
|
|
74
|
+
|
|
75
|
+
添加以下配置块:
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{
|
|
79
|
+
"mcpServers": {
|
|
80
|
+
"luvv-security-scanner": {
|
|
81
|
+
"command": "node",
|
|
82
|
+
"args": [
|
|
83
|
+
"/Users/你的用户名/luvv-mcp-server/dist/main.js"
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**实际路径示例**(macOS 用户 `zhangsan`):
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"mcpServers": {
|
|
95
|
+
"luvv-security-scanner": {
|
|
96
|
+
"command": "node",
|
|
97
|
+
"args": [
|
|
98
|
+
"/Users/zhangsan/luvv-mcp-server/dist/main.js"
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**实际路径示例**(Windows 用户 `Administrator`):
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"mcpServers": {
|
|
110
|
+
"luvv-security-scanner": {
|
|
111
|
+
"command": "node",
|
|
112
|
+
"args": [
|
|
113
|
+
"C:\\Users\\Administrator\\luvv-mcp-server\\dist\\main.js"
|
|
114
|
+
]
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
配置完成后,**重启 Claude Desktop**。在对话中输入"帮我扫描 /path/to/project 的安全问题",Claude 会自动调用 `run_security_scan` 工具。
|
|
121
|
+
|
|
122
|
+
### 5. 验证 MCP 连接
|
|
123
|
+
|
|
124
|
+
启动 Claude Desktop 后,检查工具栏是否出现新工具图标(锤子),或在对话中尝试:
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
请列出你当前可用的 MCP 工具
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
如果看到 `run_security_scan`,说明连接成功。
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Docker 运行方式
|
|
135
|
+
|
|
136
|
+
Docker 镜像已预装 semgrep + gitleaks,适合环境隔离或 CI/CD 集成。
|
|
137
|
+
|
|
138
|
+
### 构建镜像
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
docker compose build
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### 交互式运行(手动测试)
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
# 启动容器并进入交互式 stdio 模式
|
|
148
|
+
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | \
|
|
149
|
+
docker compose run --rm -T mcp
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### 通过管道发送扫描请求
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
# 构造一个扫描请求的 MCP 消息
|
|
156
|
+
cat <<'JSONRPC' | docker compose run --rm -T mcp
|
|
157
|
+
{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"run_security_scan","arguments":{"target_path":"/scan"}}}
|
|
158
|
+
JSONRPC
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### 扫描本地代码
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
# 将本地项目挂载为 /scan
|
|
165
|
+
SCAN_TARGET=$(pwd) docker compose run --rm mcp
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## 输出示例
|
|
171
|
+
|
|
172
|
+
```json
|
|
173
|
+
{
|
|
174
|
+
"target_path": "/home/user/my-project",
|
|
175
|
+
"resolved_path": "/home/user/my-project",
|
|
176
|
+
"scan_time": "2026-06-13T12:00:00.000Z",
|
|
177
|
+
"duration_ms": 12345,
|
|
178
|
+
"tools": {
|
|
179
|
+
"semgrep": {
|
|
180
|
+
"available": true,
|
|
181
|
+
"results": {
|
|
182
|
+
"results": [
|
|
183
|
+
{
|
|
184
|
+
"check_id": "python.lang.security.audit.dangerous-subprocess-use",
|
|
185
|
+
"path": "src/utils.py",
|
|
186
|
+
"start": { "line": 42 },
|
|
187
|
+
"end": { "line": 42 },
|
|
188
|
+
"extra": {
|
|
189
|
+
"severity": "ERROR",
|
|
190
|
+
"message": "Detected subprocess function without a static string"
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
]
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
"gitleaks": {
|
|
197
|
+
"available": true,
|
|
198
|
+
"results": [
|
|
199
|
+
{
|
|
200
|
+
"RuleID": "generic-api-key",
|
|
201
|
+
"Description": "Generic API Key",
|
|
202
|
+
"File": "config.py",
|
|
203
|
+
"StartLine": 15,
|
|
204
|
+
"Secret": "***REDACTED***",
|
|
205
|
+
"Match": "sk_live_xxxxxxxxxxxx"
|
|
206
|
+
}
|
|
207
|
+
]
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
"summary": {
|
|
211
|
+
"total_findings": 2,
|
|
212
|
+
"semgrep_findings": 1,
|
|
213
|
+
"gitleaks_findings": 1
|
|
214
|
+
},
|
|
215
|
+
"environment": {
|
|
216
|
+
"node_version": "v20.14.0",
|
|
217
|
+
"platform": "linux",
|
|
218
|
+
"hostname": "dev-machine"
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
当工具未安装时:
|
|
224
|
+
|
|
225
|
+
```json
|
|
226
|
+
{
|
|
227
|
+
"tools": {
|
|
228
|
+
"semgrep": {
|
|
229
|
+
"available": false,
|
|
230
|
+
"error": "semgrep 未安装或不在 PATH 中",
|
|
231
|
+
"install_hint": "# macOS\nbrew install semgrep\n\n# Linux (pip)\npip install semgrep"
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## 环境变量
|
|
240
|
+
|
|
241
|
+
参见 `.env.example`,可复制为 `.env` 后修改:
|
|
242
|
+
|
|
243
|
+
| 变量 | 默认值 | 说明 |
|
|
244
|
+
|------|--------|------|
|
|
245
|
+
| `SEMGREP_CONFIG` | `auto` | semgrep 规则集(`p/security-audit`、`p/owasp-top-ten` 等) |
|
|
246
|
+
| `SEMGREP_TIMEOUT` | `300` | semgrep 超时时间(秒) |
|
|
247
|
+
| `GITLEAKS_TIMEOUT` | `120` | gitleaks 超时时间(秒) |
|
|
248
|
+
| `LOG_LEVEL` | `info` | 日志级别(debug/info/warn/error) |
|
|
249
|
+
| `INCLUDE_RAW_OUTPUT` | `false` | 是否返回完整原始输出 |
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## 项目结构
|
|
254
|
+
|
|
255
|
+
```
|
|
256
|
+
luvv-mcp-server/
|
|
257
|
+
├── src/
|
|
258
|
+
│ ├── main.ts # CLI 入口,按参数路由传输模式
|
|
259
|
+
│ ├── server.ts # createServer() 工厂函数
|
|
260
|
+
│ ├── tools/
|
|
261
|
+
│ │ ├── index.ts # registerAllTools() 汇总
|
|
262
|
+
│ │ └── run-security-scan.ts # 安全扫描工具定义与处理器
|
|
263
|
+
│ ├── transports/
|
|
264
|
+
│ │ └── stdio.ts # stdio 传输启动器(+ 信号处理)
|
|
265
|
+
│ └── lib/
|
|
266
|
+
│ ├── logger.ts # 结构化 stderr 日志
|
|
267
|
+
│ ├── executor.ts # spawn 封装 + 路径校验
|
|
268
|
+
│ └── scanner.ts # semgrep + gitleaks 编排
|
|
269
|
+
├── tests/
|
|
270
|
+
│ └── run-security-scan.test.ts # 单元测试
|
|
271
|
+
├── dist/ # 编译产物(npm run build)
|
|
272
|
+
├── vitest.config.ts
|
|
273
|
+
├── package.json
|
|
274
|
+
├── tsconfig.json
|
|
275
|
+
├── Dockerfile
|
|
276
|
+
├── docker-compose.yml
|
|
277
|
+
├── .env.example
|
|
278
|
+
└── README.md
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## License
|
|
282
|
+
|
|
283
|
+
MIT
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 外部命令执行工具 — 提供子进程 spawn、二进制检测、超时控制等基础能力。
|
|
3
|
+
*/
|
|
4
|
+
export interface ExecResult {
|
|
5
|
+
stdout: string;
|
|
6
|
+
stderr: string;
|
|
7
|
+
exitCode: number | null;
|
|
8
|
+
killed: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface BinaryInfo {
|
|
11
|
+
found: boolean;
|
|
12
|
+
version: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* 检查可执行文件是否在 PATH 中,并捕获其 --version 输出的首行。
|
|
16
|
+
* semgrep 和部分工具有时将版本信息写入 stderr,因此合并两者。
|
|
17
|
+
*/
|
|
18
|
+
export declare function detectBinary(name: string, timeoutMs?: number): Promise<BinaryInfo>;
|
|
19
|
+
/**
|
|
20
|
+
* 执行命令,超时后发送 SIGKILL 强制终止。
|
|
21
|
+
* 返回 stdout、stderr、退出码以及是否因超时而被 kill。
|
|
22
|
+
*/
|
|
23
|
+
export declare function runCommand(command: string, args: string[], timeoutMs: number): Promise<ExecResult>;
|
|
24
|
+
export interface PathCheck {
|
|
25
|
+
ok: boolean;
|
|
26
|
+
resolved: string;
|
|
27
|
+
reason?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 验证路径存在、为目录、且可读。
|
|
31
|
+
*/
|
|
32
|
+
export declare function verifyDirectory(raw: string): Promise<PathCheck>;
|
|
33
|
+
/**
|
|
34
|
+
* 拒绝扫描系统敏感路径,避免误操作。
|
|
35
|
+
*/
|
|
36
|
+
export declare function isSensitivePath(target: string): boolean;
|
|
37
|
+
export interface DependencyStatus {
|
|
38
|
+
semgrep: {
|
|
39
|
+
installed: boolean;
|
|
40
|
+
version: string;
|
|
41
|
+
};
|
|
42
|
+
gitleaks: {
|
|
43
|
+
installed: boolean;
|
|
44
|
+
version: string;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export declare function checkDependencies(): Promise<DependencyStatus>;
|
|
48
|
+
//# sourceMappingURL=executor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../src/lib/executor.ts"],"names":[],"mappings":"AAAA;;GAEG;AAWH,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAMD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CA2BlF;AAMD;;;GAGG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,UAAU,CAAC,CAqCrB;AAMD,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,OAAO,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAYrE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAYvD;AAMD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE;QAAE,SAAS,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACjD,QAAQ,EAAE;QAAE,SAAS,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CACnD;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAcnE"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 外部命令执行工具 — 提供子进程 spawn、二进制检测、超时控制等基础能力。
|
|
3
|
+
*/
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import { stat } from "node:fs/promises";
|
|
6
|
+
import { resolve } from "node:path";
|
|
7
|
+
import { logger } from "./logger.js";
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// 二进制可用性检测
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
/**
|
|
12
|
+
* 检查可执行文件是否在 PATH 中,并捕获其 --version 输出的首行。
|
|
13
|
+
* semgrep 和部分工具有时将版本信息写入 stderr,因此合并两者。
|
|
14
|
+
*/
|
|
15
|
+
export function detectBinary(name, timeoutMs = 10_000) {
|
|
16
|
+
return new Promise((resolve) => {
|
|
17
|
+
const child = spawn(name, ["--version"], {
|
|
18
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
19
|
+
timeout: timeoutMs,
|
|
20
|
+
});
|
|
21
|
+
let merged = "";
|
|
22
|
+
child.stdout?.on("data", (chunk) => {
|
|
23
|
+
merged += chunk.toString();
|
|
24
|
+
});
|
|
25
|
+
child.stderr?.on("data", (chunk) => {
|
|
26
|
+
merged += chunk.toString();
|
|
27
|
+
});
|
|
28
|
+
child.on("close", (code) => {
|
|
29
|
+
resolve({
|
|
30
|
+
found: code === 0,
|
|
31
|
+
version: merged.trim().split("\n")[0] ?? "",
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
child.on("error", () => {
|
|
35
|
+
resolve({ found: false, version: "" });
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// 带超时的命令执行
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
/**
|
|
43
|
+
* 执行命令,超时后发送 SIGKILL 强制终止。
|
|
44
|
+
* 返回 stdout、stderr、退出码以及是否因超时而被 kill。
|
|
45
|
+
*/
|
|
46
|
+
export function runCommand(command, args, timeoutMs) {
|
|
47
|
+
return new Promise((resolve) => {
|
|
48
|
+
const child = spawn(command, args, {
|
|
49
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
50
|
+
});
|
|
51
|
+
let stdout = "";
|
|
52
|
+
let stderr = "";
|
|
53
|
+
let killed = false;
|
|
54
|
+
const timer = setTimeout(() => {
|
|
55
|
+
killed = true;
|
|
56
|
+
child.kill("SIGKILL");
|
|
57
|
+
}, timeoutMs);
|
|
58
|
+
child.stdout?.on("data", (chunk) => {
|
|
59
|
+
stdout += chunk.toString();
|
|
60
|
+
});
|
|
61
|
+
child.stderr?.on("data", (chunk) => {
|
|
62
|
+
stderr += chunk.toString();
|
|
63
|
+
});
|
|
64
|
+
child.on("close", (code) => {
|
|
65
|
+
clearTimeout(timer);
|
|
66
|
+
resolve({ stdout, stderr, exitCode: code, killed });
|
|
67
|
+
});
|
|
68
|
+
child.on("error", (err) => {
|
|
69
|
+
clearTimeout(timer);
|
|
70
|
+
resolve({
|
|
71
|
+
stdout: "",
|
|
72
|
+
stderr: `spawn(${command}) 失败: ${err.message}`,
|
|
73
|
+
exitCode: null,
|
|
74
|
+
killed: false,
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* 验证路径存在、为目录、且可读。
|
|
81
|
+
*/
|
|
82
|
+
export async function verifyDirectory(raw) {
|
|
83
|
+
const resolvedPath = resolve(raw);
|
|
84
|
+
try {
|
|
85
|
+
const info = await stat(resolvedPath);
|
|
86
|
+
if (!info.isDirectory()) {
|
|
87
|
+
return { ok: false, resolved: resolvedPath, reason: "路径不是目录" };
|
|
88
|
+
}
|
|
89
|
+
return { ok: true, resolved: resolvedPath };
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
93
|
+
return { ok: false, resolved: resolvedPath, reason: `无法访问: ${detail}` };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* 拒绝扫描系统敏感路径,避免误操作。
|
|
98
|
+
*/
|
|
99
|
+
export function isSensitivePath(target) {
|
|
100
|
+
const denyList = [
|
|
101
|
+
"/etc/passwd",
|
|
102
|
+
"/etc/shadow",
|
|
103
|
+
"/proc",
|
|
104
|
+
"/sys",
|
|
105
|
+
"/root",
|
|
106
|
+
"C:\\Windows\\System32",
|
|
107
|
+
"~/.ssh",
|
|
108
|
+
];
|
|
109
|
+
const lower = target.toLowerCase();
|
|
110
|
+
return denyList.some((entry) => lower.includes(entry.toLowerCase()));
|
|
111
|
+
}
|
|
112
|
+
export async function checkDependencies() {
|
|
113
|
+
const [semgrep, gitleaks] = await Promise.all([
|
|
114
|
+
detectBinary("semgrep"),
|
|
115
|
+
detectBinary("gitleaks"),
|
|
116
|
+
]);
|
|
117
|
+
logger.info(`依赖检测: semgrep=${semgrep.found ? semgrep.version || "✓" : "✗"}, gitleaks=${gitleaks.found ? gitleaks.version || "✓" : "✗"}`);
|
|
118
|
+
return {
|
|
119
|
+
semgrep: { installed: semgrep.found, version: semgrep.version },
|
|
120
|
+
gitleaks: { installed: gitleaks.found, version: gitleaks.version },
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=executor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../src/lib/executor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 结构化日志模块 — 所有日志写入 stderr,确保 MCP 协议通道(stdout)不受污染。
|
|
3
|
+
*
|
|
4
|
+
* 输出格式为单行 JSON,便于日志聚合工具解析:
|
|
5
|
+
* {"ts":"2026-06-13T...","level":"info","msg":"...","ctx":{}}
|
|
6
|
+
*/
|
|
7
|
+
export declare const logger: {
|
|
8
|
+
debug: (msg: string, ctx?: unknown) => void;
|
|
9
|
+
info: (msg: string, ctx?: unknown) => void;
|
|
10
|
+
warn: (msg: string, ctx?: unknown) => void;
|
|
11
|
+
error: (msg: string, ctx?: unknown) => void;
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/lib/logger.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA+BH,eAAO,MAAM,MAAM;iBACJ,MAAM,QAAQ,OAAO;gBACtB,MAAM,QAAQ,OAAO;gBACrB,MAAM,QAAQ,OAAO;iBACpB,MAAM,QAAQ,OAAO;CACnC,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 结构化日志模块 — 所有日志写入 stderr,确保 MCP 协议通道(stdout)不受污染。
|
|
3
|
+
*
|
|
4
|
+
* 输出格式为单行 JSON,便于日志聚合工具解析:
|
|
5
|
+
* {"ts":"2026-06-13T...","level":"info","msg":"...","ctx":{}}
|
|
6
|
+
*/
|
|
7
|
+
const SEVERITY_RANK = {
|
|
8
|
+
debug: 0,
|
|
9
|
+
info: 1,
|
|
10
|
+
warn: 2,
|
|
11
|
+
error: 3,
|
|
12
|
+
};
|
|
13
|
+
/** 从 LOG_LEVEL 环境变量读取阈值,默认 "info" */
|
|
14
|
+
function threshold() {
|
|
15
|
+
const raw = (process.env.LOG_LEVEL ?? "info").toLowerCase();
|
|
16
|
+
return raw in SEVERITY_RANK ? raw : "info";
|
|
17
|
+
}
|
|
18
|
+
function emit(level, message, context) {
|
|
19
|
+
const current = threshold();
|
|
20
|
+
if (SEVERITY_RANK[level] < SEVERITY_RANK[current])
|
|
21
|
+
return;
|
|
22
|
+
const record = {
|
|
23
|
+
ts: new Date().toISOString(),
|
|
24
|
+
level,
|
|
25
|
+
msg: message,
|
|
26
|
+
...(context !== undefined ? { ctx: context } : {}),
|
|
27
|
+
};
|
|
28
|
+
process.stderr.write(JSON.stringify(record) + "\n");
|
|
29
|
+
}
|
|
30
|
+
export const logger = {
|
|
31
|
+
debug: (msg, ctx) => emit("debug", msg, ctx),
|
|
32
|
+
info: (msg, ctx) => emit("info", msg, ctx),
|
|
33
|
+
warn: (msg, ctx) => emit("warn", msg, ctx),
|
|
34
|
+
error: (msg, ctx) => emit("error", msg, ctx),
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/lib/logger.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,MAAM,aAAa,GAAgC;IACjD,KAAK,EAAE,CAAC;IACR,IAAI,EAAE,CAAC;IACP,IAAI,EAAE,CAAC;IACP,KAAK,EAAE,CAAC;CACT,CAAC;AAEF,qCAAqC;AACrC,SAAS,SAAS;IAChB,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;IAC5D,OAAO,GAAG,IAAI,aAAa,CAAC,CAAC,CAAE,GAAmB,CAAC,CAAC,CAAC,MAAM,CAAC;AAC9D,CAAC;AAED,SAAS,IAAI,CAAC,KAAkB,EAAE,OAAe,EAAE,OAAiB;IAClE,MAAM,OAAO,GAAG,SAAS,EAAE,CAAC;IAC5B,IAAI,aAAa,CAAC,KAAK,CAAC,GAAG,aAAa,CAAC,OAAO,CAAC;QAAE,OAAO;IAE1D,MAAM,MAAM,GAAG;QACb,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QAC5B,KAAK;QACL,GAAG,EAAE,OAAO;QACZ,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACnD,CAAC;IAEF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC;AACtD,CAAC;AAED,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,KAAK,EAAE,CAAC,GAAW,EAAE,GAAa,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC;IAC9D,IAAI,EAAE,CAAC,GAAW,EAAE,GAAa,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC;IAC5D,IAAI,EAAE,CAAC,GAAW,EAAE,GAAa,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC;IAC5D,KAAK,EAAE,CAAC,GAAW,EAAE,GAAa,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC;CAC/D,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 扫描编排模块 — 封装 semgrep SAST 和 gitleaks 密钥检测的调用逻辑。
|
|
3
|
+
*
|
|
4
|
+
* 设计原则:
|
|
5
|
+
* - 每个扫描器返回统一结构的 ToolResult(含 available / error / install_hint / results)
|
|
6
|
+
* - 工具未安装时不报错,而是在结果中嵌入各平台安装指引
|
|
7
|
+
* - 所有运行时异常在此层兜底,上层(tool handler)只关心组装报告
|
|
8
|
+
*/
|
|
9
|
+
export interface ToolResult {
|
|
10
|
+
available: boolean;
|
|
11
|
+
error?: string;
|
|
12
|
+
installGuide?: string;
|
|
13
|
+
findings?: unknown;
|
|
14
|
+
}
|
|
15
|
+
interface ScanOutcome {
|
|
16
|
+
semgrep: ToolResult;
|
|
17
|
+
gitleaks: ToolResult;
|
|
18
|
+
elapsedMs: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 并行执行 semgrep 和 gitleaks,返回合并结果及耗时。
|
|
22
|
+
* 单个扫描器失败不影响另一方,所有错误封装在 ToolResult 中。
|
|
23
|
+
*/
|
|
24
|
+
export declare function executeScan(target: string): Promise<ScanOutcome>;
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../src/lib/scanner.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AASH,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,UAAU,WAAW;IACnB,OAAO,EAAE,UAAU,CAAC;IACpB,QAAQ,EAAE,UAAU,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB;AAuKD;;;GAGG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAOtE"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 扫描编排模块 — 封装 semgrep SAST 和 gitleaks 密钥检测的调用逻辑。
|
|
3
|
+
*
|
|
4
|
+
* 设计原则:
|
|
5
|
+
* - 每个扫描器返回统一结构的 ToolResult(含 available / error / install_hint / results)
|
|
6
|
+
* - 工具未安装时不报错,而是在结果中嵌入各平台安装指引
|
|
7
|
+
* - 所有运行时异常在此层兜底,上层(tool handler)只关心组装报告
|
|
8
|
+
*/
|
|
9
|
+
import { detectBinary, runCommand } from "./executor.js";
|
|
10
|
+
import { logger } from "./logger.js";
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// 常量
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
const TIMEOUT_SEMGREP = (Number(process.env.SEMGREP_TIMEOUT) || 300) * 1000;
|
|
15
|
+
const TIMEOUT_GITLEAKS = (Number(process.env.GITLEAKS_TIMEOUT) || 120) * 1000;
|
|
16
|
+
const RULESET = process.env.SEMGREP_CONFIG || "auto";
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// 安装指引(纯文本,多平台)
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
const INSTALL_SEMGREP = [
|
|
21
|
+
"# macOS",
|
|
22
|
+
"brew install semgrep",
|
|
23
|
+
"",
|
|
24
|
+
"# Linux",
|
|
25
|
+
"pipx install semgrep",
|
|
26
|
+
"",
|
|
27
|
+
"# 或通过 pip",
|
|
28
|
+
"pip3 install semgrep",
|
|
29
|
+
"",
|
|
30
|
+
"# Docker(无需本地安装)",
|
|
31
|
+
"docker run --rm -v \"$(pwd):/src\" returntocorp/semgrep semgrep --config=auto /src",
|
|
32
|
+
].join("\n");
|
|
33
|
+
const INSTALL_GITLEAKS = [
|
|
34
|
+
"# macOS",
|
|
35
|
+
"brew install gitleaks",
|
|
36
|
+
"",
|
|
37
|
+
"# Linux (预编译二进制)",
|
|
38
|
+
"curl -fsSL https://github.com/gitleaks/gitleaks/releases/latest/download/gitleaks_8.18.4_linux_x64.tar.gz | tar -xz -C /usr/local/bin/ gitleaks",
|
|
39
|
+
"chmod +x /usr/local/bin/gitleaks",
|
|
40
|
+
"",
|
|
41
|
+
"# 或使用 Go 安装",
|
|
42
|
+
"go install github.com/gitleaks/gitleaks/v8@latest",
|
|
43
|
+
].join("\n");
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// 扫描器实现
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
async function scanSemgrep(target) {
|
|
48
|
+
const bin = await detectBinary("semgrep");
|
|
49
|
+
if (!bin.found) {
|
|
50
|
+
return {
|
|
51
|
+
available: false,
|
|
52
|
+
error: "semgrep 未安装或不在 PATH 中",
|
|
53
|
+
installGuide: INSTALL_SEMGREP,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
logger.info(`semgrep 扫描启动: ${target} (ruleset=${RULESET})`);
|
|
57
|
+
try {
|
|
58
|
+
const { stdout, stderr, exitCode, killed } = await runCommand("semgrep", ["--config", RULESET, "--json", "--quiet", target], TIMEOUT_SEMGREP);
|
|
59
|
+
if (killed) {
|
|
60
|
+
return {
|
|
61
|
+
available: true,
|
|
62
|
+
error: `扫描超时(>${TIMEOUT_SEMGREP / 1000}s),建议缩小扫描范围`,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (stderr) {
|
|
66
|
+
logger.warn(`semgrep stderr: ${stderr.slice(0, 400)}`);
|
|
67
|
+
}
|
|
68
|
+
let parsed;
|
|
69
|
+
try {
|
|
70
|
+
parsed = JSON.parse(stdout || "{}");
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// 尝试从混合输出中截取 JSON 段
|
|
74
|
+
const start = stdout.indexOf("{");
|
|
75
|
+
if (start >= 0) {
|
|
76
|
+
try {
|
|
77
|
+
parsed = JSON.parse(stdout.slice(start));
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
parsed = { _raw: stdout.slice(0, 2000), _parseError: true };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
parsed = { _raw: stdout.slice(0, 2000), _parseError: true };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const results = parsed?.results;
|
|
88
|
+
const count = Array.isArray(results) ? results.length : 0;
|
|
89
|
+
logger.info(`semgrep 完成: ${count} 个发现 (exit=${exitCode})`);
|
|
90
|
+
return { available: true, findings: parsed };
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
94
|
+
logger.error(`semgrep 异常: ${msg}`);
|
|
95
|
+
return { available: true, error: `semgrep 执行失败: ${msg}` };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async function scanGitleaks(target) {
|
|
99
|
+
const bin = await detectBinary("gitleaks");
|
|
100
|
+
if (!bin.found) {
|
|
101
|
+
return {
|
|
102
|
+
available: false,
|
|
103
|
+
error: "gitleaks 未安装或不在 PATH 中",
|
|
104
|
+
installGuide: INSTALL_GITLEAKS,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
logger.info(`gitleaks 扫描启动: ${target}`);
|
|
108
|
+
try {
|
|
109
|
+
const { stdout, stderr, exitCode, killed } = await runCommand("gitleaks", [
|
|
110
|
+
"detect",
|
|
111
|
+
"--no-git",
|
|
112
|
+
"--source",
|
|
113
|
+
target,
|
|
114
|
+
"--format",
|
|
115
|
+
"json",
|
|
116
|
+
"--exit-code",
|
|
117
|
+
"0",
|
|
118
|
+
"--verbose",
|
|
119
|
+
], TIMEOUT_GITLEAKS);
|
|
120
|
+
if (killed) {
|
|
121
|
+
return {
|
|
122
|
+
available: true,
|
|
123
|
+
error: `扫描超时(>${TIMEOUT_GITLEAKS / 1000}s)`,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (stderr) {
|
|
127
|
+
logger.warn(`gitleaks stderr: ${stderr.slice(0, 400)}`);
|
|
128
|
+
}
|
|
129
|
+
let parsed;
|
|
130
|
+
try {
|
|
131
|
+
parsed = JSON.parse(stdout || "[]");
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
parsed = { _raw: stdout.slice(0, 2000), _parseError: true };
|
|
135
|
+
}
|
|
136
|
+
const count = Array.isArray(parsed) ? parsed.length : 0;
|
|
137
|
+
logger.info(`gitleaks 完成: ${count} 个发现 (exit=${exitCode})`);
|
|
138
|
+
return { available: true, findings: parsed };
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
142
|
+
logger.error(`gitleaks 异常: ${msg}`);
|
|
143
|
+
return { available: true, error: `gitleaks 执行失败: ${msg}` };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
// 并行编排入口
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
/**
|
|
150
|
+
* 并行执行 semgrep 和 gitleaks,返回合并结果及耗时。
|
|
151
|
+
* 单个扫描器失败不影响另一方,所有错误封装在 ToolResult 中。
|
|
152
|
+
*/
|
|
153
|
+
export async function executeScan(target) {
|
|
154
|
+
const start = Date.now();
|
|
155
|
+
const [semgrep, gitleaks] = await Promise.all([
|
|
156
|
+
scanSemgrep(target),
|
|
157
|
+
scanGitleaks(target),
|
|
158
|
+
]);
|
|
159
|
+
return { semgrep, gitleaks, elapsedMs: Date.now() - start };
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.js","sourceRoot":"","sources":["../../src/lib/scanner.ts"],"names":[],"mappings":"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"}
|
package/dist/main.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Luvv MCPServer — 入口路由器
|
|
4
|
+
*
|
|
5
|
+
* 用法:
|
|
6
|
+
* node dist/main.js # 默认 stdio 模式
|
|
7
|
+
* node dist/main.js stdio # 显式 stdio
|
|
8
|
+
*
|
|
9
|
+
* 未来可扩展:
|
|
10
|
+
* node dist/main.js sse # Server-Sent Events
|
|
11
|
+
* node dist/main.js http # Streamable HTTP
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=main.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";AACA;;;;;;;;;;GAUG"}
|
package/dist/main.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Luvv MCPServer — 入口路由器
|
|
4
|
+
*
|
|
5
|
+
* 用法:
|
|
6
|
+
* node dist/main.js # 默认 stdio 模式
|
|
7
|
+
* node dist/main.js stdio # 显式 stdio
|
|
8
|
+
*
|
|
9
|
+
* 未来可扩展:
|
|
10
|
+
* node dist/main.js sse # Server-Sent Events
|
|
11
|
+
* node dist/main.js http # Streamable HTTP
|
|
12
|
+
*/
|
|
13
|
+
import { logger } from "./lib/logger.js";
|
|
14
|
+
const transport = process.argv[2] || "stdio";
|
|
15
|
+
async function main() {
|
|
16
|
+
switch (transport) {
|
|
17
|
+
case "stdio": {
|
|
18
|
+
const { launchStdio } = await import("./transports/stdio.js");
|
|
19
|
+
await launchStdio();
|
|
20
|
+
break;
|
|
21
|
+
}
|
|
22
|
+
default:
|
|
23
|
+
logger.error(`未知传输模式: ${transport}`);
|
|
24
|
+
process.stderr.write(`用法: node dist/main.js [stdio]\n`);
|
|
25
|
+
process.stderr.write(`可用模式: stdio\n`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
main().catch((err) => {
|
|
30
|
+
logger.error("启动失败", {
|
|
31
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
32
|
+
});
|
|
33
|
+
process.exit(1);
|
|
34
|
+
});
|
|
35
|
+
//# sourceMappingURL=main.js.map
|
package/dist/main.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";AACA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAEzC,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC;AAE7C,KAAK,UAAU,IAAI;IACjB,QAAQ,SAAS,EAAE,CAAC;QAClB,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,MAAM,CAAC,uBAAuB,CAAC,CAAC;YAC9D,MAAM,WAAW,EAAE,CAAC;YACpB,MAAM;QACR,CAAC;QACD;YACE,MAAM,CAAC,KAAK,CAAC,WAAW,SAAS,EAAE,CAAC,CAAC;YACrC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC;YACxD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;YACtC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE;QACnB,MAAM,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;KACzD,CAAC,CAAC;IACH,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Server 工厂 — 负责创建和配置 McpServer 实例。
|
|
3
|
+
*
|
|
4
|
+
* 此模块不关心传输层协议(stdio/SSE/HTTP),只负责:
|
|
5
|
+
* 1. 创建服务器实例(name + version)
|
|
6
|
+
* 2. 注册所有工具 / 资源 / 提示
|
|
7
|
+
* 3. 返回 server 实例及清理函数
|
|
8
|
+
*/
|
|
9
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
10
|
+
export interface ServerContext {
|
|
11
|
+
server: McpServer;
|
|
12
|
+
/** 优雅关闭:依次关闭 server 并执行清理回调 */
|
|
13
|
+
shutdown: () => Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* 创建并配置一个完整的 MCP Server。
|
|
17
|
+
*
|
|
18
|
+
* 调用方负责将 server 连接到具体传输层(如 StdioServerTransport)。
|
|
19
|
+
*/
|
|
20
|
+
export declare function createServer(): ServerContext;
|
|
21
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAQpE,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,SAAS,CAAC;IAClB,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B;AAMD;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,aAAa,CA8B5C"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Server 工厂 — 负责创建和配置 McpServer 实例。
|
|
3
|
+
*
|
|
4
|
+
* 此模块不关心传输层协议(stdio/SSE/HTTP),只负责:
|
|
5
|
+
* 1. 创建服务器实例(name + version)
|
|
6
|
+
* 2. 注册所有工具 / 资源 / 提示
|
|
7
|
+
* 3. 返回 server 实例及清理函数
|
|
8
|
+
*/
|
|
9
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
10
|
+
import { registerAllTools } from "./tools/index.js";
|
|
11
|
+
import { logger } from "./lib/logger.js";
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// 工厂函数
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
/**
|
|
16
|
+
* 创建并配置一个完整的 MCP Server。
|
|
17
|
+
*
|
|
18
|
+
* 调用方负责将 server 连接到具体传输层(如 StdioServerTransport)。
|
|
19
|
+
*/
|
|
20
|
+
export function createServer() {
|
|
21
|
+
logger.info("正在初始化 Luvv MCPServer...");
|
|
22
|
+
const server = new McpServer({
|
|
23
|
+
name: "luvv-security-scanner",
|
|
24
|
+
version: "1.1.0",
|
|
25
|
+
});
|
|
26
|
+
// --- 注册能力 ---
|
|
27
|
+
registerAllTools(server);
|
|
28
|
+
// --- 构造清理函数 ---
|
|
29
|
+
const cleanupTasks = [];
|
|
30
|
+
const shutdown = async () => {
|
|
31
|
+
logger.info("正在关闭服务器...");
|
|
32
|
+
for (const task of cleanupTasks) {
|
|
33
|
+
try {
|
|
34
|
+
await task();
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
logger.error("清理任务失败", { detail: String(err) });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
await server.close();
|
|
41
|
+
logger.info("服务器已关闭");
|
|
42
|
+
};
|
|
43
|
+
logger.info("MCP Server 实例创建完成");
|
|
44
|
+
return { server, shutdown };
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAYzC,8EAA8E;AAC9E,OAAO;AACP,8EAA8E;AAE9E;;;;GAIG;AACH,MAAM,UAAU,YAAY;IAC1B,MAAM,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;IAEvC,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,uBAAuB;QAC7B,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;IAEH,eAAe;IACf,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAEzB,iBAAiB;IACjB,MAAM,YAAY,GAA+B,EAAE,CAAC;IAEpD,MAAM,QAAQ,GAAG,KAAK,IAAmB,EAAE;QACzC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC1B,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;YAChC,IAAI,CAAC;gBACH,MAAM,IAAI,EAAE,CAAC;YACf,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAClD,CAAC;QACH,CAAC;QACD,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACrB,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACxB,CAAC,CAAC;IAEF,MAAM,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IAEjC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;AAC9B,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 工具注册汇总 — 所有 MCP 工具的集中注册入口。
|
|
3
|
+
*
|
|
4
|
+
* 新增工具时:
|
|
5
|
+
* 1. 在 tools/ 下创建独立文件
|
|
6
|
+
* 2. 导出 registerXxx(server) 函数
|
|
7
|
+
* 3. 在此文件中导入并调用
|
|
8
|
+
*/
|
|
9
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
10
|
+
/**
|
|
11
|
+
* 向 McpServer 实例注册所有工具。
|
|
12
|
+
* 被 server.ts 的 createServer() 工厂调用。
|
|
13
|
+
*/
|
|
14
|
+
export declare function registerAllTools(server: McpServer): void;
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGzE;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAExD"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 工具注册汇总 — 所有 MCP 工具的集中注册入口。
|
|
3
|
+
*
|
|
4
|
+
* 新增工具时:
|
|
5
|
+
* 1. 在 tools/ 下创建独立文件
|
|
6
|
+
* 2. 导出 registerXxx(server) 函数
|
|
7
|
+
* 3. 在此文件中导入并调用
|
|
8
|
+
*/
|
|
9
|
+
import { registerRunSecurityScan } from "./run-security-scan.js";
|
|
10
|
+
/**
|
|
11
|
+
* 向 McpServer 实例注册所有工具。
|
|
12
|
+
* 被 server.ts 的 createServer() 工厂调用。
|
|
13
|
+
*/
|
|
14
|
+
export function registerAllTools(server) {
|
|
15
|
+
registerRunSecurityScan(server);
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEjE;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAiB;IAChD,uBAAuB,CAAC,MAAM,CAAC,CAAC;AAClC,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* run_security_scan 工具定义与处理器。
|
|
3
|
+
*
|
|
4
|
+
* 接收 target_path,内部编排 semgrep + gitleaks 并行扫描,
|
|
5
|
+
* 返回结构化的安全审计报告。
|
|
6
|
+
*/
|
|
7
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
export declare const SecurityScanInput: z.ZodObject<{
|
|
10
|
+
target_path: z.ZodString;
|
|
11
|
+
}, "strip", z.ZodTypeAny, {
|
|
12
|
+
target_path: string;
|
|
13
|
+
}, {
|
|
14
|
+
target_path: string;
|
|
15
|
+
}>;
|
|
16
|
+
export type SecurityScanArgs = z.infer<typeof SecurityScanInput>;
|
|
17
|
+
export declare const TOOL_NAME = "run_security_scan";
|
|
18
|
+
export declare const TOOL_CONFIG: {
|
|
19
|
+
title: string;
|
|
20
|
+
description: string;
|
|
21
|
+
inputSchema: z.ZodObject<{
|
|
22
|
+
target_path: z.ZodString;
|
|
23
|
+
}, "strip", z.ZodTypeAny, {
|
|
24
|
+
target_path: string;
|
|
25
|
+
}, {
|
|
26
|
+
target_path: string;
|
|
27
|
+
}>;
|
|
28
|
+
annotations: {
|
|
29
|
+
readOnlyHint: boolean;
|
|
30
|
+
destructiveHint: boolean;
|
|
31
|
+
idempotentHint: boolean;
|
|
32
|
+
openWorldHint: boolean;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* 向 McpServer 实例注册 run_security_scan 工具。
|
|
37
|
+
* 采用 server.tool() 高层 API,与官方 server.registerTool() 等价但更简洁。
|
|
38
|
+
*/
|
|
39
|
+
export declare function registerRunSecurityScan(server: McpServer): void;
|
|
40
|
+
//# sourceMappingURL=run-security-scan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-security-scan.d.ts","sourceRoot":"","sources":["../../src/tools/run-security-scan.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AASxB,eAAO,MAAM,iBAAiB;;;;;;EAK5B,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAMjE,eAAO,MAAM,SAAS,sBAAsB,CAAC;AAE7C,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;CAYvB,CAAC;AAqGF;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA4E/D"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* run_security_scan 工具定义与处理器。
|
|
3
|
+
*
|
|
4
|
+
* 接收 target_path,内部编排 semgrep + gitleaks 并行扫描,
|
|
5
|
+
* 返回结构化的安全审计报告。
|
|
6
|
+
*/
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { executeScan } from "../lib/scanner.js";
|
|
9
|
+
import { verifyDirectory, isSensitivePath } from "../lib/executor.js";
|
|
10
|
+
import { logger } from "../lib/logger.js";
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// 输入 Schema(Zod)
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
export const SecurityScanInput = z.object({
|
|
15
|
+
target_path: z
|
|
16
|
+
.string()
|
|
17
|
+
.min(1, "target_path 不能为空")
|
|
18
|
+
.describe("要扫描的目标目录的绝对路径,如 /home/user/project"),
|
|
19
|
+
});
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// 工具元数据
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
export const TOOL_NAME = "run_security_scan";
|
|
24
|
+
export const TOOL_CONFIG = {
|
|
25
|
+
title: "安全审计扫描",
|
|
26
|
+
description: "对目标目录执行安全扫描,内部自动并行调用 semgrep(SAST 静态分析)和 gitleaks(硬编码密钥检测)," +
|
|
27
|
+
"返回合并的结构化 JSON 审计报告。若依赖工具未安装,报告中会附带各平台的安装指引。",
|
|
28
|
+
inputSchema: SecurityScanInput,
|
|
29
|
+
annotations: {
|
|
30
|
+
readOnlyHint: true, // 只读操作,不修改任何文件
|
|
31
|
+
destructiveHint: false, // 非破坏性
|
|
32
|
+
idempotentHint: true, // 对同一目录多次扫描结果一致(幂等)
|
|
33
|
+
openWorldHint: false, // 不依赖外部世界状态
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// 结果裁剪(缩减大型输出,保护敏感数据)
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
/** 对 semgrep 输出只保留摘要字段 */
|
|
40
|
+
function trimSemgrep(findings) {
|
|
41
|
+
const data = findings;
|
|
42
|
+
if (!data || !Array.isArray(data.results))
|
|
43
|
+
return findings;
|
|
44
|
+
return {
|
|
45
|
+
...data,
|
|
46
|
+
results: data.results.map((r) => ({
|
|
47
|
+
check_id: r.check_id,
|
|
48
|
+
path: r.path,
|
|
49
|
+
start: r.start,
|
|
50
|
+
end: r.end,
|
|
51
|
+
extra: {
|
|
52
|
+
severity: r.extra?.severity,
|
|
53
|
+
message: r.extra?.message,
|
|
54
|
+
},
|
|
55
|
+
})),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/** 对 gitleaks 输出做密钥掩码处理 */
|
|
59
|
+
function trimGitleaks(findings) {
|
|
60
|
+
if (!Array.isArray(findings))
|
|
61
|
+
return findings;
|
|
62
|
+
return findings.map((f) => ({
|
|
63
|
+
RuleID: f.RuleID,
|
|
64
|
+
Description: f.Description,
|
|
65
|
+
File: f.File,
|
|
66
|
+
StartLine: f.StartLine,
|
|
67
|
+
Secret: f.Secret ? "***MASKED***" : undefined,
|
|
68
|
+
Match: f.Match,
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
function buildReport(targetRaw, resolved, elapsed, semgrepResult, gitleaksResult) {
|
|
72
|
+
const semFindings = semgrepResult.findings?.results?.length ?? 0;
|
|
73
|
+
const glFindings = Array.isArray(gitleaksResult.findings)
|
|
74
|
+
? gitleaksResult.findings.length
|
|
75
|
+
: 0;
|
|
76
|
+
return {
|
|
77
|
+
target_path: targetRaw,
|
|
78
|
+
resolved_path: resolved,
|
|
79
|
+
scan_time: new Date().toISOString(),
|
|
80
|
+
duration_ms: elapsed,
|
|
81
|
+
tools: {
|
|
82
|
+
semgrep: semgrepResult,
|
|
83
|
+
gitleaks: gitleaksResult,
|
|
84
|
+
},
|
|
85
|
+
summary: {
|
|
86
|
+
total_findings: semFindings + glFindings,
|
|
87
|
+
semgrep_findings: semFindings,
|
|
88
|
+
gitleaks_findings: glFindings,
|
|
89
|
+
},
|
|
90
|
+
environment: {
|
|
91
|
+
node_version: process.version,
|
|
92
|
+
platform: process.platform,
|
|
93
|
+
hostname: process.env.COMPUTERNAME || process.env.HOSTNAME || "unknown",
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
// 注册入口
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
/**
|
|
101
|
+
* 向 McpServer 实例注册 run_security_scan 工具。
|
|
102
|
+
* 采用 server.tool() 高层 API,与官方 server.registerTool() 等价但更简洁。
|
|
103
|
+
*/
|
|
104
|
+
export function registerRunSecurityScan(server) {
|
|
105
|
+
server.registerTool(TOOL_NAME, TOOL_CONFIG, async (args) => {
|
|
106
|
+
const { target_path } = SecurityScanInput.parse(args);
|
|
107
|
+
// --- 1. 敏感路径拦截 ---
|
|
108
|
+
if (isSensitivePath(target_path)) {
|
|
109
|
+
logger.warn(`拒绝敏感路径扫描请求: ${target_path}`);
|
|
110
|
+
return {
|
|
111
|
+
content: [
|
|
112
|
+
{
|
|
113
|
+
type: "text",
|
|
114
|
+
text: JSON.stringify({
|
|
115
|
+
target_path,
|
|
116
|
+
scan_time: new Date().toISOString(),
|
|
117
|
+
error: "出于安全原因,拒绝扫描系统敏感目录",
|
|
118
|
+
tools: { semgrep: { available: false }, gitleaks: { available: false } },
|
|
119
|
+
}, null, 2),
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
// --- 2. 路径验证 ---
|
|
125
|
+
const verified = await verifyDirectory(target_path);
|
|
126
|
+
if (!verified.ok) {
|
|
127
|
+
logger.warn(`路径验证失败: ${verified.reason}`);
|
|
128
|
+
return {
|
|
129
|
+
content: [
|
|
130
|
+
{
|
|
131
|
+
type: "text",
|
|
132
|
+
text: JSON.stringify({
|
|
133
|
+
target_path,
|
|
134
|
+
scan_time: new Date().toISOString(),
|
|
135
|
+
error: verified.reason ?? "路径不可访问",
|
|
136
|
+
tools: { semgrep: { available: false }, gitleaks: { available: false } },
|
|
137
|
+
}, null, 2),
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
// --- 3. 执行扫描 ---
|
|
143
|
+
logger.info(`开始安全扫描: ${verified.resolved}`);
|
|
144
|
+
const { semgrep, gitleaks, elapsedMs } = await executeScan(verified.resolved);
|
|
145
|
+
// --- 4. 裁剪输出(非原始模式) ---
|
|
146
|
+
const wantRaw = process.env.INCLUDE_RAW_OUTPUT === "true";
|
|
147
|
+
const semgrepOut = {
|
|
148
|
+
available: semgrep.available,
|
|
149
|
+
...(semgrep.error ? { error: semgrep.error } : {}),
|
|
150
|
+
...(semgrep.installGuide ? { install_guide: semgrep.installGuide } : {}),
|
|
151
|
+
findings: wantRaw ? semgrep.findings : trimSemgrep(semgrep.findings),
|
|
152
|
+
};
|
|
153
|
+
const gitleaksOut = {
|
|
154
|
+
available: gitleaks.available,
|
|
155
|
+
...(gitleaks.error ? { error: gitleaks.error } : {}),
|
|
156
|
+
...(gitleaks.installGuide ? { install_guide: gitleaks.installGuide } : {}),
|
|
157
|
+
findings: wantRaw ? gitleaks.findings : trimGitleaks(gitleaks.findings),
|
|
158
|
+
};
|
|
159
|
+
// --- 5. 组装报告 ---
|
|
160
|
+
const report = buildReport(target_path, verified.resolved, elapsedMs, semgrepOut, gitleaksOut);
|
|
161
|
+
logger.info(`扫描完成: ${report.summary.total_findings} 个发现, 耗时 ${elapsedMs}ms`);
|
|
162
|
+
return {
|
|
163
|
+
content: [
|
|
164
|
+
{
|
|
165
|
+
type: "text",
|
|
166
|
+
text: JSON.stringify(report, null, 2),
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
};
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=run-security-scan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-security-scan.js","sourceRoot":"","sources":["../../src/tools/run-security-scan.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stdio 传输启动器 — 通过标准输入/输出运行 MCP 服务器。
|
|
3
|
+
*
|
|
4
|
+
* 这是默认的传输模式,适用于 Claude Desktop 本地集成。
|
|
5
|
+
* 进程 stdout 承载 MCP 协议 JSON-RPC 消息,stderr 承载日志。
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* 启动 stdio 模式服务器。
|
|
9
|
+
*
|
|
10
|
+
* 流程:
|
|
11
|
+
* 1. 预热检测 semgrep / gitleaks 可用性(仅日志,不阻塞)
|
|
12
|
+
* 2. 创建 McpServer 实例并注册工具
|
|
13
|
+
* 3. 连接到 StdioServerTransport
|
|
14
|
+
* 4. 注册 SIGINT / SIGTERM 优雅退出
|
|
15
|
+
*/
|
|
16
|
+
export declare function launchStdio(): Promise<void>;
|
|
17
|
+
//# sourceMappingURL=stdio.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stdio.d.ts","sourceRoot":"","sources":["../../src/transports/stdio.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH;;;;;;;;GAQG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAwCjD"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* stdio 传输启动器 — 通过标准输入/输出运行 MCP 服务器。
|
|
3
|
+
*
|
|
4
|
+
* 这是默认的传输模式,适用于 Claude Desktop 本地集成。
|
|
5
|
+
* 进程 stdout 承载 MCP 协议 JSON-RPC 消息,stderr 承载日志。
|
|
6
|
+
*/
|
|
7
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
|
+
import { createServer } from "../server.js";
|
|
9
|
+
import { checkDependencies } from "../lib/executor.js";
|
|
10
|
+
import { logger } from "../lib/logger.js";
|
|
11
|
+
/**
|
|
12
|
+
* 启动 stdio 模式服务器。
|
|
13
|
+
*
|
|
14
|
+
* 流程:
|
|
15
|
+
* 1. 预热检测 semgrep / gitleaks 可用性(仅日志,不阻塞)
|
|
16
|
+
* 2. 创建 McpServer 实例并注册工具
|
|
17
|
+
* 3. 连接到 StdioServerTransport
|
|
18
|
+
* 4. 注册 SIGINT / SIGTERM 优雅退出
|
|
19
|
+
*/
|
|
20
|
+
export async function launchStdio() {
|
|
21
|
+
logger.info("启动 stdio 传输模式...");
|
|
22
|
+
// --- 1. 预热检测 ---
|
|
23
|
+
const deps = await checkDependencies();
|
|
24
|
+
if (!deps.semgrep.installed || !deps.gitleaks.installed) {
|
|
25
|
+
logger.warn("部分依赖工具未安装,扫描时将在报告中附带安装指引", {
|
|
26
|
+
semgrep: deps.semgrep.installed,
|
|
27
|
+
gitleaks: deps.gitleaks.installed,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
// --- 2. 创建服务器 ---
|
|
31
|
+
const { server, shutdown } = createServer();
|
|
32
|
+
// --- 3. 连接传输 ---
|
|
33
|
+
const transport = new StdioServerTransport();
|
|
34
|
+
await server.connect(transport);
|
|
35
|
+
logger.info("服务器已就绪,等待客户端请求...");
|
|
36
|
+
// --- 4. 信号处理 ---
|
|
37
|
+
const handleSignal = async (signal) => {
|
|
38
|
+
logger.info(`收到 ${signal} 信号,准备退出`);
|
|
39
|
+
await shutdown();
|
|
40
|
+
process.exit(0);
|
|
41
|
+
};
|
|
42
|
+
// 阻止重复注册(防止多次信号触发重复清理)
|
|
43
|
+
process.once("SIGINT", () => handleSignal("SIGINT"));
|
|
44
|
+
process.once("SIGTERM", () => handleSignal("SIGTERM"));
|
|
45
|
+
// 未捕获异常兜底
|
|
46
|
+
process.on("unhandledRejection", (reason) => {
|
|
47
|
+
logger.error("未处理的 Promise 拒绝", { detail: String(reason) });
|
|
48
|
+
});
|
|
49
|
+
process.on("uncaughtException", (err) => {
|
|
50
|
+
logger.error("未捕获的异常", { detail: err.message });
|
|
51
|
+
shutdown().finally(() => process.exit(1));
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=stdio.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stdio.js","sourceRoot":"","sources":["../../src/transports/stdio.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE1C;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IAEhC,kBAAkB;IAClB,MAAM,IAAI,GAAG,MAAM,iBAAiB,EAAE,CAAC;IACvC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC;QACxD,MAAM,CAAC,IAAI,CAAC,0BAA0B,EAAE;YACtC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS;YAC/B,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,SAAS;SAClC,CAAC,CAAC;IACL,CAAC;IAED,mBAAmB;IACnB,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,YAAY,EAAE,CAAC;IAE5C,kBAAkB;IAClB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEhC,MAAM,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IAEjC,kBAAkB;IAClB,MAAM,YAAY,GAAG,KAAK,EAAE,MAAc,EAAE,EAAE;QAC5C,MAAM,CAAC,IAAI,CAAC,MAAM,MAAM,UAAU,CAAC,CAAC;QACpC,MAAM,QAAQ,EAAE,CAAC;QACjB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC;IAEF,uBAAuB;IACvB,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC;IACrD,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC;IAEvD,UAAU;IACV,OAAO,CAAC,EAAE,CAAC,oBAAoB,EAAE,CAAC,MAAM,EAAE,EAAE;QAC1C,MAAM,CAAC,KAAK,CAAC,iBAAiB,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IACH,OAAO,CAAC,EAAE,CAAC,mBAAmB,EAAE,CAAC,GAAG,EAAE,EAAE;QACtC,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QAChD,QAAQ,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;AACL,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "luvv-mcp-server",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "生产级安全审计 MCP Server — 封装 semgrep + gitleaks,通过 stdio 协议为 Claude Desktop 提供自动化代码扫描能力",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/main.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"start": "node dist/main.js",
|
|
10
|
+
"dev": "tsc --watch",
|
|
11
|
+
"clean": "rm -rf dist",
|
|
12
|
+
"test": "vitest run",
|
|
13
|
+
"test:watch": "vitest",
|
|
14
|
+
"test:coverage": "vitest run --coverage",
|
|
15
|
+
"prestart": "npm run build"
|
|
16
|
+
},
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=20.0.0"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@modelcontextprotocol/sdk": "^1.6.1",
|
|
22
|
+
"zod": "^3.23.8"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/node": "^20.14.0",
|
|
26
|
+
"typescript": "^5.5.0",
|
|
27
|
+
"vitest": "^2.1.0",
|
|
28
|
+
"@vitest/coverage-v8": "^2.1.0"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"mcp",
|
|
32
|
+
"security",
|
|
33
|
+
"audit",
|
|
34
|
+
"semgrep",
|
|
35
|
+
"gitleaks",
|
|
36
|
+
"claude",
|
|
37
|
+
"sast"
|
|
38
|
+
],
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"author": "Luvv"
|
|
41
|
+
}
|