archgraph-argo 0.4.2 → 0.5.1

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.
@@ -16,6 +16,8 @@ const {
16
16
  } = require('./repositoryArgoEnvironment.js');
17
17
  const {
18
18
  getWorkspaceRoot,
19
+ hasStaticWorkspace,
20
+ setMcpWorkspaceRoots,
19
21
  } = require('./argo-paths.js');
20
22
  const canonicalSemanticInitStorage = new AsyncLocalStorage();
21
23
 
@@ -503,6 +505,59 @@ function send(message) {
503
505
  process.stdout.write(`${JSON.stringify(message)}\n`);
504
506
  }
505
507
 
508
+ let rootRequestSeq = 0;
509
+ let rootRequestInFlight = false;
510
+ let rootRequestTimer = null;
511
+ let rootsSettled = false;
512
+ const pendingRootRequests = new Map();
513
+ const deferredToolCalls = [];
514
+
515
+ function flushDeferredToolCalls() {
516
+ const pending = deferredToolCalls.splice(0);
517
+ for (const request of pending) {
518
+ void (async () => {
519
+ const response = await handleRequest(request);
520
+ if (response) {
521
+ send(response);
522
+ }
523
+ })();
524
+ }
525
+ }
526
+
527
+ function settleRoots() {
528
+ if (rootsSettled) {
529
+ return;
530
+ }
531
+ rootsSettled = true;
532
+ if (rootRequestTimer) {
533
+ clearTimeout(rootRequestTimer);
534
+ rootRequestTimer = null;
535
+ }
536
+ rootRequestInFlight = false;
537
+ pendingRootRequests.clear();
538
+ flushDeferredToolCalls();
539
+ }
540
+
541
+ function requestRoots() {
542
+ if (rootRequestInFlight || rootsSettled) {
543
+ return;
544
+ }
545
+ rootRequestInFlight = true;
546
+ const id = `argo-roots-${++rootRequestSeq}`;
547
+ rootRequestTimer = setTimeout(() => {
548
+ rootRequestTimer = null;
549
+ rootRequestInFlight = false;
550
+ settleRoots();
551
+ }, 8000);
552
+ pendingRootRequests.set(id, (result) => {
553
+ if (result && Array.isArray(result.roots)) {
554
+ setMcpWorkspaceRoots(result.roots);
555
+ }
556
+ settleRoots();
557
+ });
558
+ send({ jsonrpc: '2.0', id, method: 'roots/list', params: {} });
559
+ }
560
+
506
561
  async function handleRequest(request, dependencies = undefined) {
507
562
  const { id, method, params } = request;
508
563
 
@@ -512,7 +567,7 @@ async function handleRequest(request, dependencies = undefined) {
512
567
  id,
513
568
  result: {
514
569
  protocolVersion: '2024-11-05',
515
- capabilities: { tools: {} },
570
+ capabilities: { tools: {}, roots: { listChanged: true } },
516
571
  serverInfo: {
517
572
  name: 'argo',
518
573
  version: '1.0.0',
@@ -525,6 +580,14 @@ async function handleRequest(request, dependencies = undefined) {
525
580
  return null;
526
581
  }
527
582
 
583
+ if (method === 'notifications/roots/list_changed') {
584
+ setMcpWorkspaceRoots(params && params.roots);
585
+ if (params && Array.isArray(params.roots) && params.roots.length > 0) {
586
+ settleRoots();
587
+ }
588
+ return null;
589
+ }
590
+
528
591
  if (method === 'tools/list') {
529
592
  // Deduplicate tools by name — last writer wins.
530
593
  // Priority order: validatorMcp > systemArchitectureMcp > local TOOLS.
@@ -606,10 +669,32 @@ async function main() {
606
669
  } catch {
607
670
  continue;
608
671
  }
672
+
673
+ // A JSON-RPC message without a `method` is a response to a server-sent request.
674
+ if (typeof request.method === 'undefined' && request.id !== undefined) {
675
+ const resolver = pendingRootRequests.get(request.id);
676
+ if (resolver) {
677
+ pendingRootRequests.delete(request.id);
678
+ resolver(request.result);
679
+ }
680
+ continue;
681
+ }
682
+
683
+ // Workspace-dependent tool calls must wait for roots so the global server
684
+ // targets the current workspace instead of the home directory.
685
+ if (request.method === 'tools/call' && !hasStaticWorkspace() && !rootsSettled) {
686
+ deferredToolCalls.push(request);
687
+ continue;
688
+ }
689
+
609
690
  const response = await handleRequest(request);
610
691
  if (response) {
611
692
  send(response);
612
693
  }
694
+ if ((request.method === 'initialize' || request.method === 'notifications/initialized')
695
+ && !hasStaticWorkspace()) {
696
+ requestRoots();
697
+ }
613
698
  }
614
699
  }
615
700
 
@@ -1,5 +1,6 @@
1
1
  const fs = require('node:fs');
2
2
  const path = require('node:path');
3
+ const { fileURLToPath } = require('node:url');
3
4
 
4
5
  /**
5
6
  * Shared path resolution for the Argo toolchain.
@@ -17,10 +18,12 @@ const path = require('node:path');
17
18
  * The workspace root is resolved from, in priority order:
18
19
  * 1. ARGO_REPO_ROOT — explicit override (set by MCP client configs).
19
20
  * 2. WORKSPACE_FOLDER — set by some hosts.
20
- * 3. The embedded repo root, when the Argo installation is still inside a repo
21
+ * 3. The MCP `roots` advertised by the client (a global server resolves the
22
+ * root that contains design/KG/SystemArchitecture.json when several roots
23
+ * are present, so it never operates on an unrelated folder).
24
+ * 4. The embedded repo root, when the Argo installation is still inside a repo
21
25
  * (i.e. `<argoRoot>/../design/KG/SystemArchitecture.json` exists).
22
- * 4. process.cwd() — the MCP client must launch the server with its cwd
23
- * set to the target workspace root.
26
+ * 5. process.cwd() — last-resort fallback when no root is available.
24
27
  */
25
28
 
26
29
  function getArgoRoot() {
@@ -28,20 +31,71 @@ function getArgoRoot() {
28
31
  return path.resolve(__dirname, '..');
29
32
  }
30
33
 
34
+ let mcpWorkspaceRoots = [];
35
+
36
+ function setMcpWorkspaceRoots(roots) {
37
+ mcpWorkspaceRoots = Array.isArray(roots) ? roots : [];
38
+ }
39
+
40
+ function rootToPath(root) {
41
+ if (!root) {
42
+ return null;
43
+ }
44
+ const uri = typeof root === 'string' ? root : root.uri;
45
+ if (typeof uri !== 'string' || uri.trim() === '') {
46
+ return null;
47
+ }
48
+ if (uri.startsWith('file://')) {
49
+ try {
50
+ return fileURLToPath(uri);
51
+ } catch {
52
+ return null;
53
+ }
54
+ }
55
+ return uri;
56
+ }
57
+
31
58
  function getWorkspaceRoot() {
32
59
  const explicit = process.env.ARGO_REPO_ROOT || process.env.WORKSPACE_FOLDER;
33
60
  if (explicit && String(explicit).trim() !== '') {
34
61
  return path.resolve(explicit);
35
62
  }
36
63
 
64
+ // The client may expose several roots (a multi-root workspace). Never
65
+ // silently operate on an unrelated folder: prefer the root that actually
66
+ // contains the ArchGraph marker before falling back to the first root.
67
+ const marker = path.join('design', 'KG', 'SystemArchitecture.json');
68
+ for (const root of mcpWorkspaceRoots) {
69
+ const rootPath = rootToPath(root);
70
+ if (rootPath && fs.existsSync(path.join(rootPath, marker))) {
71
+ return path.resolve(rootPath);
72
+ }
73
+ }
74
+
75
+ for (const root of mcpWorkspaceRoots) {
76
+ const rootPath = rootToPath(root);
77
+ if (rootPath) {
78
+ return path.resolve(rootPath);
79
+ }
80
+ }
81
+
37
82
  const embedded = path.resolve(getArgoRoot(), '..');
38
- if (fs.existsSync(path.join(embedded, 'design', 'KG', 'SystemArchitecture.json'))) {
83
+ if (fs.existsSync(path.join(embedded, marker))) {
39
84
  return embedded;
40
85
  }
41
86
 
42
87
  return path.resolve(process.cwd());
43
88
  }
44
89
 
90
+ function hasStaticWorkspace() {
91
+ const explicit = process.env.ARGO_REPO_ROOT || process.env.WORKSPACE_FOLDER;
92
+ if (explicit && String(explicit).trim() !== '') {
93
+ return true;
94
+ }
95
+ const embedded = path.resolve(getArgoRoot(), '..');
96
+ return fs.existsSync(path.join(embedded, 'design', 'KG', 'SystemArchitecture.json'));
97
+ }
98
+
45
99
  function resolveArgoPath(...segments) {
46
100
  return path.resolve(getArgoRoot(), ...segments);
47
101
  }
@@ -71,7 +125,9 @@ module.exports = {
71
125
  getArgoRoot,
72
126
  getArgoEnvPath,
73
127
  getWorkspaceRoot,
128
+ hasStaticWorkspace,
74
129
  normalizeRelativePath,
75
130
  resolveArgoPath,
76
131
  resolveWorkspacePath,
132
+ setMcpWorkspaceRoots,
77
133
  };
package/install-argo.ps1 CHANGED
@@ -135,8 +135,6 @@ if ($SkipMcp) {
135
135
  type = 'stdio'
136
136
  command = 'node'
137
137
  args = @($argoServer)
138
- cwd = '${workspaceFolder}'
139
- env = [ordered]@{ ARGO_REPO_ROOT = '${workspaceFolder}' }
140
138
  }
141
139
 
142
140
  $config = [ordered]@{ servers = $servers }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "archgraph-argo",
3
- "version": "0.4.2",
3
+ "version": "0.5.1",
4
4
  "description": "Deploy the ArchGraph ARGO toolchain, skills, and rules (schema, scripts, argo-init skill, global rule) with one command.",
5
5
  "license": "MIT",
6
6
  "bin": {