fix(companion): absolute node path for MCP spawn + restrict to mcp tools
claude resolves the MCP server command against the child env (no PATH), so a bare 'node' failed to spawn (status:failed). Use process.execPath. Also pass --tools to drop claude's built-ins (Bash/Read/Write/…) — companion gets only the four mcp__void__* tools. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -58,6 +58,7 @@ import { createInterface } from 'readline';
|
|||||||
* @param {string[]} [opts.allowedTools] Tool names to allow (--allowedTools multi-value)
|
* @param {string[]} [opts.allowedTools] Tool names to allow (--allowedTools multi-value)
|
||||||
* @param {function} [opts.onEvent] Called for each normalized event
|
* @param {function} [opts.onEvent] Called for each normalized event
|
||||||
* @param {string} [opts.claudeExe] Path or name of claude binary (default: CLAUDE_EXE env or 'claude')
|
* @param {string} [opts.claudeExe] Path or name of claude binary (default: CLAUDE_EXE env or 'claude')
|
||||||
|
* @param {string[]} [opts.tools] Exclusive available-tools allowlist (--tools); removes built-ins
|
||||||
* @param {string} [opts.home] If set, overrides HOME in child env (for service-user creds)
|
* @param {string} [opts.home] If set, overrides HOME in child env (for service-user creds)
|
||||||
* @param {string} [opts.cwd] Working directory for the child process
|
* @param {string} [opts.cwd] Working directory for the child process
|
||||||
* @param {number} [opts.timeoutMs] Milliseconds before SIGTERM (default: 600000)
|
* @param {number} [opts.timeoutMs] Milliseconds before SIGTERM (default: 600000)
|
||||||
@@ -71,6 +72,7 @@ export async function runClaudeTurn(opts) {
|
|||||||
userText,
|
userText,
|
||||||
mcpConfigPath,
|
mcpConfigPath,
|
||||||
allowedTools = [],
|
allowedTools = [],
|
||||||
|
tools = [],
|
||||||
onEvent,
|
onEvent,
|
||||||
claudeExe = process.env.CLAUDE_EXE || 'claude',
|
claudeExe = process.env.CLAUDE_EXE || 'claude',
|
||||||
home = process.env.VOID_CLAUDE_HOME,
|
home = process.env.VOID_CLAUDE_HOME,
|
||||||
@@ -94,6 +96,12 @@ export async function runClaudeTurn(opts) {
|
|||||||
args.push('--mcp-config', mcpConfigPath, '--strict-mcp-config');
|
args.push('--mcp-config', mcpConfigPath, '--strict-mcp-config');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tools.length > 0) {
|
||||||
|
// --tools is the EXCLUSIVE availability allowlist: restricts the session to
|
||||||
|
// exactly these tools, removing claude's built-ins (Bash/Read/Write/Grep/…).
|
||||||
|
args.push('--tools', ...tools);
|
||||||
|
}
|
||||||
|
|
||||||
if (allowedTools.length > 0) {
|
if (allowedTools.length > 0) {
|
||||||
// --allowedTools accepts space-separated list as multiple values under one flag
|
// --allowedTools accepts space-separated list as multiple values under one flag
|
||||||
args.push('--allowedTools', ...allowedTools);
|
args.push('--allowedTools', ...allowedTools);
|
||||||
|
|||||||
@@ -75,7 +75,9 @@ spacesScopedRouter.post('/turn',
|
|||||||
const mcpConfig = {
|
const mcpConfig = {
|
||||||
mcpServers: {
|
mcpServers: {
|
||||||
void: {
|
void: {
|
||||||
command: 'node',
|
// Absolute node path: claude resolves `command` against the MCP child's
|
||||||
|
// env (which has no PATH), so a bare 'node' fails to spawn ("status:failed").
|
||||||
|
command: process.execPath,
|
||||||
args: [COMPANION_STDIO_PATH],
|
args: [COMPANION_STDIO_PATH],
|
||||||
env: {
|
env: {
|
||||||
VOID_SPACE_ID: req.params.space_id,
|
VOID_SPACE_ID: req.params.space_id,
|
||||||
@@ -92,6 +94,13 @@ spacesScopedRouter.post('/turn',
|
|||||||
const claudeExe = req.app.locals.claudeExe || process.env.CLAUDE_EXE || 'claude';
|
const claudeExe = req.app.locals.claudeExe || process.env.CLAUDE_EXE || 'claude';
|
||||||
const draftIds = [];
|
const draftIds = [];
|
||||||
|
|
||||||
|
const companionTools = [
|
||||||
|
'mcp__void__search',
|
||||||
|
'mcp__void__read',
|
||||||
|
'mcp__void__context',
|
||||||
|
'mcp__void__propose_change'
|
||||||
|
];
|
||||||
|
|
||||||
let result;
|
let result;
|
||||||
try {
|
try {
|
||||||
result = await runClaudeTurn({
|
result = await runClaudeTurn({
|
||||||
@@ -99,12 +108,10 @@ spacesScopedRouter.post('/turn',
|
|||||||
systemPrompt: SYSTEM,
|
systemPrompt: SYSTEM,
|
||||||
userText: text,
|
userText: text,
|
||||||
mcpConfigPath,
|
mcpConfigPath,
|
||||||
allowedTools: [
|
// `tools` restricts the session to ONLY our tools (no built-in Bash/Read/…);
|
||||||
'mcp__void__search',
|
// `allowedTools` auto-approves them in non-interactive (--print) mode.
|
||||||
'mcp__void__read',
|
tools: companionTools,
|
||||||
'mcp__void__context',
|
allowedTools: companionTools,
|
||||||
'mcp__void__propose_change'
|
|
||||||
],
|
|
||||||
claudeExe,
|
claudeExe,
|
||||||
home: process.env.VOID_CLAUDE_HOME || undefined,
|
home: process.env.VOID_CLAUDE_HOME || undefined,
|
||||||
onEvent: (e) => {
|
onEvent: (e) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user