import { useState, useEffect } from 'react';
import { Button } from './ui/button';
import { Input } from './ui/input';
import { Badge } from './ui/badge';
import { X, Plus, Settings as SettingsIcon, Shield, AlertTriangle, Moon, Sun, Server, Edit3, Trash2, Globe, Terminal, Zap, FolderOpen, LogIn } from 'lucide-react';
import { useTheme } from '../contexts/ThemeContext';
import { useTasksSettings } from '../contexts/TasksSettingsContext';
import StandaloneShell from './StandaloneShell';
import ClaudeLogo from './ClaudeLogo';
import CursorLogo from './CursorLogo';
function Settings({ isOpen, onClose, projects = [] }) {
const { isDarkMode, toggleDarkMode } = useTheme();
const {
tasksEnabled,
setTasksEnabled,
isTaskMasterInstalled,
isTaskMasterReady,
installationStatus,
isCheckingInstallation
} = useTasksSettings();
const [allowedTools, setAllowedTools] = useState([]);
const [disallowedTools, setDisallowedTools] = useState([]);
const [newAllowedTool, setNewAllowedTool] = useState('');
const [newDisallowedTool, setNewDisallowedTool] = useState('');
const [skipPermissions, setSkipPermissions] = useState(false);
const [isSaving, setIsSaving] = useState(false);
const [saveStatus, setSaveStatus] = useState(null);
const [projectSortOrder, setProjectSortOrder] = useState('name');
const [mcpServers, setMcpServers] = useState([]);
const [showMcpForm, setShowMcpForm] = useState(false);
const [editingMcpServer, setEditingMcpServer] = useState(null);
const [mcpFormData, setMcpFormData] = useState({
name: '',
type: 'stdio',
scope: 'user',
projectPath: '', // For local scope
config: {
command: '',
args: [],
env: {},
url: '',
headers: {},
timeout: 30000
},
jsonInput: '', // For JSON import
importMode: 'form' // 'form' or 'json'
});
const [mcpLoading, setMcpLoading] = useState(false);
const [mcpTestResults, setMcpTestResults] = useState({});
const [mcpServerTools, setMcpServerTools] = useState({});
const [mcpToolsLoading, setMcpToolsLoading] = useState({});
const [activeTab, setActiveTab] = useState('tools');
const [jsonValidationError, setJsonValidationError] = useState('');
const [toolsProvider, setToolsProvider] = useState('claude'); // 'claude' or 'cursor'
// Cursor-specific states
const [cursorAllowedCommands, setCursorAllowedCommands] = useState([]);
const [cursorDisallowedCommands, setCursorDisallowedCommands] = useState([]);
const [cursorSkipPermissions, setCursorSkipPermissions] = useState(false);
const [newCursorCommand, setNewCursorCommand] = useState('');
const [newCursorDisallowedCommand, setNewCursorDisallowedCommand] = useState('');
const [cursorMcpServers, setCursorMcpServers] = useState([]);
// Login modal states
const [showLoginModal, setShowLoginModal] = useState(false);
const [loginProvider, setLoginProvider] = useState(''); // 'claude' or 'cursor'
const [selectedProject, setSelectedProject] = useState(null);
// Common tool patterns for Claude
const commonTools = [
'Bash(git log:*)',
'Bash(git diff:*)',
'Bash(git status:*)',
'Write',
'Read',
'Edit',
'Glob',
'Grep',
'MultiEdit',
'Task',
'TodoWrite',
'TodoRead',
'WebFetch',
'WebSearch'
];
// Common shell commands for Cursor
const commonCursorCommands = [
'Shell(ls)',
'Shell(mkdir)',
'Shell(cd)',
'Shell(cat)',
'Shell(echo)',
'Shell(git status)',
'Shell(git diff)',
'Shell(git log)',
'Shell(npm install)',
'Shell(npm run)',
'Shell(python)',
'Shell(node)'
];
// Fetch Cursor MCP servers
const fetchCursorMcpServers = async () => {
try {
const token = localStorage.getItem('auth-token');
const response = await fetch('/api/cursor/mcp', {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
if (response.ok) {
const data = await response.json();
setCursorMcpServers(data.servers || []);
} else {
console.error('Failed to fetch Cursor MCP servers');
}
} catch (error) {
console.error('Error fetching Cursor MCP servers:', error);
}
};
// MCP API functions
const fetchMcpServers = async () => {
try {
const token = localStorage.getItem('auth-token');
// Try to read directly from config files for complete details
const configResponse = await fetch('/api/mcp/config/read', {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
if (configResponse.ok) {
const configData = await configResponse.json();
if (configData.success && configData.servers) {
setMcpServers(configData.servers);
return;
}
}
// Fallback to Claude CLI
const cliResponse = await fetch('/api/mcp/cli/list', {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
if (cliResponse.ok) {
const cliData = await cliResponse.json();
if (cliData.success && cliData.servers) {
// Convert CLI format to our format
const servers = cliData.servers.map(server => ({
id: server.name,
name: server.name,
type: server.type,
scope: 'user',
config: {
command: server.command || '',
args: server.args || [],
env: server.env || {},
url: server.url || '',
headers: server.headers || {},
timeout: 30000
},
created: new Date().toISOString(),
updated: new Date().toISOString()
}));
setMcpServers(servers);
return;
}
}
// Final fallback to direct config reading
const response = await fetch('/api/mcp/servers?scope=user', {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
if (response.ok) {
const data = await response.json();
setMcpServers(data.servers || []);
} else {
console.error('Failed to fetch MCP servers');
}
} catch (error) {
console.error('Error fetching MCP servers:', error);
}
};
const saveMcpServer = async (serverData) => {
try {
const token = localStorage.getItem('auth-token');
if (editingMcpServer) {
// For editing, remove old server and add new one
await deleteMcpServer(editingMcpServer.id, 'user');
}
// Use Claude CLI to add the server
const response = await fetch('/api/mcp/cli/add', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: serverData.name,
type: serverData.type,
scope: serverData.scope,
projectPath: serverData.projectPath,
command: serverData.config?.command,
args: serverData.config?.args || [],
url: serverData.config?.url,
headers: serverData.config?.headers || {},
env: serverData.config?.env || {}
})
});
if (response.ok) {
const result = await response.json();
if (result.success) {
await fetchMcpServers(); // Refresh the list
return true;
} else {
throw new Error(result.error || 'Failed to save server via Claude CLI');
}
} else {
const error = await response.json();
throw new Error(error.error || 'Failed to save server');
}
} catch (error) {
console.error('Error saving MCP server:', error);
throw error;
}
};
const deleteMcpServer = async (serverId, scope = 'user') => {
try {
const token = localStorage.getItem('auth-token');
// Use Claude CLI to remove the server with proper scope
const response = await fetch(`/api/mcp/cli/remove/${serverId}?scope=${scope}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
if (response.ok) {
const result = await response.json();
if (result.success) {
await fetchMcpServers(); // Refresh the list
return true;
} else {
throw new Error(result.error || 'Failed to delete server via Claude CLI');
}
} else {
const error = await response.json();
throw new Error(error.error || 'Failed to delete server');
}
} catch (error) {
console.error('Error deleting MCP server:', error);
throw error;
}
};
const testMcpServer = async (serverId, scope = 'user') => {
try {
const token = localStorage.getItem('auth-token');
const response = await fetch(`/api/mcp/servers/${serverId}/test?scope=${scope}`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
if (response.ok) {
const data = await response.json();
return data.testResult;
} else {
const error = await response.json();
throw new Error(error.error || 'Failed to test server');
}
} catch (error) {
console.error('Error testing MCP server:', error);
throw error;
}
};
const discoverMcpTools = async (serverId, scope = 'user') => {
try {
const token = localStorage.getItem('auth-token');
const response = await fetch(`/api/mcp/servers/${serverId}/tools?scope=${scope}`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
if (response.ok) {
const data = await response.json();
return data.toolsResult;
} else {
const error = await response.json();
throw new Error(error.error || 'Failed to discover tools');
}
} catch (error) {
console.error('Error discovering MCP tools:', error);
throw error;
}
};
useEffect(() => {
if (isOpen) {
loadSettings();
}
}, [isOpen]);
const loadSettings = async () => {
try {
// Load Claude settings from localStorage
const savedSettings = localStorage.getItem('claude-settings');
if (savedSettings) {
const settings = JSON.parse(savedSettings);
setAllowedTools(settings.allowedTools || []);
setDisallowedTools(settings.disallowedTools || []);
setSkipPermissions(settings.skipPermissions || false);
setProjectSortOrder(settings.projectSortOrder || 'name');
} else {
// Set defaults
setAllowedTools([]);
setDisallowedTools([]);
setSkipPermissions(false);
setProjectSortOrder('name');
}
// Load Cursor settings from localStorage
const savedCursorSettings = localStorage.getItem('cursor-tools-settings');
if (savedCursorSettings) {
const cursorSettings = JSON.parse(savedCursorSettings);
setCursorAllowedCommands(cursorSettings.allowedCommands || []);
setCursorDisallowedCommands(cursorSettings.disallowedCommands || []);
setCursorSkipPermissions(cursorSettings.skipPermissions || false);
} else {
// Set Cursor defaults
setCursorAllowedCommands([]);
setCursorDisallowedCommands([]);
setCursorSkipPermissions(false);
}
// Load MCP servers from API
await fetchMcpServers();
// Load Cursor MCP servers
await fetchCursorMcpServers();
} catch (error) {
console.error('Error loading tool settings:', error);
// Set defaults on error
setAllowedTools([]);
setDisallowedTools([]);
setSkipPermissions(false);
setProjectSortOrder('name');
}
};
// Login handlers
const handleClaudeLogin = () => {
setLoginProvider('claude');
setSelectedProject(projects?.[0] || { name: 'default', fullPath: process.cwd() });
setShowLoginModal(true);
};
const handleCursorLogin = () => {
setLoginProvider('cursor');
setSelectedProject(projects?.[0] || { name: 'default', fullPath: process.cwd() });
setShowLoginModal(true);
};
const handleLoginComplete = (exitCode) => {
if (exitCode === 0) {
// Login successful - could show a success message here
}
setShowLoginModal(false);
};
const saveSettings = () => {
setIsSaving(true);
setSaveStatus(null);
try {
// Save Claude settings
const claudeSettings = {
allowedTools,
disallowedTools,
skipPermissions,
projectSortOrder,
lastUpdated: new Date().toISOString()
};
// Save Cursor settings
const cursorSettings = {
allowedCommands: cursorAllowedCommands,
disallowedCommands: cursorDisallowedCommands,
skipPermissions: cursorSkipPermissions,
lastUpdated: new Date().toISOString()
};
// Save to localStorage
localStorage.setItem('claude-settings', JSON.stringify(claudeSettings));
localStorage.setItem('cursor-tools-settings', JSON.stringify(cursorSettings));
setSaveStatus('success');
setTimeout(() => {
onClose();
}, 1000);
} catch (error) {
console.error('Error saving tool settings:', error);
setSaveStatus('error');
} finally {
setIsSaving(false);
}
};
const addAllowedTool = (tool) => {
if (tool && !allowedTools.includes(tool)) {
setAllowedTools([...allowedTools, tool]);
setNewAllowedTool('');
}
};
const removeAllowedTool = (tool) => {
setAllowedTools(allowedTools.filter(t => t !== tool));
};
const addDisallowedTool = (tool) => {
if (tool && !disallowedTools.includes(tool)) {
setDisallowedTools([...disallowedTools, tool]);
setNewDisallowedTool('');
}
};
const removeDisallowedTool = (tool) => {
setDisallowedTools(disallowedTools.filter(t => t !== tool));
};
// MCP form handling functions
const resetMcpForm = () => {
setMcpFormData({
name: '',
type: 'stdio',
scope: 'user', // Default to user scope
projectPath: '',
config: {
command: '',
args: [],
env: {},
url: '',
headers: {},
timeout: 30000
},
jsonInput: '',
importMode: 'form'
});
setEditingMcpServer(null);
setShowMcpForm(false);
setJsonValidationError('');
};
const openMcpForm = (server = null) => {
if (server) {
setEditingMcpServer(server);
setMcpFormData({
name: server.name,
type: server.type,
scope: server.scope,
projectPath: server.projectPath || '',
config: { ...server.config },
raw: server.raw, // Store raw config for display
importMode: 'form', // Always use form mode when editing
jsonInput: ''
});
} else {
resetMcpForm();
}
setShowMcpForm(true);
};
const handleMcpSubmit = async (e) => {
e.preventDefault();
setMcpLoading(true);
try {
if (mcpFormData.importMode === 'json') {
// Use JSON import endpoint
const token = localStorage.getItem('auth-token');
const response = await fetch('/api/mcp/cli/add-json', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: mcpFormData.name,
jsonConfig: mcpFormData.jsonInput,
scope: mcpFormData.scope,
projectPath: mcpFormData.projectPath
})
});
if (response.ok) {
const result = await response.json();
if (result.success) {
await fetchMcpServers(); // Refresh the list
resetMcpForm();
setSaveStatus('success');
} else {
throw new Error(result.error || 'Failed to add server via JSON');
}
} else {
const error = await response.json();
throw new Error(error.error || 'Failed to add server');
}
} else {
// Use regular form-based save
await saveMcpServer(mcpFormData);
resetMcpForm();
setSaveStatus('success');
}
} catch (error) {
alert(`Error: ${error.message}`);
setSaveStatus('error');
} finally {
setMcpLoading(false);
}
};
const handleMcpDelete = async (serverId, scope) => {
if (confirm('Are you sure you want to delete this MCP server?')) {
try {
await deleteMcpServer(serverId, scope);
setSaveStatus('success');
} catch (error) {
alert(`Error: ${error.message}`);
setSaveStatus('error');
}
}
};
const handleMcpTest = async (serverId, scope) => {
try {
setMcpTestResults({ ...mcpTestResults, [serverId]: { loading: true } });
const result = await testMcpServer(serverId, scope);
setMcpTestResults({ ...mcpTestResults, [serverId]: result });
} catch (error) {
setMcpTestResults({
...mcpTestResults,
[serverId]: {
success: false,
message: error.message,
details: []
}
});
}
};
const handleMcpToolsDiscovery = async (serverId, scope) => {
try {
setMcpToolsLoading({ ...mcpToolsLoading, [serverId]: true });
const result = await discoverMcpTools(serverId, scope);
setMcpServerTools({ ...mcpServerTools, [serverId]: result });
} catch (error) {
setMcpServerTools({
...mcpServerTools,
[serverId]: {
success: false,
tools: [],
resources: [],
prompts: []
}
});
} finally {
setMcpToolsLoading({ ...mcpToolsLoading, [serverId]: false });
}
};
const updateMcpConfig = (key, value) => {
setMcpFormData(prev => ({
...prev,
config: {
...prev.config,
[key]: value
}
}));
};
const getTransportIcon = (type) => {
switch (type) {
case 'stdio': return
Tools that are automatically allowed without prompting for permission
Quick add common tools:
Tools that are automatically blocked without prompting for permission
"Bash(git log:*)" - Allow all git log commands"Bash(git diff:*)" - Allow all git diff commands"Write" - Allow all Write tool usage"Read" - Allow all Read tool usage"Bash(rm:*)" - Block all rm commands (dangerous)Model Context Protocol servers provide additional tools and data sources to Claude
{server.config.command}{server.config.url}{server.config.args.join(' ')}{Object.entries(server.config.env).map(([k, v]) => `${k}=${v}`).join(', ')}
{JSON.stringify(server.raw, null, 2)}
{tool.name}
{tool.description && tool.description !== 'No description provided' && (
- {tool.description}
)}
{resource.name}
{resource.description && resource.description !== 'No description provided' && (
- {resource.description}
)}
{prompt.name}
{prompt.description && prompt.description !== 'No description provided' && (
- {prompt.description}
)}
Shell commands that are automatically allowed without prompting for permission
Quick add common commands:
Shell commands that should always be denied
"Shell(ls)" - Allow ls command"Shell(git status)" - Allow git status command"Shell(mkdir)" - Allow mkdir command"-f" flag - Skip all permission prompts (dangerous)TaskMaster CLI is required to use task management features. Install it to get started:
npm install -g task-master-ai
View on GitHub
After installation:
task-master init in your project directoryAI-Powered Task Management: Break complex projects into manageable subtasks with AI assistance
PRD: Generate structured tasks from Product Requirements Documents
Dependency Tracking: Understand task relationships and execution order
Progress Visualization: Kanban boards, and detailed task views
TaskMaster AI (aka claude-task-master ) is an advanced AI-powered task management system built for developers.
Find documentation, setup guides, and examples for advanced TaskMaster workflows