@saiteja1123/mcp-server 1.1.2 → 1.1.3
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 +3 -1
- package/src/api-scan.mjs +36 -1
- package/src/cli.js +209 -30
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saiteja1123/mcp-server",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Vibesecur MCP security scanner - one-folder locking, cross-IDE, Cursor/VSCode/Windsurf",
|
|
6
6
|
"type": "module",
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
"start": "node ./src/server.js",
|
|
17
17
|
"dev": "node --watch ./src/server.js",
|
|
18
18
|
"bind": "node ./src/cli.js bind",
|
|
19
|
+
"init": "node ./src/cli.js init",
|
|
20
|
+
"doctor": "node ./src/cli.js doctor",
|
|
19
21
|
"test": "node --input-type=module --eval \"import('./src/server.js')\""
|
|
20
22
|
},
|
|
21
23
|
"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;
|