voyageai-cli 1.30.0 → 1.30.2
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 +4 -4
- package/package.json +1 -1
- package/src/cli.js +8 -0
- package/src/commands/about.js +3 -3
- package/src/commands/chat.js +32 -11
- package/src/commands/code-search.js +751 -0
- package/src/commands/doctor.js +1 -1
- package/src/commands/export.js +124 -0
- package/src/commands/import.js +195 -0
- package/src/commands/index-workspace.js +243 -0
- package/src/commands/mcp-server.js +113 -3
- package/src/commands/playground.js +120 -4
- package/src/commands/quickstart.js +4 -4
- package/src/commands/workflow.js +132 -65
- package/src/lib/catalog.js +4 -2
- package/src/lib/code-search.js +315 -0
- package/src/lib/codegen.js +1 -1
- package/src/lib/explanations.js +3 -3
- package/src/lib/export/contexts/benchmark-export.js +27 -0
- package/src/lib/export/contexts/chat-export.js +41 -0
- package/src/lib/export/contexts/explore-export.js +22 -0
- package/src/lib/export/contexts/search-export.js +54 -0
- package/src/lib/export/contexts/workflow-export.js +80 -0
- package/src/lib/export/formats/clipboard-export.js +29 -0
- package/src/lib/export/formats/csv-export.js +45 -0
- package/src/lib/export/formats/json-export.js +50 -0
- package/src/lib/export/formats/markdown-export.js +189 -0
- package/src/lib/export/formats/mermaid-export.js +274 -0
- package/src/lib/export/formats/pdf-export.js +117 -0
- package/src/lib/export/formats/png-export.js +96 -0
- package/src/lib/export/formats/svg-export.js +116 -0
- package/src/lib/export/index.js +175 -0
- package/src/lib/github.js +226 -0
- package/src/lib/template-engine.js +154 -20
- package/src/lib/workflow-builder.js +753 -0
- package/src/lib/workflow-formatters.js +454 -0
- package/src/lib/workflow-input-cache.js +111 -0
- package/src/lib/workflow-scaffold.js +1 -1
- package/src/lib/workflow.js +297 -28
- package/src/mcp/install.js +280 -7
- package/src/mcp/schemas/index.js +170 -0
- package/src/mcp/server.js +19 -4
- package/src/mcp/tools/authoring.js +662 -0
- package/src/mcp/tools/code-search.js +620 -0
- package/src/mcp/tools/ingest.js +2 -5
- package/src/mcp/tools/retrieval.js +2 -15
- package/src/mcp/tools/workspace.js +452 -0
- package/src/mcp/utils.js +20 -0
- package/src/playground/announcements.md +52 -5
- package/src/playground/help/workflow-nodes.js +127 -2
- package/src/playground/index.html +17109 -12438
- package/src/playground/vendor/mermaid.min.js +2811 -0
- package/src/workflows/code-review.json +110 -0
- package/src/workflows/cost-analysis.json +5 -0
- package/src/workflows/rag-chat.json +165 -0
- package/src/workflows/tests/code-review.fresh-index.test.json +83 -0
- package/src/workflows/tests/code-review.happy-path.test.json +121 -0
- package/src/workflows/tests/code-review.no-question.test.json +70 -0
- package/src/workflows/tests/consistency-check.happy-path.test.json +28 -0
- package/src/workflows/tests/consistency-check.missing-source.test.json +26 -0
- package/src/workflows/tests/cost-analysis.happy-path.test.json +28 -0
- package/src/workflows/tests/enrich-and-ingest.happy-path.test.json +38 -0
- package/src/workflows/tests/enrich-and-ingest.notify-fails.test.json +38 -0
- package/src/workflows/tests/intelligent-ingest.all-filtered.test.json +26 -0
- package/src/workflows/tests/intelligent-ingest.happy-path.test.json +28 -0
- package/src/workflows/tests/kb-health-report.custom-queries.test.json +24 -0
- package/src/workflows/tests/kb-health-report.happy-path.test.json +26 -0
- package/src/workflows/tests/multi-collection-search.happy-path.test.json +40 -0
- package/src/workflows/tests/multi-collection-search.one-empty.test.json +28 -0
- package/src/workflows/tests/rag-chat.happy-path.test.json +26 -0
- package/src/workflows/tests/rag-chat.no-relevant-results.test.json +25 -0
- package/src/workflows/tests/research-and-summarize.happy-path.test.json +33 -0
- package/src/workflows/tests/research-and-summarize.no-results.test.json +29 -0
- package/src/workflows/tests/search-with-fallback.empty-both.test.json +24 -0
- package/src/workflows/tests/search-with-fallback.fallback-branch.test.json +24 -0
- package/src/workflows/tests/search-with-fallback.happy-path.test.json +27 -0
- package/src/workflows/tests/smart-ingest.duplicate-detected.test.json +34 -0
- package/src/workflows/tests/smart-ingest.happy-path.test.json +31 -0
- package/src/playground/assets/announcements/appstore.jpg +0 -0
- package/src/playground/assets/announcements/circuits.jpg +0 -0
- package/src/playground/assets/announcements/csvingest.jpg +0 -0
- package/src/playground/assets/announcements/green-wave.jpg +0 -0
package/src/mcp/install.js
CHANGED
|
@@ -19,11 +19,21 @@ const TARGETS = {
|
|
|
19
19
|
'claude-code': {
|
|
20
20
|
name: 'Claude Code',
|
|
21
21
|
configPath: () => path.join(os.homedir(), '.claude', 'settings.json'),
|
|
22
|
-
configKey: 'mcpServers',
|
|
22
|
+
configKey: 'mcpServers',
|
|
23
23
|
},
|
|
24
24
|
cursor: {
|
|
25
25
|
name: 'Cursor',
|
|
26
26
|
configPath: () => path.join(os.homedir(), '.cursor', 'mcp.json'),
|
|
27
|
+
// Cursor also supports workspace-level config
|
|
28
|
+
workspaceConfigPath: () => '.cursor/mcp.json',
|
|
29
|
+
postInstall: () => {
|
|
30
|
+
return [
|
|
31
|
+
'Cursor MCP integration tips:',
|
|
32
|
+
' • Restart Cursor to load the vai MCP server',
|
|
33
|
+
' • Use @vai in Cursor Chat to invoke vai tools',
|
|
34
|
+
' • Run "vai mcp status" to verify installation',
|
|
35
|
+
];
|
|
36
|
+
},
|
|
27
37
|
},
|
|
28
38
|
windsurf: {
|
|
29
39
|
name: 'Windsurf',
|
|
@@ -37,6 +47,39 @@ const TARGETS = {
|
|
|
37
47
|
return path.join(os.homedir(), '.config', 'Code', 'User', 'settings.json');
|
|
38
48
|
},
|
|
39
49
|
configKey: 'mcp.servers',
|
|
50
|
+
// VS Code needs MCP extension or GitHub Copilot with MCP support
|
|
51
|
+
postInstall: () => {
|
|
52
|
+
return [
|
|
53
|
+
'VS Code MCP integration tips:',
|
|
54
|
+
' • Install the vai VS Code extension for native integration',
|
|
55
|
+
' • Or use GitHub Copilot Chat with MCP support (requires Copilot subscription)',
|
|
56
|
+
' • Or use the vai-vscode extension from the vscode-extension/ folder',
|
|
57
|
+
' • Run "vai mcp-server --transport http" for HTTP-based integrations',
|
|
58
|
+
];
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
'vscode-insiders': {
|
|
62
|
+
name: 'VS Code Insiders',
|
|
63
|
+
configPath: () => {
|
|
64
|
+
if (process.platform === 'darwin') return path.join(os.homedir(), 'Library', 'Application Support', 'Code - Insiders', 'User', 'settings.json');
|
|
65
|
+
if (process.platform === 'win32') return path.join(process.env.APPDATA || '', 'Code - Insiders', 'User', 'settings.json');
|
|
66
|
+
return path.join(os.homedir(), '.config', 'Code - Insiders', 'User', 'settings.json');
|
|
67
|
+
},
|
|
68
|
+
configKey: 'mcp.servers',
|
|
69
|
+
postInstall: () => {
|
|
70
|
+
return [
|
|
71
|
+
'VS Code Insiders MCP integration tips:',
|
|
72
|
+
' • Insiders often has newer MCP features',
|
|
73
|
+
' • Install the vai VS Code extension for native integration',
|
|
74
|
+
' • Or use GitHub Copilot Chat with MCP support',
|
|
75
|
+
];
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
'cursor-workspace': {
|
|
79
|
+
name: 'Cursor (Workspace)',
|
|
80
|
+
configPath: () => null, // Requires workspace path
|
|
81
|
+
workspaceConfigPath: () => '.cursor/mcp.json',
|
|
82
|
+
requiresWorkspace: true,
|
|
40
83
|
},
|
|
41
84
|
};
|
|
42
85
|
|
|
@@ -48,14 +91,57 @@ function buildVaiEntry(opts = {}) {
|
|
|
48
91
|
command: 'vai',
|
|
49
92
|
args: ['mcp-server'],
|
|
50
93
|
};
|
|
94
|
+
|
|
95
|
+
// Transport configuration
|
|
51
96
|
if (opts.transport === 'http') {
|
|
52
97
|
entry.args.push('--transport', 'http');
|
|
53
98
|
if (opts.port) entry.args.push('--port', String(opts.port));
|
|
99
|
+
if (opts.sse === false) entry.args.push('--no-sse');
|
|
54
100
|
}
|
|
101
|
+
|
|
102
|
+
// Environment variables
|
|
103
|
+
const env = {};
|
|
55
104
|
const apiKey = opts.apiKey || process.env.VOYAGE_API_KEY || '';
|
|
56
|
-
|
|
57
|
-
|
|
105
|
+
const mongoUri = opts.mongodbUri || process.env.MONGODB_URI || '';
|
|
106
|
+
|
|
107
|
+
if (apiKey) env.VOYAGE_API_KEY = apiKey;
|
|
108
|
+
if (mongoUri) env.MONGODB_URI = mongoUri;
|
|
109
|
+
if (opts.db) env.VAI_DEFAULT_DB = opts.db;
|
|
110
|
+
if (opts.collection) env.VAI_DEFAULT_COLLECTION = opts.collection;
|
|
111
|
+
if (opts.verbose) env.VAI_MCP_VERBOSE = '1';
|
|
112
|
+
|
|
113
|
+
if (Object.keys(env).length > 0) {
|
|
114
|
+
entry.env = env;
|
|
58
115
|
}
|
|
116
|
+
|
|
117
|
+
return entry;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Build an extended vai MCP entry with tool descriptions for Cursor/VS Code.
|
|
122
|
+
* This helps the AI understand what tools are available.
|
|
123
|
+
*/
|
|
124
|
+
function buildVaiEntryWithMetadata(opts = {}) {
|
|
125
|
+
const entry = buildVaiEntry(opts);
|
|
126
|
+
|
|
127
|
+
// Add metadata for better AI tool discovery
|
|
128
|
+
entry.metadata = {
|
|
129
|
+
name: 'vai',
|
|
130
|
+
description: 'Voyage AI semantic search and RAG tools for MongoDB Atlas Vector Search',
|
|
131
|
+
tools: [
|
|
132
|
+
{ name: 'vai_query', description: 'Full RAG query with reranking' },
|
|
133
|
+
{ name: 'vai_search', description: 'Raw vector similarity search' },
|
|
134
|
+
{ name: 'vai_rerank', description: 'Rerank documents by relevance' },
|
|
135
|
+
{ name: 'vai_embed', description: 'Generate embeddings for text' },
|
|
136
|
+
{ name: 'vai_similarity', description: 'Compare text similarity' },
|
|
137
|
+
{ name: 'vai_ingest', description: 'Chunk and store documents' },
|
|
138
|
+
{ name: 'vai_collections', description: 'List MongoDB collections' },
|
|
139
|
+
{ name: 'vai_models', description: 'List available Voyage AI models' },
|
|
140
|
+
{ name: 'vai_explain', description: 'Explain embedding concepts' },
|
|
141
|
+
{ name: 'vai_estimate', description: 'Estimate embedding costs' },
|
|
142
|
+
],
|
|
143
|
+
};
|
|
144
|
+
|
|
59
145
|
return entry;
|
|
60
146
|
}
|
|
61
147
|
|
|
@@ -110,7 +196,7 @@ function setNestedKey(obj, keyPath, value) {
|
|
|
110
196
|
|
|
111
197
|
/**
|
|
112
198
|
* Install vai MCP entry into a target tool's config.
|
|
113
|
-
* Returns { installed: boolean, message: string }
|
|
199
|
+
* Returns { installed: boolean, message: string, tips?: string[] }
|
|
114
200
|
*/
|
|
115
201
|
function installTarget(targetKey, opts = {}) {
|
|
116
202
|
const target = TARGETS[targetKey];
|
|
@@ -118,7 +204,19 @@ function installTarget(targetKey, opts = {}) {
|
|
|
118
204
|
return { installed: false, message: `Unknown target: ${targetKey}. Available: ${Object.keys(TARGETS).join(', ')}` };
|
|
119
205
|
}
|
|
120
206
|
|
|
207
|
+
// Handle workspace-level installs
|
|
208
|
+
if (target.requiresWorkspace) {
|
|
209
|
+
if (!opts.workspacePath) {
|
|
210
|
+
return { installed: false, message: `${target.name}: requires --workspace-path option` };
|
|
211
|
+
}
|
|
212
|
+
return installWorkspaceConfig(target, opts);
|
|
213
|
+
}
|
|
214
|
+
|
|
121
215
|
const configPath = target.configPath();
|
|
216
|
+
if (!configPath) {
|
|
217
|
+
return { installed: false, message: `${target.name}: config path not available` };
|
|
218
|
+
}
|
|
219
|
+
|
|
122
220
|
const mcpKey = target.configKey || 'mcpServers';
|
|
123
221
|
const config = readConfig(configPath) || {};
|
|
124
222
|
|
|
@@ -134,12 +232,63 @@ function installTarget(targetKey, opts = {}) {
|
|
|
134
232
|
}
|
|
135
233
|
|
|
136
234
|
const existed = !!servers.vai;
|
|
137
|
-
|
|
235
|
+
|
|
236
|
+
// Use extended entry with metadata for Cursor/VS Code
|
|
237
|
+
if (targetKey === 'cursor' || targetKey.startsWith('vscode')) {
|
|
238
|
+
servers.vai = buildVaiEntryWithMetadata(opts);
|
|
239
|
+
} else {
|
|
240
|
+
servers.vai = buildVaiEntry(opts);
|
|
241
|
+
}
|
|
242
|
+
|
|
138
243
|
// Ensure the nested key points to our updated servers
|
|
139
244
|
setNestedKey(config, mcpKey, servers);
|
|
140
245
|
|
|
141
246
|
writeConfig(configPath, config);
|
|
142
|
-
|
|
247
|
+
|
|
248
|
+
const result = {
|
|
249
|
+
installed: true,
|
|
250
|
+
message: `${target.name}: ${existed ? 'updated' : 'installed'} vai in ${configPath}`,
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
// Add post-install tips if available
|
|
254
|
+
if (target.postInstall) {
|
|
255
|
+
result.tips = target.postInstall();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return result;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Install vai MCP config at workspace level.
|
|
263
|
+
*/
|
|
264
|
+
function installWorkspaceConfig(target, opts) {
|
|
265
|
+
const workspacePath = opts.workspacePath;
|
|
266
|
+
const relativeConfigPath = target.workspaceConfigPath();
|
|
267
|
+
const configPath = path.join(workspacePath, relativeConfigPath);
|
|
268
|
+
|
|
269
|
+
const config = readConfig(configPath) || {};
|
|
270
|
+
const mcpKey = target.configKey || 'mcpServers';
|
|
271
|
+
|
|
272
|
+
let servers = getNestedKey(config, mcpKey);
|
|
273
|
+
if (servers == null || typeof servers !== 'object') {
|
|
274
|
+
servers = {};
|
|
275
|
+
setNestedKey(config, mcpKey, servers);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (servers.vai && !opts.force) {
|
|
279
|
+
return { installed: false, message: `${target.name}: vai already configured in ${configPath} — use --force to overwrite` };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const existed = !!servers.vai;
|
|
283
|
+
servers.vai = buildVaiEntryWithMetadata(opts);
|
|
284
|
+
setNestedKey(config, mcpKey, servers);
|
|
285
|
+
|
|
286
|
+
writeConfig(configPath, config);
|
|
287
|
+
return {
|
|
288
|
+
installed: true,
|
|
289
|
+
message: `${target.name}: ${existed ? 'updated' : 'installed'} vai in ${configPath}`,
|
|
290
|
+
tips: ['Workspace-level config will be used when opening this folder in Cursor'],
|
|
291
|
+
};
|
|
143
292
|
}
|
|
144
293
|
|
|
145
294
|
/**
|
|
@@ -175,7 +324,17 @@ function uninstallTarget(targetKey) {
|
|
|
175
324
|
function statusAll() {
|
|
176
325
|
const results = [];
|
|
177
326
|
for (const [key, target] of Object.entries(TARGETS)) {
|
|
178
|
-
|
|
327
|
+
// Skip workspace-only targets in global status
|
|
328
|
+
if (target.requiresWorkspace) {
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const configPath = target.configPath?.();
|
|
333
|
+
if (!configPath) {
|
|
334
|
+
results.push({ target: key, name: target.name, configPath: 'N/A', status: 'workspace-only' });
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
|
|
179
338
|
const mcpKey = target.configKey || 'mcpServers';
|
|
180
339
|
const config = readConfig(configPath);
|
|
181
340
|
|
|
@@ -192,10 +351,124 @@ function statusAll() {
|
|
|
192
351
|
return results;
|
|
193
352
|
}
|
|
194
353
|
|
|
354
|
+
/**
|
|
355
|
+
* Get detailed information about a target for help/docs.
|
|
356
|
+
*/
|
|
357
|
+
function getTargetInfo(targetKey) {
|
|
358
|
+
const target = TARGETS[targetKey];
|
|
359
|
+
if (!target) return null;
|
|
360
|
+
|
|
361
|
+
return {
|
|
362
|
+
key: targetKey,
|
|
363
|
+
name: target.name,
|
|
364
|
+
configPath: target.configPath?.() || null,
|
|
365
|
+
workspaceConfigPath: target.workspaceConfigPath?.() || null,
|
|
366
|
+
requiresWorkspace: target.requiresWorkspace || false,
|
|
367
|
+
tips: target.postInstall?.() || [],
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Generate a sample .cursor/mcp.json or .vscode/settings.json for documentation.
|
|
373
|
+
*/
|
|
374
|
+
function generateSampleConfig(targetKey, opts = {}) {
|
|
375
|
+
const target = TARGETS[targetKey];
|
|
376
|
+
if (!target) return null;
|
|
377
|
+
|
|
378
|
+
const mcpKey = target.configKey || 'mcpServers';
|
|
379
|
+
const entry = buildVaiEntryWithMetadata(opts);
|
|
380
|
+
|
|
381
|
+
const config = {};
|
|
382
|
+
setNestedKey(config, mcpKey, { vai: entry });
|
|
383
|
+
|
|
384
|
+
return JSON.stringify(config, null, 2);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Verify that vai is accessible in PATH.
|
|
389
|
+
*/
|
|
390
|
+
function verifyVaiInstallation() {
|
|
391
|
+
const { execSync } = require('child_process');
|
|
392
|
+
try {
|
|
393
|
+
const version = execSync('vai --version', { encoding: 'utf8', timeout: 5000 }).trim();
|
|
394
|
+
return { installed: true, version };
|
|
395
|
+
} catch {
|
|
396
|
+
return { installed: false, version: null };
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Diagnose MCP installation issues.
|
|
402
|
+
*/
|
|
403
|
+
function diagnose(targetKey) {
|
|
404
|
+
const results = [];
|
|
405
|
+
|
|
406
|
+
// Check vai installation
|
|
407
|
+
const vaiStatus = verifyVaiInstallation();
|
|
408
|
+
if (!vaiStatus.installed) {
|
|
409
|
+
results.push({ level: 'error', message: 'vai CLI not found in PATH. Run: npm install -g voyageai-cli' });
|
|
410
|
+
} else {
|
|
411
|
+
results.push({ level: 'ok', message: `vai ${vaiStatus.version} installed` });
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Check target config
|
|
415
|
+
const target = TARGETS[targetKey];
|
|
416
|
+
if (!target) {
|
|
417
|
+
results.push({ level: 'error', message: `Unknown target: ${targetKey}` });
|
|
418
|
+
return results;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const configPath = target.configPath?.();
|
|
422
|
+
if (!configPath) {
|
|
423
|
+
results.push({ level: 'warning', message: `${target.name}: No global config path (may require workspace config)` });
|
|
424
|
+
} else if (!fs.existsSync(configPath)) {
|
|
425
|
+
results.push({ level: 'warning', message: `${target.name}: Config file not found at ${configPath}` });
|
|
426
|
+
} else {
|
|
427
|
+
const config = readConfig(configPath);
|
|
428
|
+
const mcpKey = target.configKey || 'mcpServers';
|
|
429
|
+
const servers = getNestedKey(config, mcpKey);
|
|
430
|
+
|
|
431
|
+
if (servers?.vai) {
|
|
432
|
+
results.push({ level: 'ok', message: `${target.name}: vai configured in ${configPath}` });
|
|
433
|
+
|
|
434
|
+
// Validate the entry
|
|
435
|
+
const entry = servers.vai;
|
|
436
|
+
if (entry.command !== 'vai') {
|
|
437
|
+
results.push({ level: 'warning', message: `${target.name}: command should be "vai", found "${entry.command}"` });
|
|
438
|
+
}
|
|
439
|
+
if (!entry.args?.includes('mcp-server')) {
|
|
440
|
+
results.push({ level: 'warning', message: `${target.name}: args should include "mcp-server"` });
|
|
441
|
+
}
|
|
442
|
+
} else {
|
|
443
|
+
results.push({ level: 'warning', message: `${target.name}: vai not configured. Run: vai mcp install ${targetKey}` });
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Check environment variables
|
|
448
|
+
if (process.env.VOYAGE_API_KEY) {
|
|
449
|
+
results.push({ level: 'ok', message: 'VOYAGE_API_KEY environment variable set' });
|
|
450
|
+
} else {
|
|
451
|
+
results.push({ level: 'warning', message: 'VOYAGE_API_KEY not set (required for embeddings)' });
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (process.env.MONGODB_URI) {
|
|
455
|
+
results.push({ level: 'ok', message: 'MONGODB_URI environment variable set' });
|
|
456
|
+
} else {
|
|
457
|
+
results.push({ level: 'warning', message: 'MONGODB_URI not set (required for vector search)' });
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return results;
|
|
461
|
+
}
|
|
462
|
+
|
|
195
463
|
module.exports = {
|
|
196
464
|
TARGETS,
|
|
197
465
|
installTarget,
|
|
198
466
|
uninstallTarget,
|
|
199
467
|
statusAll,
|
|
200
468
|
buildVaiEntry,
|
|
469
|
+
buildVaiEntryWithMetadata,
|
|
470
|
+
getTargetInfo,
|
|
471
|
+
generateSampleConfig,
|
|
472
|
+
verifyVaiInstallation,
|
|
473
|
+
diagnose,
|
|
201
474
|
};
|
package/src/mcp/schemas/index.js
CHANGED
|
@@ -87,6 +87,166 @@ const ingestSchema = {
|
|
|
87
87
|
model: z.string().default('voyage-4-large').describe('Voyage AI embedding model'),
|
|
88
88
|
};
|
|
89
89
|
|
|
90
|
+
/** vai_index_workspace input schema */
|
|
91
|
+
const indexWorkspaceSchema = {
|
|
92
|
+
path: z.string().optional().describe('Workspace directory path. Defaults to current working directory.'),
|
|
93
|
+
db: z.string().optional().describe('MongoDB database name'),
|
|
94
|
+
collection: z.string().optional().describe('Collection to store indexed documents'),
|
|
95
|
+
contentType: z.enum(['code', 'docs', 'config', 'all']).default('code')
|
|
96
|
+
.describe('Type of content to index: code (source files), docs (markdown/text), config (json/yaml), or all'),
|
|
97
|
+
model: z.string().default('voyage-4-large').describe('Voyage AI embedding model'),
|
|
98
|
+
maxFiles: z.number().int().min(1).max(10000).default(1000).describe('Maximum number of files to index'),
|
|
99
|
+
maxFileSize: z.number().int().min(1000).max(1000000).default(100000).describe('Maximum file size in bytes'),
|
|
100
|
+
chunkSize: z.number().int().min(100).max(4000).default(512).describe('Target chunk size in characters'),
|
|
101
|
+
chunkOverlap: z.number().int().min(0).max(500).default(50).describe('Overlap between chunks in characters'),
|
|
102
|
+
batchSize: z.number().int().min(1).max(50).default(10).describe('Number of files to process per batch'),
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/** vai_search_code input schema */
|
|
106
|
+
const searchCodeSchema = {
|
|
107
|
+
query: z.string().min(1).max(5000).describe('Semantic search query for code'),
|
|
108
|
+
db: z.string().optional().describe('MongoDB database name'),
|
|
109
|
+
collection: z.string().optional().describe('Collection with indexed code'),
|
|
110
|
+
limit: z.number().int().min(1).max(50).default(10).describe('Maximum number of results'),
|
|
111
|
+
language: z.string().optional().describe('Filter by programming language (e.g., "js", "py", "go")'),
|
|
112
|
+
category: z.enum(['code', 'docs', 'config']).optional().describe('Filter by content category'),
|
|
113
|
+
model: z.string().optional().describe('Voyage AI embedding model'),
|
|
114
|
+
filter: z.record(z.string(), z.unknown()).optional().describe('Additional MongoDB filter'),
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/** vai_explain_code input schema */
|
|
118
|
+
const explainCodeSchema = {
|
|
119
|
+
code: z.string().min(1).max(10000).describe('Code snippet to explain'),
|
|
120
|
+
language: z.string().optional().describe('Programming language of the code'),
|
|
121
|
+
db: z.string().optional().describe('MongoDB database name'),
|
|
122
|
+
collection: z.string().optional().describe('Collection with indexed documentation'),
|
|
123
|
+
contextLimit: z.number().int().min(1).max(20).default(5).describe('Number of context documents to retrieve'),
|
|
124
|
+
model: z.string().optional().describe('Voyage AI embedding model'),
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/** vai_code_index input schema */
|
|
128
|
+
const codeIndexSchema = {
|
|
129
|
+
source: z.string().min(1).describe(
|
|
130
|
+
'Local directory path or GitHub repo URL (e.g., "/path/to/project" or "https://github.com/org/repo")'
|
|
131
|
+
),
|
|
132
|
+
db: z.string().optional().describe('MongoDB database name. Default: "vai_code_search"'),
|
|
133
|
+
collection: z.string().optional().describe(
|
|
134
|
+
'Collection name. Auto-derived from project name if omitted.'
|
|
135
|
+
),
|
|
136
|
+
model: z.string().optional().describe(
|
|
137
|
+
'Embedding model. Default: auto-detected (voyage-code-3 for code, voyage-4-large for docs)'
|
|
138
|
+
),
|
|
139
|
+
branch: z.string().default('main').describe('Git branch for remote repos'),
|
|
140
|
+
maxFiles: z.number().int().min(1).max(10000).default(5000)
|
|
141
|
+
.describe('Maximum files to index'),
|
|
142
|
+
maxFileSize: z.number().int().min(1000).max(1000000).default(100000)
|
|
143
|
+
.describe('Maximum file size in bytes'),
|
|
144
|
+
chunkSize: z.number().int().min(100).max(4000).default(512)
|
|
145
|
+
.describe('Target chunk size in characters'),
|
|
146
|
+
chunkOverlap: z.number().int().min(0).max(500).default(50)
|
|
147
|
+
.describe('Overlap between chunks in characters'),
|
|
148
|
+
batchSize: z.number().int().min(1).max(50).default(20)
|
|
149
|
+
.describe('Files per embedding batch'),
|
|
150
|
+
refresh: z.boolean().default(false)
|
|
151
|
+
.describe('Incremental refresh: only re-index changed files'),
|
|
152
|
+
contentType: z.enum(['code', 'docs', 'config', 'all']).default('code')
|
|
153
|
+
.describe('Type of content to index'),
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/** vai_code_search input schema */
|
|
157
|
+
const codeSearchSchema = {
|
|
158
|
+
query: z.string().min(1).max(5000).describe(
|
|
159
|
+
'Natural language search query (e.g., "where do we handle auth timeouts")'
|
|
160
|
+
),
|
|
161
|
+
db: z.string().optional().describe('MongoDB database name'),
|
|
162
|
+
collection: z.string().optional().describe('Collection with indexed code'),
|
|
163
|
+
limit: z.number().int().min(1).max(50).default(10)
|
|
164
|
+
.describe('Maximum number of results'),
|
|
165
|
+
language: z.string().optional()
|
|
166
|
+
.describe('Filter by programming language (e.g., "js", "py", "go")'),
|
|
167
|
+
category: z.enum(['code', 'docs', 'config']).optional()
|
|
168
|
+
.describe('Filter by content category'),
|
|
169
|
+
rerank: z.boolean().default(true)
|
|
170
|
+
.describe('Rerank results with Voyage AI reranker for better relevance'),
|
|
171
|
+
rerankModel: z.enum(['rerank-2.5', 'rerank-2.5-lite']).default('rerank-2.5')
|
|
172
|
+
.describe('Reranking model'),
|
|
173
|
+
model: z.string().optional()
|
|
174
|
+
.describe('Embedding model for query. Default: voyage-code-3'),
|
|
175
|
+
filter: z.record(z.string(), z.unknown()).optional()
|
|
176
|
+
.describe('Additional MongoDB filter on metadata fields'),
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
/** vai_code_query input schema */
|
|
180
|
+
const codeQuerySchema = {
|
|
181
|
+
query: z.string().min(1).max(5000).describe(
|
|
182
|
+
'Question about the codebase (e.g., "how does the auth middleware work")'
|
|
183
|
+
),
|
|
184
|
+
db: z.string().optional().describe('MongoDB database name'),
|
|
185
|
+
collection: z.string().optional().describe('Collection with indexed code'),
|
|
186
|
+
limit: z.number().int().min(1).max(20).default(5)
|
|
187
|
+
.describe('Maximum results (fewer, higher quality)'),
|
|
188
|
+
language: z.string().optional()
|
|
189
|
+
.describe('Filter by programming language'),
|
|
190
|
+
model: z.string().optional()
|
|
191
|
+
.describe('Embedding model. Default: voyage-code-3'),
|
|
192
|
+
filter: z.record(z.string(), z.unknown()).optional()
|
|
193
|
+
.describe('Additional MongoDB filter'),
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
/** vai_code_find_similar input schema */
|
|
197
|
+
const codeFindSimilarSchema = {
|
|
198
|
+
code: z.string().min(1).max(10000).describe(
|
|
199
|
+
'Code snippet to find similar implementations for'
|
|
200
|
+
),
|
|
201
|
+
db: z.string().optional().describe('MongoDB database name'),
|
|
202
|
+
collection: z.string().optional().describe('Collection with indexed code'),
|
|
203
|
+
limit: z.number().int().min(1).max(50).default(10)
|
|
204
|
+
.describe('Maximum results'),
|
|
205
|
+
language: z.string().optional()
|
|
206
|
+
.describe('Filter by programming language'),
|
|
207
|
+
model: z.string().optional()
|
|
208
|
+
.describe('Embedding model. Default: voyage-code-3'),
|
|
209
|
+
threshold: z.number().min(0).max(1).default(0.5)
|
|
210
|
+
.describe('Minimum similarity score (0-1)'),
|
|
211
|
+
filter: z.record(z.string(), z.unknown()).optional()
|
|
212
|
+
.describe('Additional MongoDB filter'),
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
/** vai_code_status input schema */
|
|
216
|
+
const codeStatusSchema = {
|
|
217
|
+
db: z.string().optional().describe('MongoDB database name'),
|
|
218
|
+
collection: z.string().optional().describe('Collection to check'),
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
/** vai_generate_workflow input schema */
|
|
222
|
+
const generateWorkflowSchema = {
|
|
223
|
+
description: z.string().min(1).max(500).describe('Natural language description of the workflow to generate'),
|
|
224
|
+
category: z.enum(['retrieval', 'analysis', 'ingestion', 'domain-specific', 'utility', 'integration']).optional()
|
|
225
|
+
.describe('Workflow category'),
|
|
226
|
+
tools: z.array(z.string()).optional()
|
|
227
|
+
.describe('Explicit list of tools to include (e.g., ["query", "rerank", "generate"]). If omitted, tools are inferred from the description.'),
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
/** vai_validate_workflow input schema */
|
|
231
|
+
const validateWorkflowSchema = {
|
|
232
|
+
workflow: z.object({
|
|
233
|
+
name: z.string().optional(),
|
|
234
|
+
description: z.string().optional(),
|
|
235
|
+
version: z.string().optional(),
|
|
236
|
+
inputs: z.record(z.string(), z.unknown()).optional(),
|
|
237
|
+
defaults: z.record(z.string(), z.unknown()).optional(),
|
|
238
|
+
steps: z.array(z.object({
|
|
239
|
+
id: z.string(),
|
|
240
|
+
tool: z.string(),
|
|
241
|
+
name: z.string().optional(),
|
|
242
|
+
inputs: z.record(z.string(), z.unknown()).optional(),
|
|
243
|
+
condition: z.string().optional(),
|
|
244
|
+
forEach: z.string().optional(),
|
|
245
|
+
})),
|
|
246
|
+
output: z.record(z.string(), z.unknown()).optional(),
|
|
247
|
+
}).describe('The workflow JSON definition to validate'),
|
|
248
|
+
};
|
|
249
|
+
|
|
90
250
|
module.exports = {
|
|
91
251
|
querySchema,
|
|
92
252
|
searchSchema,
|
|
@@ -99,4 +259,14 @@ module.exports = {
|
|
|
99
259
|
explainSchema,
|
|
100
260
|
estimateSchema,
|
|
101
261
|
ingestSchema,
|
|
262
|
+
indexWorkspaceSchema,
|
|
263
|
+
searchCodeSchema,
|
|
264
|
+
explainCodeSchema,
|
|
265
|
+
codeIndexSchema,
|
|
266
|
+
codeSearchSchema,
|
|
267
|
+
codeQuerySchema,
|
|
268
|
+
codeFindSimilarSchema,
|
|
269
|
+
codeStatusSchema,
|
|
270
|
+
generateWorkflowSchema,
|
|
271
|
+
validateWorkflowSchema,
|
|
102
272
|
};
|
package/src/mcp/server.js
CHANGED
|
@@ -8,6 +8,9 @@ const { registerEmbeddingTools } = require('./tools/embedding');
|
|
|
8
8
|
const { registerManagementTools } = require('./tools/management');
|
|
9
9
|
const { registerUtilityTools } = require('./tools/utility');
|
|
10
10
|
const { registerIngestTool } = require('./tools/ingest');
|
|
11
|
+
const { registerWorkspaceTools } = require('./tools/workspace');
|
|
12
|
+
const { registerCodeSearchTools } = require('./tools/code-search');
|
|
13
|
+
const { registerAuthoringTools } = require('./tools/authoring');
|
|
11
14
|
|
|
12
15
|
const VERSION = require('../../package.json').version;
|
|
13
16
|
|
|
@@ -27,6 +30,9 @@ function createServer() {
|
|
|
27
30
|
registerManagementTools(server, schemas);
|
|
28
31
|
registerUtilityTools(server, schemas);
|
|
29
32
|
registerIngestTool(server, schemas);
|
|
33
|
+
registerWorkspaceTools(server, schemas);
|
|
34
|
+
registerCodeSearchTools(server, schemas);
|
|
35
|
+
registerAuthoringTools(server, schemas);
|
|
30
36
|
|
|
31
37
|
return server;
|
|
32
38
|
}
|
|
@@ -66,7 +72,7 @@ async function runHttpServer({ port = 3100, host = '127.0.0.1', sse = false } =
|
|
|
66
72
|
const allKeys = envKey ? [...serverKeys, envKey] : serverKeys;
|
|
67
73
|
const requireAuth = allKeys.length > 0;
|
|
68
74
|
|
|
69
|
-
/** Bearer token authentication middleware */
|
|
75
|
+
/** Bearer token authentication middleware (timing-safe comparison) */
|
|
70
76
|
function authenticateRequest(req, res, next) {
|
|
71
77
|
if (!requireAuth) return next();
|
|
72
78
|
const authHeader = req.headers.authorization;
|
|
@@ -74,7 +80,13 @@ async function runHttpServer({ port = 3100, host = '127.0.0.1', sse = false } =
|
|
|
74
80
|
return res.status(401).json({ error: 'Missing or invalid Authorization header' });
|
|
75
81
|
}
|
|
76
82
|
const token = authHeader.slice(7);
|
|
77
|
-
|
|
83
|
+
const tokenBuf = Buffer.from(token);
|
|
84
|
+
const match = allKeys.some(key => {
|
|
85
|
+
const keyBuf = Buffer.from(key);
|
|
86
|
+
if (keyBuf.length !== tokenBuf.length) return false;
|
|
87
|
+
return crypto.timingSafeEqual(keyBuf, tokenBuf);
|
|
88
|
+
});
|
|
89
|
+
if (!match) {
|
|
78
90
|
return res.status(401).json({ error: 'Invalid API key' });
|
|
79
91
|
}
|
|
80
92
|
next();
|
|
@@ -93,7 +105,6 @@ async function runHttpServer({ port = 3100, host = '127.0.0.1', sse = false } =
|
|
|
93
105
|
|
|
94
106
|
// Check Voyage AI connectivity
|
|
95
107
|
try {
|
|
96
|
-
const { getConfigValue } = require('../lib/config');
|
|
97
108
|
const hasKey = !!(process.env.VOYAGE_API_KEY || getConfigValue('apiKey'));
|
|
98
109
|
health.voyageAi = hasKey ? 'configured' : 'not configured';
|
|
99
110
|
} catch {
|
|
@@ -102,7 +113,6 @@ async function runHttpServer({ port = 3100, host = '127.0.0.1', sse = false } =
|
|
|
102
113
|
|
|
103
114
|
// Check MongoDB connectivity
|
|
104
115
|
try {
|
|
105
|
-
const { getConfigValue } = require('../lib/config');
|
|
106
116
|
const hasUri = !!(process.env.MONGODB_URI || getConfigValue('mongodbUri'));
|
|
107
117
|
health.mongodb = hasUri ? 'configured' : 'not configured';
|
|
108
118
|
} catch {
|
|
@@ -173,3 +183,8 @@ function generateKey() {
|
|
|
173
183
|
}
|
|
174
184
|
|
|
175
185
|
module.exports = { createServer, runStdioServer, runHttpServer, generateKey };
|
|
186
|
+
|
|
187
|
+
// Allow direct execution: `node src/mcp/server.js`
|
|
188
|
+
if (require.main === module) {
|
|
189
|
+
runStdioServer();
|
|
190
|
+
}
|