feat: run claude as non-root for bypassPermissions + replace MCP menu with live tools view

This commit is contained in:
Zac Gaetano 2026-04-05 12:26:37 -04:00
parent 318e7f01ad
commit 1e171dff1f

View file

@ -55,10 +55,8 @@ const App = () => {
const [editingTaskId, setEditingTaskId] = useState(null); const [editingTaskId, setEditingTaskId] = useState(null);
const [taskRuns, setTaskRuns] = useState({}); const [taskRuns, setTaskRuns] = useState({});
// MCP state // Connected tools state (read-only, populated from Claude's system/init)
const [mcpServers, setMcpServers] = useState([]); const [claudeTools, setClaudeTools] = useState(null);
const [showMcpForm, setShowMcpForm] = useState(false);
const [mcpForm, setMcpForm] = useState({ name: '', server_type: 'sse', url: '', command: '' });
// ---- Data fetching ---- // ---- Data fetching ----
const fetchTasks = async () => { const fetchTasks = async () => {
@ -68,10 +66,10 @@ const App = () => {
} catch (e) { /* ignore */ } } catch (e) { /* ignore */ }
}; };
const fetchMcpServers = async () => { const fetchClaudeTools = async () => {
try { try {
const r = await fetch(`${API}/api/mcp/servers`); const r = await fetch(`${API}/api/claude/tools`);
if (r.ok) setMcpServers(await r.json()); if (r.ok) setClaudeTools(await r.json());
} catch (e) { /* ignore */ } } catch (e) { /* ignore */ }
}; };
@ -95,12 +93,13 @@ const App = () => {
useEffect(() => { useEffect(() => {
fetchTasks(); fetchTasks();
fetchMcpServers(); fetchClaudeTools();
fetchChatSessions(); fetchChatSessions();
fetchClaudeStatus(); fetchClaudeStatus();
const interval = setInterval(() => { const interval = setInterval(() => {
fetchTasks(); fetchTasks();
fetchClaudeStatus(); fetchClaudeStatus();
fetchClaudeTools();
}, 8000); }, 8000);
return () => clearInterval(interval); return () => clearInterval(interval);
}, []); }, []);
@ -468,9 +467,9 @@ const App = () => {
onClick={() => { setActiveView('tasks'); fetchTasks(); }}> onClick={() => { setActiveView('tasks'); fetchTasks(); }}>
<span className="nav-icon">📋</span> Tasks <span className="nav-icon">📋</span> Tasks
</button> </button>
<button className={`nav-item ${activeView === 'mcp' ? 'active' : ''}`} <button className={`nav-item ${activeView === 'tools' ? 'active' : ''}`}
onClick={() => { setActiveView('mcp'); fetchMcpServers(); }}> onClick={() => { setActiveView('tools'); fetchClaudeTools(); }}>
<span className="nav-icon">🔌</span> MCP Servers <span className="nav-icon">🔌</span> Tools
</button> </button>
</nav> </nav>
@ -758,86 +757,58 @@ const App = () => {
</div> </div>
)} )}
{/* ====== MCP VIEW ====== */} {/* ====== TOOLS VIEW ====== */}
{activeView === 'mcp' && ( {activeView === 'tools' && (
<div className="view-container"> <div className="view-container">
<div className="view-header"> <div className="view-header">
<h2>MCP Servers</h2> <h2>Connected Tools</h2>
<button className="btn btn-primary" onClick={() => setShowMcpForm(true)}> <button className="btn btn-secondary btn-sm" onClick={fetchClaudeTools}>↺ Refresh</button>
+ Add Server
</button>
</div> </div>
<p className="view-desc"> <p className="view-desc">
Configure MCP servers that Claude can use as tools. These are saved and Tools available in the current Claude session — synced automatically from your account.
passed to Claude via its config. Restart Claude after adding servers. {claudeTools && ` ${claudeTools.total} tools across ${Object.keys(claudeTools.mcp_servers || {}).length} MCP servers.`}
</p> </p>
{showMcpForm && ( {!claudeTools || claudeTools.total === 0 ? (
<div className="form-card"> <div className="empty-state">
<h3>Add MCP Server</h3> {claudeTools === null
<div className="form-grid"> ? 'Loading tools…'
<div className="form-field"> : 'No tools detected yet. Start Claude and wait for initialization.'}
<label>Name</label> </div>
<input value={mcpForm.name} ) : (
onChange={e => setMcpForm(p => ({ ...p, name: e.target.value }))} <div className="mcp-list">
placeholder="my-server" /> {/* Built-in tools */}
</div> {claudeTools.builtin_tools?.length > 0 && (
<div className="form-field"> <div className="mcp-card">
<label>Type</label> <div className="mcp-card-header">
<select value={mcpForm.server_type} <div>
onChange={e => setMcpForm(p => ({ ...p, server_type: e.target.value }))}> <span className="mcp-name">Built-in Tools</span>
<option value="sse">SSE (HTTP)</option> <span className="mcp-type-badge">{claudeTools.builtin_tools.length} tools</span>
<option value="stdio">stdio (command)</option> </div>
</select>
</div>
{mcpForm.server_type === 'sse' && (
<div className="form-field form-field-full">
<label>URL</label>
<input value={mcpForm.url}
onChange={e => setMcpForm(p => ({ ...p, url: e.target.value }))}
placeholder="https://mcp.example.com/sse" />
</div> </div>
)} <div className="mcp-url">{claudeTools.builtin_tools.join(', ')}</div>
{mcpForm.server_type === 'stdio' && ( </div>
<div className="form-field form-field-full"> )}
<label>Command</label> {/* MCP servers */}
<input value={mcpForm.command} {Object.entries(claudeTools.mcp_servers || {}).map(([server, count]) => (
onChange={e => setMcpForm(p => ({ ...p, command: e.target.value }))} <div key={server} className="mcp-card">
placeholder="npx -y @my/mcp-server" /> <div className="mcp-card-header">
<div>
<span className="mcp-name">{server}</span>
<span className="mcp-type-badge">{count} tools</span>
<span className="task-status-badge status-idle" style={{marginLeft: '6px'}}>✓ connected</span>
</div>
</div> </div>
)} <div className="mcp-url">
</div> {(claudeTools.raw || [])
<div className="form-actions"> .filter(t => t.name?.startsWith(`mcp__${server}__`))
<button className="btn btn-primary" onClick={saveMcp} .map(t => t.name.replace(`mcp__${server}__`, ''))
disabled={!mcpForm.name}> .join(', ')}
Add Server </div>
</button> </div>
<button className="btn btn-secondary" onClick={() => setShowMcpForm(false)}> ))}
Cancel
</button>
</div>
</div> </div>
)} )}
<div className="mcp-list">
{mcpServers.length === 0 && (
<div className="empty-state">No MCP servers configured.</div>
)}
{mcpServers.map(s => (
<div key={s.name} className="mcp-card">
<div className="mcp-card-header">
<div>
<span className="mcp-name">{s.name}</span>
<span className="mcp-type-badge">{s.type || 'sse'}</span>
</div>
<button className="btn btn-sm btn-danger" onClick={() => removeMcp(s.name)}>
Remove
</button>
</div>
<div className="mcp-url">{s.url || s.command || ''}</div>
</div>
))}
</div>
</div> </div>
)} )}