Claude / scripts /mcp-tmp-guard.js
Cnass's picture
Agent Web 2.0: token tasarrufu, kalici MCP kurulumu, guvenlik, yan menu araclari, 3B avatar ve ses
765fe0a verified
Raw History Blame Contribute Delete
3.88 kB
#!/usr/bin/env node
// Claude Code PreToolUse hook for the Bash tool (see scripts/mcp-tmp-guard):
// refuses `claude mcp add ...` when the MCP server would run from /tmp, which
// is wiped every time the Space restarts, and tells Claude where to put it.
'use strict';
const path = require('path');
const { tempPathIn } = require(path.join(__dirname, 'temp-paths.js'));
const MCP_ADD = /\bmcp\s+add(?:-json)?\b/;
const CD = /^\s*(?:builtin\s+)?(?:cd|pushd)\s+["']?([^\s"']+)/;
const TEMP_DIR = /^(?:\/var)?\/tmp(?:\/|$)|^\/dev\/shm(?:\/|$)/;
const PWD = /\$\(pwd\)|`pwd`|\$\{?PWD\b/;
// Only the `mcp add` part of a command line counts (`mv /tmp/x ~/mcp-servers/x
// && claude mcp add ...` is the fix, not the problem), plus a `cd` into a temp
// folder that the add part then refers to through $(pwd) / $PWD.
function tempPathAdded(command) {
let cwd = '';
for (const part of command.split(/&&|\|\||[;|\n]/)) {
const cd = CD.exec(part);
if (cd) cwd = TEMP_DIR.test(cd[1]) ? cd[1] : '';
if (!MCP_ADD.test(part)) continue;
const found = tempPathIn(part) || (cwd && PWD.test(part) ? cwd : '');
if (found) return found;
}
return '';
}
// `claude mcp add ... -- node ~/mcp-servers/x/...` runs straight from the
// Storage Bucket, whose node_modules / venv links are gone after a restart.
const BUCKET_SERVER = /(?:~|\$HOME|\$\{HOME\}|\/data\/home|\/home\/node)\/mcp-servers\/([A-Za-z0-9._-]+)/;
function bucketServerAdded(command) {
for (const part of command.split(/&&|\|\||[;|\n]/)) {
if (!MCP_ADD.test(part) || /agentweb-mcp-run/.test(part)) continue;
const m = BUCKET_SERVER.exec(part);
if (m) return m[1];
}
return '';
}
function decide(event, home) {
if (!event || event.tool_name !== 'Bash') return null;
const command = event.tool_input && typeof event.tool_input.command === 'string' ? event.tool_input.command : '';
if (!MCP_ADD.test(command)) return null;
const bucket = bucketServerAdded(command);
if (bucket) {
return {
hookSpecificOutput: {
hookEventName: 'PreToolUse',
permissionDecision: 'deny',
permissionDecisionReason:
`Agent Web stopped this command: it registers an MCP server that runs straight from ~/mcp-servers/${bucket} on the Storage Bucket, ` +
'which loses symlinks and execute bits (node_modules/.bin, venv/bin/python) at every restart, so the server would break. ' +
`Use: agentweb-install mcp <name> --dir ~/mcp-servers/${bucket} [--run "<command, e.g. node {build}/dist/index.js>"] [--env K=V] ` +
'(it builds the runnable copy on local disk at every start and registers `agentweb-mcp-run <name>`). See `agentweb-install help`.',
},
};
}
const found = tempPathAdded(command);
if (!found) return null;
const target = `${home ? String(home).replace(/\/+$/, '') : '~'}/mcp-servers/<name>`;
return {
hookSpecificOutput: {
hookEventName: 'PreToolUse',
permissionDecision: 'deny',
permissionDecisionReason:
`Agent Web stopped this command: it registers an MCP server that uses ${found}. ` +
'/tmp is wiped every time this Space restarts, so the server would stop working. ' +
`Install it with \`agentweb-install mcp <name> --git <url>\` (or --dir <folder>) instead: the source goes to ${target} ` +
'and the runnable copy is rebuilt on local disk at every start. See `agentweb-install help`.',
},
};
}
if (require.main === module) {
let raw = '';
process.stdin.setEncoding('utf8');
process.stdin.on('data', (chunk) => {
raw += chunk;
});
process.stdin.on('end', () => {
let event;
try {
event = JSON.parse(raw);
} catch {
return;
}
const answer = decide(event, process.env.HOME);
if (answer) process.stdout.write(JSON.stringify(answer));
});
}
module.exports = { decide, tempPathAdded };