@sdsrs/code-graph 0.18.1 → 0.18.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.
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
/**
|
|
4
|
+
* Tests for claude-plugin/scripts/mcp-launcher.js — the .mcp.json entry point
|
|
5
|
+
* that resolves the binary (with auto-install fallbacks) and stdio-forwards
|
|
6
|
+
* MCP JSON-RPC. install-e2e.test.js §4.3 covers find-binary in dev mode but
|
|
7
|
+
* doesn't exercise the launcher's full chain (find → spawn → forward).
|
|
8
|
+
*
|
|
9
|
+
* The negative paths (no binary anywhere → npm install + GitHub fallback +
|
|
10
|
+
* exit 1) are intentionally NOT covered here — the network-bound fallbacks
|
|
11
|
+
* have ~150s timeouts and aren't deterministic in CI sandboxes. End-to-end
|
|
12
|
+
* dev-mode coverage is the highest-leverage gap.
|
|
13
|
+
*
|
|
14
|
+
* Run: node --test claude-plugin/scripts/mcp-launcher.test.js
|
|
15
|
+
*/
|
|
16
|
+
const test = require('node:test');
|
|
17
|
+
const assert = require('node:assert/strict');
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
const { spawn } = require('child_process');
|
|
21
|
+
|
|
22
|
+
const PLUGIN_ROOT = path.resolve(__dirname, '..');
|
|
23
|
+
const REPO_ROOT = path.resolve(PLUGIN_ROOT, '..');
|
|
24
|
+
const LAUNCHER = path.join(__dirname, 'mcp-launcher.js');
|
|
25
|
+
const BINARY_NAME = process.platform === 'win32' ? 'code-graph-mcp.exe' : 'code-graph-mcp';
|
|
26
|
+
const REL_BINARY = path.join(REPO_ROOT, 'target', 'release', BINARY_NAME);
|
|
27
|
+
|
|
28
|
+
function hasBuiltBinary() {
|
|
29
|
+
return fs.existsSync(REL_BINARY);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Run the launcher, send one MCP message on stdin, collect stdout/stderr,
|
|
34
|
+
* resolve once we either see a JSON-RPC response on stdout or hit timeout.
|
|
35
|
+
*/
|
|
36
|
+
function runLauncherInitialize(timeoutMs = 15000) {
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
const child = spawn(process.execPath, [LAUNCHER], {
|
|
39
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
40
|
+
env: { ...process.env },
|
|
41
|
+
cwd: REPO_ROOT,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
let stdout = '';
|
|
45
|
+
let stderr = '';
|
|
46
|
+
const timer = setTimeout(() => {
|
|
47
|
+
child.kill('SIGTERM');
|
|
48
|
+
reject(new Error(`launcher timed out after ${timeoutMs}ms; stdout=${stdout.slice(0, 400)} stderr=${stderr.slice(0, 400)}`));
|
|
49
|
+
}, timeoutMs);
|
|
50
|
+
|
|
51
|
+
child.stdout.on('data', (d) => {
|
|
52
|
+
stdout += d.toString();
|
|
53
|
+
if (stdout.includes('"result"') || stdout.includes('"error"')) {
|
|
54
|
+
clearTimeout(timer);
|
|
55
|
+
child.kill('SIGTERM');
|
|
56
|
+
// Wait for the child to actually exit so the test doesn't leave an
|
|
57
|
+
// orphan mid-write (matters on macOS / Windows where SIGTERM
|
|
58
|
+
// delivery is less synchronous than on Linux).
|
|
59
|
+
child.once('exit', () => resolve({ stdout, stderr }));
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
child.stderr.on('data', (d) => { stderr += d.toString(); });
|
|
63
|
+
child.on('error', (err) => { clearTimeout(timer); reject(err); });
|
|
64
|
+
|
|
65
|
+
const initMsg = JSON.stringify({
|
|
66
|
+
jsonrpc: '2.0', id: 1, method: 'initialize',
|
|
67
|
+
params: {
|
|
68
|
+
protocolVersion: '2024-11-05',
|
|
69
|
+
capabilities: {},
|
|
70
|
+
clientInfo: { name: 'launcher-test', version: '1.0.0' },
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
child.stdin.write(initMsg + '\n');
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
test('mcp-launcher resolves dev binary and forwards MCP JSON-RPC stdin/stdout', async (t) => {
|
|
78
|
+
if (!hasBuiltBinary()) {
|
|
79
|
+
t.skip(`release binary missing at ${REL_BINARY} — run \`cargo build --release\` first`);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const { stdout, stderr } = await runLauncherInitialize();
|
|
84
|
+
|
|
85
|
+
// Find the JSON-RPC line in the bytes the launcher forwarded from the binary.
|
|
86
|
+
// Stderr may contain "[code-graph] ..." breadcrumbs from the launcher; those
|
|
87
|
+
// are diagnostic and shouldn't break the contract that stdout carries protocol.
|
|
88
|
+
const respLine = stdout.trim().split('\n').find((l) => l.includes('"result"'));
|
|
89
|
+
assert.ok(respLine,
|
|
90
|
+
`expected a JSON-RPC result line on launcher stdout, got: ${stdout.slice(0, 400)} | stderr: ${stderr.slice(0, 400)}`);
|
|
91
|
+
const resp = JSON.parse(respLine);
|
|
92
|
+
assert.equal(resp.jsonrpc, '2.0');
|
|
93
|
+
assert.equal(resp.id, 1);
|
|
94
|
+
assert.ok(resp.result.serverInfo, 'response must carry serverInfo from the binary');
|
|
95
|
+
assert.equal(resp.result.serverInfo.name, 'code-graph-mcp');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('mcp-launcher sets _FIND_BINARY_ROOT from __dirname (does not trust CLAUDE_PLUGIN_ROOT)', () => {
|
|
99
|
+
// Static check: the source must derive _FIND_BINARY_ROOT from __dirname so a
|
|
100
|
+
// sibling plugin's CLAUDE_PLUGIN_ROOT can't redirect us to the wrong binary.
|
|
101
|
+
// Memory: feedback_plugin_env_isolation.md.
|
|
102
|
+
const src = fs.readFileSync(LAUNCHER, 'utf8');
|
|
103
|
+
assert.match(src, /_FIND_BINARY_ROOT\s*=\s*path\.resolve\(__dirname/,
|
|
104
|
+
'launcher must derive _FIND_BINARY_ROOT from __dirname, not CLAUDE_PLUGIN_ROOT');
|
|
105
|
+
// And must NOT read CLAUDE_PLUGIN_ROOT from env.
|
|
106
|
+
assert.doesNotMatch(src, /process\.env\.CLAUDE_PLUGIN_ROOT/,
|
|
107
|
+
'launcher must not trust CLAUDE_PLUGIN_ROOT — it can leak from sibling plugins');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test('mcp-launcher rejects executable-permission failure with platform-specific hint', () => {
|
|
111
|
+
// Static check: the macOS quarantine guard must surface xattr/chmod fix
|
|
112
|
+
// commands rather than silently failing on the spawn.
|
|
113
|
+
const src = fs.readFileSync(LAUNCHER, 'utf8');
|
|
114
|
+
assert.match(src, /accessSync\s*\(\s*binary\s*,\s*fs\.constants\.X_OK\s*\)/,
|
|
115
|
+
'launcher must pre-check binary X_OK before spawn');
|
|
116
|
+
assert.match(src, /xattr -d com\.apple\.quarantine/,
|
|
117
|
+
'macOS guard must surface the xattr removal command in stderr');
|
|
118
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdsrs/code-graph",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.3",
|
|
4
4
|
"description": "MCP server that indexes codebases into an AST knowledge graph with semantic search, call graph traversal, and HTTP route tracing",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -35,10 +35,10 @@
|
|
|
35
35
|
"node": ">=16"
|
|
36
36
|
},
|
|
37
37
|
"optionalDependencies": {
|
|
38
|
-
"@sdsrs/code-graph-linux-x64": "0.18.
|
|
39
|
-
"@sdsrs/code-graph-linux-arm64": "0.18.
|
|
40
|
-
"@sdsrs/code-graph-darwin-x64": "0.18.
|
|
41
|
-
"@sdsrs/code-graph-darwin-arm64": "0.18.
|
|
42
|
-
"@sdsrs/code-graph-win32-x64": "0.18.
|
|
38
|
+
"@sdsrs/code-graph-linux-x64": "0.18.3",
|
|
39
|
+
"@sdsrs/code-graph-linux-arm64": "0.18.3",
|
|
40
|
+
"@sdsrs/code-graph-darwin-x64": "0.18.3",
|
|
41
|
+
"@sdsrs/code-graph-darwin-arm64": "0.18.3",
|
|
42
|
+
"@sdsrs/code-graph-win32-x64": "0.18.3"
|
|
43
43
|
}
|
|
44
44
|
}
|