vectorforce 0.3.2 → 0.3.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.
@@ -1,6 +1,34 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
1
4
  import { ErrorCode, VectorForceError } from '../core/errors.js';
2
5
 
3
- export const MCP_PROTOCOL_VERSION = '2024-11-05';
6
+ export const MCP_PROTOCOL_VERSION = '2025-11-25';
7
+
8
+ export const MCP_SUPPORTED_PROTOCOL_VERSIONS = [
9
+ '2025-11-25',
10
+ '2025-06-18',
11
+ '2025-03-26',
12
+ '2024-11-05'
13
+ ];
14
+
15
+ function readServerVersion() {
16
+ try {
17
+ const pkgPath = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'package.json');
18
+ return JSON.parse(readFileSync(pkgPath, 'utf8')).version || '0.3.2';
19
+ } catch {
20
+ return '0.3.2';
21
+ }
22
+ }
23
+
24
+ export const MCP_SERVER_VERSION = readServerVersion();
25
+
26
+ export function negotiateProtocolVersion(clientVersion) {
27
+ if (clientVersion && MCP_SUPPORTED_PROTOCOL_VERSIONS.includes(clientVersion)) {
28
+ return clientVersion;
29
+ }
30
+ return MCP_PROTOCOL_VERSION;
31
+ }
4
32
 
5
33
  export const MCP_TOOLS = [
6
34
  {
@@ -44,7 +72,7 @@ export const MCP_TOOLS = [
44
72
  task_id: { type: 'string' },
45
73
  checkpoint_id: { type: 'string', description: 'Optional specific checkpoint ID. Defaults to latest head.' },
46
74
  max_tokens: { type: 'integer', default: 4000, maximum: 8000 },
47
- destination_harness: { type: 'string', enum: ['antigravity', 'command-code', 'muse', 'muse-code', 'pi', 'codex', 'cli'] }
75
+ destination_harness: { type: 'string', enum: ['antigravity', 'command-code', 'muse', 'muse-code', 'pi', 'codex', 'cli', 'cursor', 'windsurf', 'github-copilot', 'copilot', 'kiro', 'kiro-ide'] }
48
76
  }
49
77
  }
50
78
  },
@@ -91,7 +119,7 @@ export const MCP_TOOLS = [
91
119
  task_id: { type: 'string', description: 'Kebab-case task slug' },
92
120
  recipient: { type: 'string', description: 'Target developer or audience identity' },
93
121
  checkpoint_id: { type: 'string', description: 'Optional specific checkpoint ID to export' },
94
- destination_harness: { type: 'string', enum: ['antigravity', 'command-code', 'muse', 'muse-code', 'pi', 'codex', 'cli', 'any'], default: 'any' },
122
+ destination_harness: { type: 'string', enum: ['antigravity', 'command-code', 'muse', 'muse-code', 'pi', 'codex', 'cli', 'cursor', 'windsurf', 'github-copilot', 'copilot', 'kiro', 'kiro-ide', 'any'], default: 'any' },
95
123
  preview: { type: 'boolean', default: false, description: 'Dry run preview without writing file' },
96
124
  out_path: { type: 'string', description: 'Optional custom bundle file destination' }
97
125
  }
@@ -117,7 +145,7 @@ export const MCP_TOOLS = [
117
145
  properties: {
118
146
  days: { type: 'integer', default: 7, description: 'Number of past days to include in the recap (defaults to 7)' },
119
147
  task_id: { type: 'string', description: 'Optional specific task ID to filter by' },
120
- destination_harness: { type: 'string', enum: ['antigravity', 'command-code', 'muse', 'muse-code', 'pi', 'codex', 'cli'] }
148
+ destination_harness: { type: 'string', enum: ['antigravity', 'command-code', 'muse', 'muse-code', 'pi', 'codex', 'cli', 'cursor', 'windsurf', 'github-copilot', 'copilot', 'kiro', 'kiro-ide'] }
121
149
  }
122
150
  }
123
151
  },
@@ -130,7 +158,7 @@ export const MCP_TOOLS = [
130
158
  properties: {
131
159
  task_id: { type: 'string', description: 'Kebab-case task slug' },
132
160
  checkpoint_id: { type: 'string', description: 'Optional specific checkpoint ID. Defaults to latest head.' },
133
- destination_harness: { type: 'string', enum: ['antigravity', 'command-code', 'muse', 'muse-code', 'pi', 'codex', 'cli'] },
161
+ destination_harness: { type: 'string', enum: ['antigravity', 'command-code', 'muse', 'muse-code', 'pi', 'codex', 'cli', 'cursor', 'windsurf', 'github-copilot', 'copilot', 'kiro', 'kiro-ide'] },
134
162
  model: { type: 'string', description: 'Optional model override (e.g. a Meta Llama or Muse model id)' },
135
163
  focus: { type: 'string', description: 'Optional question or focus for the reasoning pass' },
136
164
  include_code: { type: 'boolean', default: true, description: 'Ground reasoning in the live codebase index' }
@@ -214,14 +242,31 @@ export class McpServer {
214
242
  return;
215
243
  }
216
244
 
245
+ // JSON-RPC batch: dispatch each entry; entries without an id are notifications.
246
+ if (Array.isArray(req)) {
247
+ if (req.length === 0) {
248
+ this._sendError(null, -32600, 'Invalid Request: empty batch');
249
+ return;
250
+ }
251
+ for (const sub of req) {
252
+ this._dispatch(sub);
253
+ }
254
+ return;
255
+ }
256
+
257
+ this._dispatch(req);
258
+ }
259
+
260
+ _dispatch(req) {
261
+ if (!req || typeof req !== 'object' || Array.isArray(req)) {
262
+ this._sendError(null, -32600, 'Invalid Request');
263
+ return;
264
+ }
265
+
217
266
  const { id, method, params } = req;
218
267
 
219
- // Notifications (no id)
268
+ // Notifications (no id) are acknowledged silently, never answered.
220
269
  if (id === undefined || id === null) {
221
- if (method === 'notifications/initialized') {
222
- // Client handshake complete
223
- return;
224
- }
225
270
  return;
226
271
  }
227
272
 
@@ -229,15 +274,17 @@ export class McpServer {
229
274
  switch (method) {
230
275
  case 'initialize': {
231
276
  this._sendResult(id, {
232
- protocolVersion: MCP_PROTOCOL_VERSION,
277
+ protocolVersion: negotiateProtocolVersion(params?.protocolVersion),
233
278
  serverInfo: {
234
279
  name: 'vectorforce',
235
- version: '0.1.0'
280
+ version: MCP_SERVER_VERSION
236
281
  },
237
282
  capabilities: {
238
283
  tools: {
239
284
  listChanged: false
240
- }
285
+ },
286
+ resources: {},
287
+ prompts: {}
241
288
  }
242
289
  });
243
290
  break;
@@ -255,6 +302,31 @@ export class McpServer {
255
302
  break;
256
303
  }
257
304
 
305
+ case 'resources/list': {
306
+ this._sendResult(id, { resources: [] });
307
+ break;
308
+ }
309
+
310
+ case 'resources/templates/list': {
311
+ this._sendResult(id, { resourceTemplates: [] });
312
+ break;
313
+ }
314
+
315
+ case 'resources/read': {
316
+ this._sendError(id, -32602, `Unknown resource: ${params?.uri || '(missing uri)'}`);
317
+ break;
318
+ }
319
+
320
+ case 'prompts/list': {
321
+ this._sendResult(id, { prompts: [] });
322
+ break;
323
+ }
324
+
325
+ case 'prompts/get': {
326
+ this._sendError(id, -32602, `Unknown prompt: ${params?.name || '(missing name)'}`);
327
+ break;
328
+ }
329
+
258
330
  case 'tools/call': {
259
331
  this._handleToolCall(id, params);
260
332
  break;
@@ -1,6 +1,7 @@
1
1
  import { Authorizer } from '../policy/authorizer.js';
2
2
  import { sha256 } from '../core/hash.js';
3
3
  import { basename, dirname } from 'node:path';
4
+ import { execFileSync } from 'node:child_process';
4
5
  import { ErrorCode, VectorForceError } from '../core/errors.js';
5
6
 
6
7
  export class ProjectService {
@@ -13,6 +14,8 @@ export class ProjectService {
13
14
 
14
15
  /**
15
16
  * Initialize or retrieve a project for a workspace directory.
17
+ * Derives a deterministic project identity from Git remote URL if present,
18
+ * ensuring that multiple developers cloning the same repo share the identical project ID.
16
19
  * @param {string} workspacePath
17
20
  * @param {string} [customName]
18
21
  * @returns {{ project_id: string, canonical_path: string, name: string, created_at: string }}
@@ -23,7 +26,21 @@ export class ProjectService {
23
26
  if (existing) {
24
27
  return existing;
25
28
  }
26
- const idHash = sha256(canonical).slice(0, 16);
29
+
30
+ let identitySeed = canonical;
31
+ try {
32
+ const gitRemote = execFileSync('git', ['config', '--get', 'remote.origin.url'], {
33
+ cwd: canonical,
34
+ encoding: 'utf8',
35
+ stdio: ['ignore', 'pipe', 'ignore'],
36
+ timeout: 1000
37
+ }).trim();
38
+ if (gitRemote) {
39
+ identitySeed = gitRemote.toLowerCase();
40
+ }
41
+ } catch {}
42
+
43
+ const idHash = sha256(identitySeed).slice(0, 16);
27
44
  const projectId = `proj-${idHash}`;
28
45
  const name = customName || basename(canonical);
29
46
  return this.store.registerProject(projectId, canonical, name);
@@ -5,7 +5,21 @@ import { ErrorCode, VectorForceError } from '../core/errors.js';
5
5
  export const MAX_FILE_BYTES = 8192; // 8 KiB per file
6
6
  export const MAX_TOTAL_DOCUMENT_BYTES = 65536; // 64 KiB total
7
7
  export const ALLOWED_EXTENSIONS = ['.md', '.txt'];
8
- export const APPROVED_DESTINATIONS = ['antigravity', 'command-code', 'muse', 'muse-code', 'pi', 'codex', 'cli'];
8
+ export const APPROVED_DESTINATIONS = [
9
+ 'antigravity',
10
+ 'command-code',
11
+ 'muse',
12
+ 'muse-code',
13
+ 'pi',
14
+ 'codex',
15
+ 'cli',
16
+ 'cursor',
17
+ 'windsurf',
18
+ 'github-copilot',
19
+ 'copilot',
20
+ 'kiro',
21
+ 'kiro-ide'
22
+ ];
9
23
 
10
24
  export class Authorizer {
11
25
  /**