@saiteja1123/mcp-server 1.1.2 → 1.1.4
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/package.json +4 -1
- package/src/api-scan.mjs +36 -1
- package/src/cli.js +209 -30
- package/src/index.js +3 -4
- package/src/rule-engine/index.js +1 -1
- package/src/rule-engine/localScan.js +2 -0
- package/src/server.js +62 -5
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saiteja1123/mcp-server",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"private": false,
|
|
5
|
+
"license": "MIT",
|
|
5
6
|
"description": "Vibesecur MCP security scanner - one-folder locking, cross-IDE, Cursor/VSCode/Windsurf",
|
|
6
7
|
"type": "module",
|
|
7
8
|
"main": "./src/index.js",
|
|
@@ -16,6 +17,8 @@
|
|
|
16
17
|
"start": "node ./src/server.js",
|
|
17
18
|
"dev": "node --watch ./src/server.js",
|
|
18
19
|
"bind": "node ./src/cli.js bind",
|
|
20
|
+
"init": "node ./src/cli.js init",
|
|
21
|
+
"doctor": "node ./src/cli.js doctor",
|
|
19
22
|
"test": "node --input-type=module --eval \"import('./src/server.js')\""
|
|
20
23
|
},
|
|
21
24
|
"exports": {
|
package/src/api-scan.mjs
CHANGED
|
@@ -19,12 +19,47 @@ export function getProjectHashForPath(rootPath) {
|
|
|
19
19
|
return sha256Hex(`vibesecur:mcp:${resolved}`);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
function normalizeApiBase(raw) {
|
|
22
|
+
export function normalizeApiBase(raw) {
|
|
23
23
|
if (!raw || typeof raw !== 'string') return '';
|
|
24
24
|
const u = raw.trim().replace(/\/$/, '');
|
|
25
25
|
return u.endsWith('/api/v1') ? u : `${u}/api/v1`;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/** GET origin (no /api/v1) for lightweight health checks. */
|
|
29
|
+
export function getApiOriginFromBase(raw) {
|
|
30
|
+
const normalized = normalizeApiBase(raw);
|
|
31
|
+
if (!normalized) return '';
|
|
32
|
+
return normalized.replace(/\/api\/v1\/?$/, '') || normalized;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Quick reachability check (GET app root). Does not run a scan.
|
|
37
|
+
* @param {string} [baseUrl] - e.g. https://vibesecur.onrender.com (optional /api/v1 ok)
|
|
38
|
+
*/
|
|
39
|
+
export async function pingBackend(baseUrl) {
|
|
40
|
+
const raw = (
|
|
41
|
+
baseUrl
|
|
42
|
+
|| process.env.VIBESECUR_API_BASE
|
|
43
|
+
|| process.env.VIBESECUR_API_URL
|
|
44
|
+
|| 'https://vibesecur.onrender.com'
|
|
45
|
+
).trim();
|
|
46
|
+
const origin = getApiOriginFromBase(raw);
|
|
47
|
+
if (!origin) {
|
|
48
|
+
return { ok: false, skipped: true, message: 'No API base URL' };
|
|
49
|
+
}
|
|
50
|
+
const url = origin.endsWith('/') ? origin.slice(0, -1) : origin;
|
|
51
|
+
try {
|
|
52
|
+
const ctrl = typeof AbortSignal !== 'undefined' && AbortSignal.timeout
|
|
53
|
+
? AbortSignal.timeout(12000)
|
|
54
|
+
: undefined;
|
|
55
|
+
const res = await fetch(`${url}/`, { method: 'GET', signal: ctrl });
|
|
56
|
+
const ok = true;
|
|
57
|
+
return { ok, status: res.status, url: `${url}/` };
|
|
58
|
+
} catch (e) {
|
|
59
|
+
return { ok: false, error: e.message };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
28
63
|
export async function postRemoteLocalScan({
|
|
29
64
|
code,
|
|
30
65
|
lang = 'auto',
|
package/src/cli.js
CHANGED
|
@@ -1,19 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* vibesecur-mcp CLI
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
*
|
|
5
|
+
* Quick start: npx -y @saiteja1123/mcp-server init
|
|
6
|
+
*
|
|
7
|
+
* Commands:
|
|
8
|
+
* init [folder] bind folder + print IDE configs (optional --write=...)
|
|
9
|
+
* doctor [folder] check lock, env hints, backend reachability
|
|
10
|
+
* bind <folder>
|
|
11
|
+
* rebind <folder>
|
|
12
|
+
* status [folder]
|
|
13
|
+
* config [folder]
|
|
14
|
+
* start MCP stdio server
|
|
15
|
+
*
|
|
16
|
+
* Flags (init): --api-base=URL --skip-bind --write=cursor|vscode|windsurf|all
|
|
10
17
|
*/
|
|
11
18
|
|
|
19
|
+
import fs from 'fs/promises';
|
|
12
20
|
import path from 'path';
|
|
21
|
+
import os from 'os';
|
|
22
|
+
import { createRequire } from 'module';
|
|
13
23
|
import { fileURLToPath } from 'url';
|
|
14
24
|
import { createLock, rebindLock, diagnosticLock, readLock } from './lock.mjs';
|
|
25
|
+
import { pingBackend } from './api-scan.mjs';
|
|
15
26
|
|
|
16
|
-
const
|
|
27
|
+
const require = createRequire(import.meta.url);
|
|
28
|
+
const mcpPkg = require('../package.json');
|
|
29
|
+
const NPM_PACKAGE_NAME = mcpPkg.name || '@saiteja1123/mcp-server';
|
|
30
|
+
const DEFAULT_API_BASE = 'https://vibesecur.onrender.com';
|
|
31
|
+
|
|
32
|
+
const serverPath = fileURLToPath(new URL('./server.js', import.meta.url));
|
|
17
33
|
|
|
18
34
|
const BOLD = '\x1b[1m';
|
|
19
35
|
const GREEN = '\x1b[32m';
|
|
@@ -27,15 +43,106 @@ function ok(t) { return `${GREEN}✓ ${t}${RESET}`; }
|
|
|
27
43
|
function err(t) { return `${RED}✗ ${t}${RESET}`; }
|
|
28
44
|
function dim(t) { return `${DIM}${t}${RESET}`; }
|
|
29
45
|
|
|
30
|
-
|
|
46
|
+
function parseArgv(argv) {
|
|
47
|
+
const flags = {};
|
|
48
|
+
const positional = [];
|
|
49
|
+
for (const a of argv) {
|
|
50
|
+
if (a.startsWith('--')) {
|
|
51
|
+
const eq = a.indexOf('=');
|
|
52
|
+
if (eq === -1) flags[a.slice(2)] = true;
|
|
53
|
+
else flags[a.slice(2, eq)] = a.slice(eq + 1);
|
|
54
|
+
} else {
|
|
55
|
+
positional.push(a);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return { flags, positional };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const { flags, positional } = parseArgv(process.argv.slice(2));
|
|
62
|
+
const command = positional[0];
|
|
63
|
+
const arg = positional[1];
|
|
31
64
|
|
|
32
65
|
function serverCmd() {
|
|
33
|
-
|
|
34
|
-
|
|
66
|
+
const normalizedPath = serverPath.replace(/\\/g, '/');
|
|
67
|
+
if (normalizedPath.includes('/node_modules/')) {
|
|
68
|
+
return { command: 'npx', args: ['-y', NPM_PACKAGE_NAME, 'start'] };
|
|
35
69
|
}
|
|
36
70
|
return { command: 'node', args: [serverPath] };
|
|
37
71
|
}
|
|
38
72
|
|
|
73
|
+
function buildEnv(folder, token, apiBase) {
|
|
74
|
+
return {
|
|
75
|
+
VIBESECUR_INSTALL_TOKEN: token,
|
|
76
|
+
VIBESECUR_BOUND_ROOT: folder,
|
|
77
|
+
VIBESECUR_API_BASE: apiBase || DEFAULT_API_BASE,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function printIdeBlocks(sc, env) {
|
|
82
|
+
console.log(`${BOLD}-- Cursor (~/.cursor/mcp.json) --${RESET}`);
|
|
83
|
+
console.log(JSON.stringify({ mcpServers: { vibesecur: { command: sc.command, args: sc.args, env } } }, null, 2));
|
|
84
|
+
|
|
85
|
+
console.log(`\n${BOLD}-- VS Code (project .vscode/mcp.json) --${RESET}`);
|
|
86
|
+
console.log(JSON.stringify({ servers: { vibesecur: { type: 'stdio', command: sc.command, args: sc.args, env } } }, null, 2));
|
|
87
|
+
|
|
88
|
+
console.log(`\n${BOLD}-- Windsurf (~/.codeium/windsurf/mcp_config.json) --${RESET}`);
|
|
89
|
+
console.log(JSON.stringify({ mcpServers: { vibesecur: { command: sc.command, args: sc.args, env } } }, null, 2));
|
|
90
|
+
|
|
91
|
+
console.log(`\n${BOLD}-- Generic env (paste into your MCP client) --${RESET}`);
|
|
92
|
+
console.log(`command: ${sc.command} ${sc.args.join(' ')}`);
|
|
93
|
+
Object.entries(env).forEach(([k, v]) => console.log(` ${k}=${v}`));
|
|
94
|
+
console.log();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function mergeJsonFile(filePath, merger) {
|
|
98
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
99
|
+
let data = {};
|
|
100
|
+
try {
|
|
101
|
+
const raw = await fs.readFile(filePath, 'utf8');
|
|
102
|
+
data = JSON.parse(raw);
|
|
103
|
+
} catch {
|
|
104
|
+
data = {};
|
|
105
|
+
}
|
|
106
|
+
const next = merger(data);
|
|
107
|
+
await fs.writeFile(filePath, JSON.stringify(next, null, 2), 'utf8');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function writeIdeConfig(which, sc, env) {
|
|
111
|
+
const entry = { command: sc.command, args: sc.args, env };
|
|
112
|
+
const vscodeEntry = { type: 'stdio', command: sc.command, args: sc.args, env };
|
|
113
|
+
|
|
114
|
+
if (which === 'cursor' || which === 'all') {
|
|
115
|
+
const file = path.join(os.homedir(), '.cursor', 'mcp.json');
|
|
116
|
+
await mergeJsonFile(file, (data) => {
|
|
117
|
+
const out = { ...data };
|
|
118
|
+
out.mcpServers = { ...(out.mcpServers || {}), vibesecur: entry };
|
|
119
|
+
return out;
|
|
120
|
+
});
|
|
121
|
+
console.log(ok(`Wrote Cursor MCP config: ${file}`));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (which === 'vscode' || which === 'all') {
|
|
125
|
+
const folder = env.VIBESECUR_BOUND_ROOT;
|
|
126
|
+
const file = path.join(folder, '.vscode', 'mcp.json');
|
|
127
|
+
await mergeJsonFile(file, (data) => {
|
|
128
|
+
const out = { ...data };
|
|
129
|
+
out.servers = { ...(out.servers || {}), vibesecur: vscodeEntry };
|
|
130
|
+
return out;
|
|
131
|
+
});
|
|
132
|
+
console.log(ok(`Wrote VS Code MCP config: ${file}`));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (which === 'windsurf' || which === 'all') {
|
|
136
|
+
const file = path.join(os.homedir(), '.codeium', 'windsurf', 'mcp_config.json');
|
|
137
|
+
await mergeJsonFile(file, (data) => {
|
|
138
|
+
const out = { ...data };
|
|
139
|
+
out.mcpServers = { ...(out.mcpServers || {}), vibesecur: entry };
|
|
140
|
+
return out;
|
|
141
|
+
});
|
|
142
|
+
console.log(ok(`Wrote Windsurf MCP config: ${file}`));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
39
146
|
async function cmdBind() {
|
|
40
147
|
const folder = path.resolve(arg || process.cwd());
|
|
41
148
|
const account = process.env.VIBESECUR_ACCOUNT || 'anonymous';
|
|
@@ -89,46 +196,118 @@ async function cmdConfig() {
|
|
|
89
196
|
const folder = path.resolve(arg || process.cwd());
|
|
90
197
|
const lock = await readLock(folder);
|
|
91
198
|
const token = lock?.installToken || 'YOUR_INSTALL_TOKEN';
|
|
199
|
+
const apiBase = (flags['api-base'] || process.env.VIBESECUR_API_BASE || DEFAULT_API_BASE).trim();
|
|
92
200
|
|
|
93
201
|
console.log(`\n${h('Vibesecur MCP - IDE Config Snippets')}`);
|
|
94
202
|
console.log(`Folder: ${folder}`);
|
|
95
203
|
if (!lock) console.log(err(`No lock found. Run: vibesecur-mcp bind ${folder}`));
|
|
96
204
|
console.log();
|
|
97
205
|
|
|
98
|
-
const env =
|
|
99
|
-
VIBESECUR_INSTALL_TOKEN: token,
|
|
100
|
-
VIBESECUR_BOUND_ROOT: folder,
|
|
101
|
-
VIBESECUR_API_BASE: 'https://api.vibesecur.com',
|
|
102
|
-
};
|
|
206
|
+
const env = buildEnv(folder, token, apiBase);
|
|
103
207
|
const sc = serverCmd();
|
|
208
|
+
printIdeBlocks(sc, env);
|
|
209
|
+
}
|
|
104
210
|
|
|
105
|
-
|
|
106
|
-
|
|
211
|
+
async function cmdInit() {
|
|
212
|
+
const folder = path.resolve(arg || process.cwd());
|
|
213
|
+
const apiBase = (flags['api-base'] || process.env.VIBESECUR_API_BASE || DEFAULT_API_BASE).trim();
|
|
214
|
+
const account = process.env.VIBESECUR_ACCOUNT || 'anonymous';
|
|
215
|
+
const skipBind = flags['skip-bind'] === true || flags['skip-bind'] === 'true';
|
|
107
216
|
|
|
108
|
-
console.log(`\n${
|
|
109
|
-
console.log(
|
|
217
|
+
console.log(`\n${h('Vibesecur MCP - Init')}`);
|
|
218
|
+
console.log(`Project folder: ${folder}`);
|
|
219
|
+
console.log(`API base: ${apiBase}`);
|
|
220
|
+
console.log(`Package: ${NPM_PACKAGE_NAME}\n`);
|
|
110
221
|
|
|
111
|
-
|
|
112
|
-
|
|
222
|
+
let lock = await readLock(folder);
|
|
223
|
+
if (!lock) {
|
|
224
|
+
if (skipBind) {
|
|
225
|
+
console.log(err('No lock in this folder. Run without --skip-bind, or: vibesecur-mcp bind <folder>'));
|
|
226
|
+
process.exit(1);
|
|
227
|
+
}
|
|
228
|
+
lock = await createLock({ rootPath: folder, account });
|
|
229
|
+
console.log(ok(`Created lock: ${path.join(folder, '.vibesecur', 'lock.json')}`));
|
|
230
|
+
} else {
|
|
231
|
+
console.log(ok('Existing lock found (reusing token). Use rebind to rotate.'));
|
|
232
|
+
}
|
|
113
233
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
console.log();
|
|
234
|
+
const env = buildEnv(folder, lock.installToken, apiBase);
|
|
235
|
+
const sc = serverCmd();
|
|
236
|
+
|
|
237
|
+
console.log(`\n${BOLD}Next:${RESET} paste ONE block below into your IDE MCP settings, then reload the IDE.\n`);
|
|
238
|
+
printIdeBlocks(sc, env);
|
|
239
|
+
|
|
240
|
+
const write = flags.write;
|
|
241
|
+
if (write) {
|
|
242
|
+
const w = String(write).toLowerCase();
|
|
243
|
+
if (!['cursor', 'vscode', 'windsurf', 'all'].includes(w)) {
|
|
244
|
+
console.log(err(`Invalid --write=${write}. Use: cursor | vscode | windsurf | all`));
|
|
245
|
+
process.exit(1);
|
|
246
|
+
}
|
|
247
|
+
await writeIdeConfig(w, sc, env);
|
|
248
|
+
console.log(dim('Reload your IDE so it picks up the new MCP config.'));
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async function cmdDoctor() {
|
|
253
|
+
const folder = path.resolve(arg || process.cwd());
|
|
254
|
+
const apiBase = (flags['api-base'] || process.env.VIBESECUR_API_BASE || DEFAULT_API_BASE).trim();
|
|
255
|
+
|
|
256
|
+
console.log(`\n${h('Vibesecur MCP - Doctor')}`);
|
|
257
|
+
console.log(`Folder: ${folder}`);
|
|
258
|
+
console.log(`API base: ${apiBase}\n`);
|
|
259
|
+
|
|
260
|
+
const lock = await readLock(folder);
|
|
261
|
+
if (!lock) {
|
|
262
|
+
console.log(err('No lock file. Run: vibesecur-mcp init'));
|
|
263
|
+
} else {
|
|
264
|
+
console.log(ok('Lock file present'));
|
|
265
|
+
const diag = await diagnosticLock(folder);
|
|
266
|
+
console.log(diag.healthy ? ok('Lock diagnostic: healthy') : err('Lock diagnostic: issues'));
|
|
267
|
+
if (diag.issues?.length) diag.issues.forEach((i) => console.log(` - ${i}`));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const tokenEnv = process.env.VIBESECUR_INSTALL_TOKEN;
|
|
271
|
+
const rootEnv = process.env.VIBESECUR_BOUND_ROOT;
|
|
272
|
+
console.log(`\n${BOLD}Shell env (optional; IDE sets these when MCP runs):${RESET}`);
|
|
273
|
+
console.log(` VIBESECUR_INSTALL_TOKEN: ${tokenEnv ? dim('set') : dim('not set in this shell (normal)')}`);
|
|
274
|
+
console.log(` VIBESECUR_BOUND_ROOT: ${rootEnv ? dim('set') : dim('not set in this shell (normal)')}`);
|
|
275
|
+
console.log(` VIBESECUR_API_BASE: ${process.env.VIBESECUR_API_BASE ? dim(process.env.VIBESECUR_API_BASE) : dim(`${DEFAULT_API_BASE} (default)`)}`);
|
|
276
|
+
|
|
277
|
+
const ping = await pingBackend(apiBase);
|
|
278
|
+
if (ping.skipped) {
|
|
279
|
+
console.log(`\n${err('Backend ping skipped')}`);
|
|
280
|
+
} else if (ping.ok) {
|
|
281
|
+
const note = ping.status >= 400 ? ' (server responded; check path if unexpected)' : '';
|
|
282
|
+
console.log(`\n${ok(`Backend responded HTTP ${ping.status}${note}`)}`);
|
|
283
|
+
console.log(dim(` ${ping.url || ''}`));
|
|
284
|
+
} else {
|
|
285
|
+
console.log(`\n${err('Backend not reachable')}`);
|
|
286
|
+
if (ping.status !== undefined) console.log(` HTTP ${ping.status}`);
|
|
287
|
+
if (ping.error) console.log(` ${ping.error}`);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
console.log(`\n${BOLD}CLI:${RESET} ${NPM_PACKAGE_NAME}@${mcpPkg.version}`);
|
|
291
|
+
console.log(dim('If IDE still fails: run init again, paste fresh config, reload IDE.\n'));
|
|
118
292
|
}
|
|
119
293
|
|
|
120
294
|
function usage() {
|
|
121
295
|
console.log(`\n${h('Vibesecur MCP')}`);
|
|
122
|
-
console.log('
|
|
123
|
-
console.log('
|
|
124
|
-
console.log('
|
|
125
|
-
console.log('
|
|
126
|
-
console.log('
|
|
296
|
+
console.log(` ${dim('Quick start:')} vibesecur-mcp init`);
|
|
297
|
+
console.log(' init [folder] bind + print IDE configs [--api-base=URL] [--write=cursor|vscode|windsurf|all] [--skip-bind]');
|
|
298
|
+
console.log(' doctor [folder] check lock + backend [--api-base=URL]');
|
|
299
|
+
console.log(' bind <folder> create lock only');
|
|
300
|
+
console.log(' rebind <folder> rotate token');
|
|
301
|
+
console.log(' status [folder] lock health');
|
|
302
|
+
console.log(' config [folder] print snippets only');
|
|
303
|
+
console.log(' start MCP server (stdio)\n');
|
|
127
304
|
process.exit(1);
|
|
128
305
|
}
|
|
129
306
|
|
|
130
307
|
try {
|
|
131
308
|
switch (command) {
|
|
309
|
+
case 'init': await cmdInit(); break;
|
|
310
|
+
case 'doctor': await cmdDoctor(); break;
|
|
132
311
|
case 'bind': await cmdBind(); break;
|
|
133
312
|
case 'rebind': await cmdRebind(); break;
|
|
134
313
|
case 'status': await cmdStatus(); break;
|
package/src/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Re-
|
|
3
|
-
*
|
|
4
|
-
* `@vibesecur/rule-engine` directly.
|
|
2
|
+
* Re-export bundled local rule engine for MCP tooling.
|
|
3
|
+
* This keeps the MCP package self-contained at runtime.
|
|
5
4
|
*/
|
|
6
|
-
export * from '
|
|
5
|
+
export * from './rule-engine/index.js';
|
package/src/rule-engine/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* rule-engine/index.js
|
|
3
3
|
* Bundled inline - no external @vibesecur/rule-engine dep needed.
|
|
4
|
-
* This allows `npx @
|
|
4
|
+
* This allows `npx @saiteja1123/mcp-server` to work standalone.
|
|
5
5
|
*/
|
|
6
6
|
export { JS_RULES, PY_RULES, CHECKLIST } from './rules.js';
|
|
7
7
|
export { localScan } from './localScan.js';
|
package/src/server.js
CHANGED
|
@@ -110,7 +110,11 @@ function humanRepoSummary(meta, agg) {
|
|
|
110
110
|
|
|
111
111
|
function flattenFindings(fileResults) {
|
|
112
112
|
return fileResults.flatMap((fr) =>
|
|
113
|
-
(fr.result.findings || []).map((f) => ({
|
|
113
|
+
(fr.result.findings || []).map((f) => ({
|
|
114
|
+
...f,
|
|
115
|
+
filePath: fr.filePath,
|
|
116
|
+
snippetPreview: f.snippetPreview || f.snippet || '',
|
|
117
|
+
})),
|
|
114
118
|
);
|
|
115
119
|
}
|
|
116
120
|
|
|
@@ -124,10 +128,11 @@ function pickTopFindings(fileResults, n) {
|
|
|
124
128
|
return flat.slice(0, n).map((f) => ({
|
|
125
129
|
filePath: f.filePath,
|
|
126
130
|
lineNumber: f.lineNumber,
|
|
131
|
+
endLineNumber: f.endLineNumber || f.lineNumber,
|
|
127
132
|
ruleId: f.ruleId,
|
|
128
133
|
ruleName: f.ruleName,
|
|
129
134
|
severity: f.severity,
|
|
130
|
-
snippetPreview: (f.
|
|
135
|
+
snippetPreview: (f.snippetPreview || '').slice(0, 120),
|
|
131
136
|
}));
|
|
132
137
|
}
|
|
133
138
|
|
|
@@ -284,6 +289,11 @@ server.registerTool('scanFile', {
|
|
|
284
289
|
result = localScan(code, useLang);
|
|
285
290
|
}
|
|
286
291
|
const findings = result.findings || [];
|
|
292
|
+
const findingsWithLocation = findings.map((f) => ({
|
|
293
|
+
...f,
|
|
294
|
+
filePath: resolvedPath,
|
|
295
|
+
snippetPreview: f.snippetPreview || f.snippet || '',
|
|
296
|
+
}));
|
|
287
297
|
const bySev = findings.reduce((a, f) => {
|
|
288
298
|
a[f.severity] = (a[f.severity] || 0) + 1;
|
|
289
299
|
return a;
|
|
@@ -295,10 +305,13 @@ server.registerTool('scanFile', {
|
|
|
295
305
|
lang: useLang,
|
|
296
306
|
score: result.score,
|
|
297
307
|
grade: result.grade,
|
|
298
|
-
findings:
|
|
308
|
+
findings: findingsWithLocation.length,
|
|
299
309
|
bySeverity: bySev,
|
|
300
310
|
checklist: result.checklist,
|
|
301
|
-
result
|
|
311
|
+
result: {
|
|
312
|
+
...result,
|
|
313
|
+
findings: findingsWithLocation,
|
|
314
|
+
},
|
|
302
315
|
};
|
|
303
316
|
return { content: [{ type: 'text', text: JSON.stringify(body, null, 2) }], structuredContent: body };
|
|
304
317
|
} catch (e) {
|
|
@@ -323,6 +336,17 @@ server.registerTool('scanRepo', {
|
|
|
323
336
|
await ensureDirectory(resolvedRoot);
|
|
324
337
|
const { matchedFiles, limitedFiles, fileResults, aggregate, topRiskFiles } =
|
|
325
338
|
await gatherRepoScan(resolvedRoot, includeGlobs, excludeGlobs, maxFiles);
|
|
339
|
+
const allFindings = flattenFindings(fileResults).map((f) => ({
|
|
340
|
+
filePath: f.filePath,
|
|
341
|
+
lineNumber: f.lineNumber,
|
|
342
|
+
endLineNumber: f.endLineNumber || f.lineNumber,
|
|
343
|
+
ruleId: f.ruleId,
|
|
344
|
+
ruleName: f.ruleName,
|
|
345
|
+
severity: f.severity,
|
|
346
|
+
category: f.category,
|
|
347
|
+
snippetPreview: (f.snippetPreview || '').slice(0, 120),
|
|
348
|
+
fix: f.fix,
|
|
349
|
+
}));
|
|
326
350
|
const meta = buildScanMeta(resolvedRoot, includeGlobs, excludeGlobs, maxFiles, matchedFiles.length, limitedFiles.length);
|
|
327
351
|
const body = {
|
|
328
352
|
meta,
|
|
@@ -334,6 +358,7 @@ server.registerTool('scanRepo', {
|
|
|
334
358
|
summary: aggregate.summary,
|
|
335
359
|
checklist: aggregate.checklist,
|
|
336
360
|
topRiskFiles,
|
|
361
|
+
allFindings,
|
|
337
362
|
};
|
|
338
363
|
return { content: [{ type: 'text', text: JSON.stringify(body, null, 2) }], structuredContent: { ...body, fileResults } };
|
|
339
364
|
} catch (e) {
|
|
@@ -350,8 +375,16 @@ server.registerTool('scanSummary', {
|
|
|
350
375
|
excludeGlobs: z.array(z.string()).default(DEFAULT_EXCLUDE),
|
|
351
376
|
maxFiles: z.number().int().min(1).max(5000).default(200),
|
|
352
377
|
topFindings: z.number().int().min(1).max(50).default(20),
|
|
378
|
+
maxFindings: z.number().int().min(20).max(500).default(200),
|
|
353
379
|
},
|
|
354
|
-
}, async ({
|
|
380
|
+
}, async ({
|
|
381
|
+
rootPath,
|
|
382
|
+
includeGlobs = DEFAULT_INCLUDE,
|
|
383
|
+
excludeGlobs = DEFAULT_EXCLUDE,
|
|
384
|
+
maxFiles = 200,
|
|
385
|
+
topFindings = 20,
|
|
386
|
+
maxFindings = 200,
|
|
387
|
+
}) => {
|
|
355
388
|
try {
|
|
356
389
|
const guard = await guardPath(rootPath);
|
|
357
390
|
if (!guard.ok) return guardError(guard);
|
|
@@ -361,12 +394,24 @@ server.registerTool('scanSummary', {
|
|
|
361
394
|
await gatherRepoScan(resolvedRoot, includeGlobs, excludeGlobs, maxFiles);
|
|
362
395
|
const meta = buildScanMeta(resolvedRoot, includeGlobs, excludeGlobs, maxFiles, matchedFiles.length, limitedFiles.length);
|
|
363
396
|
const top = pickTopFindings(fileResults, topFindings);
|
|
397
|
+
const allFindings = flattenFindings(fileResults).slice(0, maxFindings).map((f) => ({
|
|
398
|
+
filePath: f.filePath,
|
|
399
|
+
lineNumber: f.lineNumber,
|
|
400
|
+
endLineNumber: f.endLineNumber || f.lineNumber,
|
|
401
|
+
ruleId: f.ruleId,
|
|
402
|
+
ruleName: f.ruleName,
|
|
403
|
+
severity: f.severity,
|
|
404
|
+
category: f.category,
|
|
405
|
+
snippetPreview: (f.snippetPreview || '').slice(0, 120),
|
|
406
|
+
fix: f.fix,
|
|
407
|
+
}));
|
|
364
408
|
const payload = {
|
|
365
409
|
meta,
|
|
366
410
|
humanSummary: humanRepoSummary(meta, aggregate),
|
|
367
411
|
summary: aggregate.summary,
|
|
368
412
|
checklist: { passed: aggregate.checklist.filter((c) => c.pass).length, total: aggregate.checklist.length },
|
|
369
413
|
topFindings: top,
|
|
414
|
+
allFindings,
|
|
370
415
|
};
|
|
371
416
|
return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }], structuredContent: payload };
|
|
372
417
|
} catch (e) {
|
|
@@ -396,6 +441,17 @@ server.registerTool('scanCurrentWorkspace', {
|
|
|
396
441
|
await ensureDirectory(guard.resolvedRoot);
|
|
397
442
|
const { matchedFiles, limitedFiles, fileResults, aggregate, topRiskFiles } =
|
|
398
443
|
await gatherRepoScan(guard.resolvedRoot, includeGlobs, excludeGlobs, maxFiles);
|
|
444
|
+
const allFindings = flattenFindings(fileResults).map((f) => ({
|
|
445
|
+
filePath: f.filePath,
|
|
446
|
+
lineNumber: f.lineNumber,
|
|
447
|
+
endLineNumber: f.endLineNumber || f.lineNumber,
|
|
448
|
+
ruleId: f.ruleId,
|
|
449
|
+
ruleName: f.ruleName,
|
|
450
|
+
severity: f.severity,
|
|
451
|
+
category: f.category,
|
|
452
|
+
snippetPreview: (f.snippetPreview || '').slice(0, 120),
|
|
453
|
+
fix: f.fix,
|
|
454
|
+
}));
|
|
399
455
|
const meta = buildScanMeta(guard.resolvedRoot, includeGlobs, excludeGlobs, maxFiles, matchedFiles.length, limitedFiles.length);
|
|
400
456
|
const body = {
|
|
401
457
|
meta,
|
|
@@ -407,6 +463,7 @@ server.registerTool('scanCurrentWorkspace', {
|
|
|
407
463
|
summary: aggregate.summary,
|
|
408
464
|
checklist: aggregate.checklist,
|
|
409
465
|
topRiskFiles,
|
|
466
|
+
allFindings,
|
|
410
467
|
};
|
|
411
468
|
return { content: [{ type: 'text', text: JSON.stringify(body, null, 2) }], structuredContent: { ...body, fileResults } };
|
|
412
469
|
} catch (e) {
|