(id, params)
| 2882 | } |
| 2883 | |
| 2884 | async function handleMCPToolCall(id, params) { |
| 2885 | const { name, arguments: args } = params; |
| 2886 | const { safeResolvePath, escapeShellArg, sanitizeToolOutput } = require('../src/security/sanitize'); |
| 2887 | const cwd = process.cwd(); |
| 2888 | let result = ''; |
| 2889 | |
| 2890 | switch (name) { |
| 2891 | case 'smallcode_read_file': { |
| 2892 | const safe = safeResolvePath(args.path, cwd); |
| 2893 | if (!safe.ok) return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `Error: ${safe.reason}` }], isError: true }}; |
| 2894 | try { result = sanitizeToolOutput(fs.readFileSync(safe.fullPath, 'utf-8')); } |
| 2895 | catch (e) { return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: `Error: ${e.message}` }], isError: true }}; } |
| 2896 | break; |
| 2897 | } |
| 2898 | case 'smallcode_bash': { |
| 2899 | const { execSync } = require('child_process'); |
| 2900 | const command = String(args.command || ''); |
| 2901 | // Apply same blocked-command checks as the agent's bash tool |
| 2902 | if (/rm\s+-rf\s+\/[^.]/.test(command) || /format\s+c:/i.test(command)) { |
| 2903 | return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: 'Error: destructive command blocked' }], isError: true }}; |
| 2904 | } |
| 2905 | try { |
| 2906 | const output = execSync(command, { encoding: 'utf-8', timeout: 30000, cwd, maxBuffer: 1024 * 1024 }); |
| 2907 | result = sanitizeToolOutput(output).slice(0, 4000); |
| 2908 | } catch (e) { result = sanitizeToolOutput((e.stdout || '') + (e.stderr || e.message || '')).slice(0, 2000); } |
| 2909 | break; |
| 2910 | } |
| 2911 | case 'smallcode_search': { |
| 2912 | const { execSync } = require('child_process'); |
| 2913 | const pattern = String(args.pattern || ''); |
| 2914 | const searchPath = args.path ? safeResolvePath(args.path, cwd) : { ok: true, fullPath: '.' }; |
| 2915 | if (!searchPath.ok) { result = `Error: ${searchPath.reason}`; break; } |
| 2916 | try { |
| 2917 | const cmd = 'rg --line-number --max-count 10 ' + escapeShellArg(pattern) + ' ' + escapeShellArg(searchPath.fullPath || '.'); |
| 2918 | result = sanitizeToolOutput(execSync(cmd, { encoding: 'utf-8', timeout: 10000, cwd })).slice(0, 3000); |
| 2919 | } catch { result = 'No matches'; } |
| 2920 | break; |
| 2921 | } |
| 2922 | case 'smallcode_patch': { |
| 2923 | const safe = safeResolvePath(args.path, cwd); |
| 2924 | if (!safe.ok) { result = `Error: ${safe.reason}`; break; } |
| 2925 | try { |
| 2926 | let content = fs.readFileSync(safe.fullPath, 'utf-8'); |
| 2927 | if (!content.includes(args.old_str)) { result = 'Error: old_str not found'; break; } |
| 2928 | const count = content.split(args.old_str).length - 1; |
| 2929 | if (count > 1) { result = `Error: old_str matches ${count} locations`; break; } |
| 2930 | content = content.replace(args.old_str, args.new_str); |
| 2931 | fs.writeFileSync(safe.fullPath, content); |
| 2932 | result = `Patched ${args.path}`; |
| 2933 | } catch (e) { result = `Error: ${e.message}`; } |
| 2934 | break; |
| 2935 | } |
| 2936 | case 'smallcode_memory_load': { |
| 2937 | const objects = memoryStore.loadForTask(args.task || '', 2000); |
| 2938 | // Handle both array return (MemoryStore in memory.js) and {objects} return |
| 2939 | const items = Array.isArray(objects) ? objects : (objects?.objects || []); |
| 2940 | result = items.length > 0 |
| 2941 | ? items.map(o => `[${o.type}] ${o.title}: ${o.content}`).join('\n\n') |
no test coverage detected