mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-04 09:13:08 +00:00
refactor: remove MCP permission settings and streamline SDK options for autonomous mode
- Removed MCP permission settings from the application, including related functions and UI components. - Updated SDK options to always bypass permissions and allow unrestricted tool access in autonomous mode. - Adjusted related components and services to reflect the removal of MCP permission configurations, ensuring a cleaner and more efficient codebase.
This commit is contained in:
@@ -252,10 +252,14 @@ export function getModelForUseCase(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Base options that apply to all SDK calls
|
* Base options that apply to all SDK calls
|
||||||
|
*
|
||||||
|
* AUTONOMOUS MODE: Always bypass permissions and allow dangerous operations
|
||||||
|
* for fully autonomous operation without user prompts.
|
||||||
*/
|
*/
|
||||||
function getBaseOptions(): Partial<Options> {
|
function getBaseOptions(): Partial<Options> {
|
||||||
return {
|
return {
|
||||||
permissionMode: 'acceptEdits',
|
permissionMode: 'bypassPermissions',
|
||||||
|
allowDangerouslySkipPermissions: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,31 +280,27 @@ interface McpPermissionOptions {
|
|||||||
* Centralizes the logic for determining permission modes and tool restrictions
|
* Centralizes the logic for determining permission modes and tool restrictions
|
||||||
* when MCP servers are configured.
|
* when MCP servers are configured.
|
||||||
*
|
*
|
||||||
|
* AUTONOMOUS MODE: Always bypass permissions for fully autonomous operation.
|
||||||
|
* Always allow unrestricted tools when MCP servers are configured.
|
||||||
|
*
|
||||||
* @param config - The SDK options config
|
* @param config - The SDK options config
|
||||||
* @returns Object with MCP permission settings to spread into final options
|
* @returns Object with MCP permission settings to spread into final options
|
||||||
*/
|
*/
|
||||||
function buildMcpOptions(config: CreateSdkOptionsConfig): McpPermissionOptions {
|
function buildMcpOptions(config: CreateSdkOptionsConfig): McpPermissionOptions {
|
||||||
const hasMcpServers = config.mcpServers && Object.keys(config.mcpServers).length > 0;
|
const hasMcpServers = config.mcpServers && Object.keys(config.mcpServers).length > 0;
|
||||||
// Default to true for autonomous workflow. Security is enforced when adding servers
|
|
||||||
// via the security warning dialog that explains the risks.
|
|
||||||
const mcpAutoApprove = config.mcpAutoApproveTools ?? true;
|
|
||||||
const mcpUnrestricted = config.mcpUnrestrictedTools ?? true;
|
|
||||||
|
|
||||||
// Determine if we should bypass permissions based on settings
|
// AUTONOMOUS MODE: Always bypass permissions and allow unrestricted tools
|
||||||
const shouldBypassPermissions = hasMcpServers && mcpAutoApprove;
|
// Only restrict tools when no MCP servers are configured
|
||||||
// Determine if we should restrict tools (only when no MCP or unrestricted is disabled)
|
const shouldRestrictTools = !hasMcpServers;
|
||||||
const shouldRestrictTools = !hasMcpServers || !mcpUnrestricted;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
shouldRestrictTools,
|
shouldRestrictTools,
|
||||||
// Only include bypass options when MCP is configured and auto-approve is enabled
|
// AUTONOMOUS MODE: Always include bypass options (though base options already set this)
|
||||||
bypassOptions: shouldBypassPermissions
|
bypassOptions: {
|
||||||
? {
|
permissionMode: 'bypassPermissions' as const,
|
||||||
permissionMode: 'bypassPermissions' as const,
|
// Required flag when using bypassPermissions mode
|
||||||
// Required flag when using bypassPermissions mode
|
allowDangerouslySkipPermissions: true,
|
||||||
allowDangerouslySkipPermissions: true,
|
},
|
||||||
}
|
|
||||||
: {},
|
|
||||||
// Include MCP servers if configured
|
// Include MCP servers if configured
|
||||||
mcpServerOptions: config.mcpServers ? { mcpServers: config.mcpServers } : {},
|
mcpServerOptions: config.mcpServers ? { mcpServers: config.mcpServers } : {},
|
||||||
};
|
};
|
||||||
@@ -392,12 +392,6 @@ export interface CreateSdkOptionsConfig {
|
|||||||
|
|
||||||
/** MCP servers to make available to the agent */
|
/** MCP servers to make available to the agent */
|
||||||
mcpServers?: Record<string, McpServerConfig>;
|
mcpServers?: Record<string, McpServerConfig>;
|
||||||
|
|
||||||
/** Auto-approve MCP tool calls without permission prompts */
|
|
||||||
mcpAutoApproveTools?: boolean;
|
|
||||||
|
|
||||||
/** Allow unrestricted tools when MCP servers are enabled */
|
|
||||||
mcpUnrestrictedTools?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-export MCP types from @automaker/types for convenience
|
// Re-export MCP types from @automaker/types for convenience
|
||||||
@@ -426,10 +420,7 @@ export function createSpecGenerationOptions(config: CreateSdkOptionsConfig): Opt
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...getBaseOptions(),
|
...getBaseOptions(),
|
||||||
// Override permissionMode - spec generation only needs read-only tools
|
// AUTONOMOUS MODE: Base options already set bypassPermissions and allowDangerouslySkipPermissions
|
||||||
// Using "acceptEdits" can cause Claude to write files to unexpected locations
|
|
||||||
// See: https://github.com/AutoMaker-Org/automaker/issues/149
|
|
||||||
permissionMode: 'default',
|
|
||||||
model: getModelForUseCase('spec', config.model),
|
model: getModelForUseCase('spec', config.model),
|
||||||
maxTurns: MAX_TURNS.maximum,
|
maxTurns: MAX_TURNS.maximum,
|
||||||
cwd: config.cwd,
|
cwd: config.cwd,
|
||||||
@@ -458,8 +449,7 @@ export function createFeatureGenerationOptions(config: CreateSdkOptionsConfig):
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...getBaseOptions(),
|
...getBaseOptions(),
|
||||||
// Override permissionMode - feature generation only needs read-only tools
|
// AUTONOMOUS MODE: Base options already set bypassPermissions and allowDangerouslySkipPermissions
|
||||||
permissionMode: 'default',
|
|
||||||
model: getModelForUseCase('features', config.model),
|
model: getModelForUseCase('features', config.model),
|
||||||
maxTurns: MAX_TURNS.quick,
|
maxTurns: MAX_TURNS.quick,
|
||||||
cwd: config.cwd,
|
cwd: config.cwd,
|
||||||
|
|||||||
@@ -191,41 +191,6 @@ export async function getMCPServersFromSettings(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get MCP permission settings from global settings.
|
|
||||||
*
|
|
||||||
* @param settingsService - Optional settings service instance
|
|
||||||
* @param logPrefix - Prefix for log messages (e.g., '[AgentService]')
|
|
||||||
* @returns Promise resolving to MCP permission settings
|
|
||||||
*/
|
|
||||||
export async function getMCPPermissionSettings(
|
|
||||||
settingsService?: SettingsService | null,
|
|
||||||
logPrefix = '[SettingsHelper]'
|
|
||||||
): Promise<{ mcpAutoApproveTools: boolean; mcpUnrestrictedTools: boolean }> {
|
|
||||||
// Default to true for autonomous workflow. Security is enforced when adding servers
|
|
||||||
// via the security warning dialog that explains the risks.
|
|
||||||
const defaults = { mcpAutoApproveTools: true, mcpUnrestrictedTools: true };
|
|
||||||
|
|
||||||
if (!settingsService) {
|
|
||||||
return defaults;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const globalSettings = await settingsService.getGlobalSettings();
|
|
||||||
const result = {
|
|
||||||
mcpAutoApproveTools: globalSettings.mcpAutoApproveTools ?? true,
|
|
||||||
mcpUnrestrictedTools: globalSettings.mcpUnrestrictedTools ?? true,
|
|
||||||
};
|
|
||||||
logger.info(
|
|
||||||
`${logPrefix} MCP permission settings: autoApprove=${result.mcpAutoApproveTools}, unrestricted=${result.mcpUnrestrictedTools}`
|
|
||||||
);
|
|
||||||
return result;
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`${logPrefix} Failed to load MCP permission settings:`, error);
|
|
||||||
return defaults;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a settings MCPServerConfig to SDK McpServerConfig format.
|
* Convert a settings MCPServerConfig to SDK McpServerConfig format.
|
||||||
* Validates required fields and throws informative errors if missing.
|
* Validates required fields and throws informative errors if missing.
|
||||||
|
|||||||
@@ -63,20 +63,13 @@ export class ClaudeProvider extends BaseProvider {
|
|||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
// Build Claude SDK options
|
// Build Claude SDK options
|
||||||
// MCP permission logic - determines how to handle tool permissions when MCP servers are configured.
|
// AUTONOMOUS MODE: Always bypass permissions for fully autonomous operation
|
||||||
// This logic mirrors buildMcpOptions() in sdk-options.ts but is applied here since
|
|
||||||
// the provider is the final point where SDK options are constructed.
|
|
||||||
const hasMcpServers = options.mcpServers && Object.keys(options.mcpServers).length > 0;
|
const hasMcpServers = options.mcpServers && Object.keys(options.mcpServers).length > 0;
|
||||||
// Default to true for autonomous workflow. Security is enforced when adding servers
|
|
||||||
// via the security warning dialog that explains the risks.
|
|
||||||
const mcpAutoApprove = options.mcpAutoApproveTools ?? true;
|
|
||||||
const mcpUnrestricted = options.mcpUnrestrictedTools ?? true;
|
|
||||||
const defaultTools = ['Read', 'Write', 'Edit', 'Glob', 'Grep', 'Bash', 'WebSearch', 'WebFetch'];
|
const defaultTools = ['Read', 'Write', 'Edit', 'Glob', 'Grep', 'Bash', 'WebSearch', 'WebFetch'];
|
||||||
|
|
||||||
// Determine permission mode based on settings
|
// AUTONOMOUS MODE: Always bypass permissions and allow unrestricted tools
|
||||||
const shouldBypassPermissions = hasMcpServers && mcpAutoApprove;
|
// Only restrict tools when no MCP servers are configured
|
||||||
// Determine if we should restrict tools (only when no MCP or unrestricted is disabled)
|
const shouldRestrictTools = !hasMcpServers;
|
||||||
const shouldRestrictTools = !hasMcpServers || !mcpUnrestricted;
|
|
||||||
|
|
||||||
const sdkOptions: Options = {
|
const sdkOptions: Options = {
|
||||||
model,
|
model,
|
||||||
@@ -88,10 +81,9 @@ export class ClaudeProvider extends BaseProvider {
|
|||||||
// Only restrict tools if explicitly set OR (no MCP / unrestricted disabled)
|
// Only restrict tools if explicitly set OR (no MCP / unrestricted disabled)
|
||||||
...(allowedTools && shouldRestrictTools && { allowedTools }),
|
...(allowedTools && shouldRestrictTools && { allowedTools }),
|
||||||
...(!allowedTools && shouldRestrictTools && { allowedTools: defaultTools }),
|
...(!allowedTools && shouldRestrictTools && { allowedTools: defaultTools }),
|
||||||
// When MCP servers are configured and auto-approve is enabled, use bypassPermissions
|
// AUTONOMOUS MODE: Always bypass permissions and allow dangerous operations
|
||||||
permissionMode: shouldBypassPermissions ? 'bypassPermissions' : 'default',
|
permissionMode: 'bypassPermissions',
|
||||||
// Required when using bypassPermissions mode
|
allowDangerouslySkipPermissions: true,
|
||||||
...(shouldBypassPermissions && { allowDangerouslySkipPermissions: true }),
|
|
||||||
abortController,
|
abortController,
|
||||||
// Resume existing SDK session if we have a session ID
|
// Resume existing SDK session if we have a session ID
|
||||||
...(sdkSessionId && conversationHistory && conversationHistory.length > 0
|
...(sdkSessionId && conversationHistory && conversationHistory.length > 0
|
||||||
|
|||||||
@@ -164,7 +164,9 @@ export function createEnhanceHandler(
|
|||||||
systemPrompt,
|
systemPrompt,
|
||||||
maxTurns: 1,
|
maxTurns: 1,
|
||||||
allowedTools: [],
|
allowedTools: [],
|
||||||
permissionMode: 'acceptEdits',
|
// AUTONOMOUS MODE: Always bypass permissions
|
||||||
|
permissionMode: 'bypassPermissions',
|
||||||
|
allowDangerouslySkipPermissions: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -96,7 +96,9 @@ export function createGenerateTitleHandler(): (req: Request, res: Response) => P
|
|||||||
systemPrompt: SYSTEM_PROMPT,
|
systemPrompt: SYSTEM_PROMPT,
|
||||||
maxTurns: 1,
|
maxTurns: 1,
|
||||||
allowedTools: [],
|
allowedTools: [],
|
||||||
permissionMode: 'acceptEdits',
|
// AUTONOMOUS MODE: Always bypass permissions
|
||||||
|
permissionMode: 'bypassPermissions',
|
||||||
|
allowDangerouslySkipPermissions: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import {
|
|||||||
getEnableSandboxModeSetting,
|
getEnableSandboxModeSetting,
|
||||||
filterClaudeMdFromContext,
|
filterClaudeMdFromContext,
|
||||||
getMCPServersFromSettings,
|
getMCPServersFromSettings,
|
||||||
getMCPPermissionSettings,
|
|
||||||
getPromptCustomization,
|
getPromptCustomization,
|
||||||
} from '../lib/settings-helpers.js';
|
} from '../lib/settings-helpers.js';
|
||||||
|
|
||||||
@@ -235,9 +234,6 @@ export class AgentService {
|
|||||||
// Load MCP servers from settings (global setting only)
|
// Load MCP servers from settings (global setting only)
|
||||||
const mcpServers = await getMCPServersFromSettings(this.settingsService, '[AgentService]');
|
const mcpServers = await getMCPServersFromSettings(this.settingsService, '[AgentService]');
|
||||||
|
|
||||||
// Load MCP permission settings (global setting only)
|
|
||||||
const mcpPermissions = await getMCPPermissionSettings(this.settingsService, '[AgentService]');
|
|
||||||
|
|
||||||
// Load project context files (CLAUDE.md, CODE_QUALITY.md, etc.)
|
// Load project context files (CLAUDE.md, CODE_QUALITY.md, etc.)
|
||||||
const contextResult = await loadContextFiles({
|
const contextResult = await loadContextFiles({
|
||||||
projectPath: effectiveWorkDir,
|
projectPath: effectiveWorkDir,
|
||||||
@@ -264,8 +260,6 @@ export class AgentService {
|
|||||||
autoLoadClaudeMd,
|
autoLoadClaudeMd,
|
||||||
enableSandboxMode,
|
enableSandboxMode,
|
||||||
mcpServers: Object.keys(mcpServers).length > 0 ? mcpServers : undefined,
|
mcpServers: Object.keys(mcpServers).length > 0 ? mcpServers : undefined,
|
||||||
mcpAutoApproveTools: mcpPermissions.mcpAutoApproveTools,
|
|
||||||
mcpUnrestrictedTools: mcpPermissions.mcpUnrestrictedTools,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Extract model, maxTurns, and allowedTools from SDK options
|
// Extract model, maxTurns, and allowedTools from SDK options
|
||||||
@@ -290,8 +284,6 @@ export class AgentService {
|
|||||||
sandbox: sdkOptions.sandbox, // Pass sandbox configuration
|
sandbox: sdkOptions.sandbox, // Pass sandbox configuration
|
||||||
sdkSessionId: session.sdkSessionId, // Pass SDK session ID for resuming
|
sdkSessionId: session.sdkSessionId, // Pass SDK session ID for resuming
|
||||||
mcpServers: Object.keys(mcpServers).length > 0 ? mcpServers : undefined, // Pass MCP servers configuration
|
mcpServers: Object.keys(mcpServers).length > 0 ? mcpServers : undefined, // Pass MCP servers configuration
|
||||||
mcpAutoApproveTools: mcpPermissions.mcpAutoApproveTools, // Pass MCP auto-approve setting
|
|
||||||
mcpUnrestrictedTools: mcpPermissions.mcpUnrestrictedTools, // Pass MCP unrestricted tools setting
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Build prompt content with images
|
// Build prompt content with images
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ import {
|
|||||||
getEnableSandboxModeSetting,
|
getEnableSandboxModeSetting,
|
||||||
filterClaudeMdFromContext,
|
filterClaudeMdFromContext,
|
||||||
getMCPServersFromSettings,
|
getMCPServersFromSettings,
|
||||||
getMCPPermissionSettings,
|
|
||||||
getPromptCustomization,
|
getPromptCustomization,
|
||||||
} from '../lib/settings-helpers.js';
|
} from '../lib/settings-helpers.js';
|
||||||
|
|
||||||
@@ -2003,9 +2002,6 @@ This mock response was generated because AUTOMAKER_MOCK_AGENT=true was set.
|
|||||||
// Load MCP servers from settings (global setting only)
|
// Load MCP servers from settings (global setting only)
|
||||||
const mcpServers = await getMCPServersFromSettings(this.settingsService, '[AutoMode]');
|
const mcpServers = await getMCPServersFromSettings(this.settingsService, '[AutoMode]');
|
||||||
|
|
||||||
// Load MCP permission settings (global setting only)
|
|
||||||
const mcpPermissions = await getMCPPermissionSettings(this.settingsService, '[AutoMode]');
|
|
||||||
|
|
||||||
// Build SDK options using centralized configuration for feature implementation
|
// Build SDK options using centralized configuration for feature implementation
|
||||||
const sdkOptions = createAutoModeOptions({
|
const sdkOptions = createAutoModeOptions({
|
||||||
cwd: workDir,
|
cwd: workDir,
|
||||||
@@ -2014,8 +2010,6 @@ This mock response was generated because AUTOMAKER_MOCK_AGENT=true was set.
|
|||||||
autoLoadClaudeMd,
|
autoLoadClaudeMd,
|
||||||
enableSandboxMode,
|
enableSandboxMode,
|
||||||
mcpServers: Object.keys(mcpServers).length > 0 ? mcpServers : undefined,
|
mcpServers: Object.keys(mcpServers).length > 0 ? mcpServers : undefined,
|
||||||
mcpAutoApproveTools: mcpPermissions.mcpAutoApproveTools,
|
|
||||||
mcpUnrestrictedTools: mcpPermissions.mcpUnrestrictedTools,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Extract model, maxTurns, and allowedTools from SDK options
|
// Extract model, maxTurns, and allowedTools from SDK options
|
||||||
@@ -2058,8 +2052,6 @@ This mock response was generated because AUTOMAKER_MOCK_AGENT=true was set.
|
|||||||
settingSources: sdkOptions.settingSources,
|
settingSources: sdkOptions.settingSources,
|
||||||
sandbox: sdkOptions.sandbox, // Pass sandbox configuration
|
sandbox: sdkOptions.sandbox, // Pass sandbox configuration
|
||||||
mcpServers: Object.keys(mcpServers).length > 0 ? mcpServers : undefined, // Pass MCP servers configuration
|
mcpServers: Object.keys(mcpServers).length > 0 ? mcpServers : undefined, // Pass MCP servers configuration
|
||||||
mcpAutoApproveTools: mcpPermissions.mcpAutoApproveTools, // Pass MCP auto-approve setting
|
|
||||||
mcpUnrestrictedTools: mcpPermissions.mcpUnrestrictedTools, // Pass MCP unrestricted tools setting
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Execute via provider
|
// Execute via provider
|
||||||
@@ -2291,8 +2283,6 @@ After generating the revised spec, output:
|
|||||||
allowedTools: allowedTools,
|
allowedTools: allowedTools,
|
||||||
abortController,
|
abortController,
|
||||||
mcpServers: Object.keys(mcpServers).length > 0 ? mcpServers : undefined,
|
mcpServers: Object.keys(mcpServers).length > 0 ? mcpServers : undefined,
|
||||||
mcpAutoApproveTools: mcpPermissions.mcpAutoApproveTools,
|
|
||||||
mcpUnrestrictedTools: mcpPermissions.mcpUnrestrictedTools,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let revisionText = '';
|
let revisionText = '';
|
||||||
@@ -2431,8 +2421,6 @@ After generating the revised spec, output:
|
|||||||
allowedTools: allowedTools,
|
allowedTools: allowedTools,
|
||||||
abortController,
|
abortController,
|
||||||
mcpServers: Object.keys(mcpServers).length > 0 ? mcpServers : undefined,
|
mcpServers: Object.keys(mcpServers).length > 0 ? mcpServers : undefined,
|
||||||
mcpAutoApproveTools: mcpPermissions.mcpAutoApproveTools,
|
|
||||||
mcpUnrestrictedTools: mcpPermissions.mcpUnrestrictedTools,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let taskOutput = '';
|
let taskOutput = '';
|
||||||
@@ -2523,8 +2511,6 @@ Implement all the changes described in the plan above.`;
|
|||||||
allowedTools: allowedTools,
|
allowedTools: allowedTools,
|
||||||
abortController,
|
abortController,
|
||||||
mcpServers: Object.keys(mcpServers).length > 0 ? mcpServers : undefined,
|
mcpServers: Object.keys(mcpServers).length > 0 ? mcpServers : undefined,
|
||||||
mcpAutoApproveTools: mcpPermissions.mcpAutoApproveTools,
|
|
||||||
mcpUnrestrictedTools: mcpPermissions.mcpUnrestrictedTools,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
for await (const msg of continuationStream) {
|
for await (const msg of continuationStream) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
import { getMCPServersFromSettings, getMCPPermissionSettings } from '@/lib/settings-helpers.js';
|
import { getMCPServersFromSettings } from '@/lib/settings-helpers.js';
|
||||||
import type { SettingsService } from '@/services/settings-service.js';
|
import type { SettingsService } from '@/services/settings-service.js';
|
||||||
|
|
||||||
// Mock the logger
|
// Mock the logger
|
||||||
@@ -286,93 +286,4 @@ describe('settings-helpers.ts', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getMCPPermissionSettings', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
vi.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return defaults when settingsService is null', async () => {
|
|
||||||
const result = await getMCPPermissionSettings(null);
|
|
||||||
expect(result).toEqual({
|
|
||||||
mcpAutoApproveTools: true,
|
|
||||||
mcpUnrestrictedTools: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return defaults when settingsService is undefined', async () => {
|
|
||||||
const result = await getMCPPermissionSettings(undefined);
|
|
||||||
expect(result).toEqual({
|
|
||||||
mcpAutoApproveTools: true,
|
|
||||||
mcpUnrestrictedTools: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return settings from service', async () => {
|
|
||||||
const mockSettingsService = {
|
|
||||||
getGlobalSettings: vi.fn().mockResolvedValue({
|
|
||||||
mcpAutoApproveTools: false,
|
|
||||||
mcpUnrestrictedTools: false,
|
|
||||||
}),
|
|
||||||
} as unknown as SettingsService;
|
|
||||||
|
|
||||||
const result = await getMCPPermissionSettings(mockSettingsService);
|
|
||||||
expect(result).toEqual({
|
|
||||||
mcpAutoApproveTools: false,
|
|
||||||
mcpUnrestrictedTools: false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should default to true when settings are undefined', async () => {
|
|
||||||
const mockSettingsService = {
|
|
||||||
getGlobalSettings: vi.fn().mockResolvedValue({}),
|
|
||||||
} as unknown as SettingsService;
|
|
||||||
|
|
||||||
const result = await getMCPPermissionSettings(mockSettingsService);
|
|
||||||
expect(result).toEqual({
|
|
||||||
mcpAutoApproveTools: true,
|
|
||||||
mcpUnrestrictedTools: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle mixed settings', async () => {
|
|
||||||
const mockSettingsService = {
|
|
||||||
getGlobalSettings: vi.fn().mockResolvedValue({
|
|
||||||
mcpAutoApproveTools: true,
|
|
||||||
mcpUnrestrictedTools: false,
|
|
||||||
}),
|
|
||||||
} as unknown as SettingsService;
|
|
||||||
|
|
||||||
const result = await getMCPPermissionSettings(mockSettingsService);
|
|
||||||
expect(result).toEqual({
|
|
||||||
mcpAutoApproveTools: true,
|
|
||||||
mcpUnrestrictedTools: false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return defaults and log error on exception', async () => {
|
|
||||||
const mockSettingsService = {
|
|
||||||
getGlobalSettings: vi.fn().mockRejectedValue(new Error('Settings error')),
|
|
||||||
} as unknown as SettingsService;
|
|
||||||
|
|
||||||
const result = await getMCPPermissionSettings(mockSettingsService, '[Test]');
|
|
||||||
expect(result).toEqual({
|
|
||||||
mcpAutoApproveTools: true,
|
|
||||||
mcpUnrestrictedTools: true,
|
|
||||||
});
|
|
||||||
// Logger will be called with error, but we don't need to assert it
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should use custom log prefix', async () => {
|
|
||||||
const mockSettingsService = {
|
|
||||||
getGlobalSettings: vi.fn().mockResolvedValue({
|
|
||||||
mcpAutoApproveTools: true,
|
|
||||||
mcpUnrestrictedTools: true,
|
|
||||||
}),
|
|
||||||
} as unknown as SettingsService;
|
|
||||||
|
|
||||||
await getMCPPermissionSettings(mockSettingsService, '[CustomPrefix]');
|
|
||||||
// Logger will be called with custom prefix, but we don't need to assert it
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
export { MCPServerHeader } from './mcp-server-header';
|
export { MCPServerHeader } from './mcp-server-header';
|
||||||
export { MCPPermissionSettings } from './mcp-permission-settings';
|
|
||||||
export { MCPToolsWarning } from './mcp-tools-warning';
|
export { MCPToolsWarning } from './mcp-tools-warning';
|
||||||
export { MCPServerCard } from './mcp-server-card';
|
export { MCPServerCard } from './mcp-server-card';
|
||||||
|
|||||||
@@ -1,96 +0,0 @@
|
|||||||
import { ShieldAlert } from 'lucide-react';
|
|
||||||
import { Label } from '@/components/ui/label';
|
|
||||||
import { Switch } from '@/components/ui/switch';
|
|
||||||
import { syncSettingsToServer } from '@/hooks/use-settings-migration';
|
|
||||||
import { cn } from '@/lib/utils';
|
|
||||||
|
|
||||||
interface MCPPermissionSettingsProps {
|
|
||||||
mcpAutoApproveTools: boolean;
|
|
||||||
mcpUnrestrictedTools: boolean;
|
|
||||||
onAutoApproveChange: (checked: boolean) => void;
|
|
||||||
onUnrestrictedChange: (checked: boolean) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function MCPPermissionSettings({
|
|
||||||
mcpAutoApproveTools,
|
|
||||||
mcpUnrestrictedTools,
|
|
||||||
onAutoApproveChange,
|
|
||||||
onUnrestrictedChange,
|
|
||||||
}: MCPPermissionSettingsProps) {
|
|
||||||
const hasAnyEnabled = mcpAutoApproveTools || mcpUnrestrictedTools;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="px-6 py-4 border-b border-border/50 bg-muted/20">
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="flex items-start gap-3">
|
|
||||||
<Switch
|
|
||||||
id="mcp-auto-approve"
|
|
||||||
checked={mcpAutoApproveTools}
|
|
||||||
onCheckedChange={async (checked) => {
|
|
||||||
onAutoApproveChange(checked);
|
|
||||||
await syncSettingsToServer();
|
|
||||||
}}
|
|
||||||
data-testid="mcp-auto-approve-toggle"
|
|
||||||
className="mt-0.5"
|
|
||||||
/>
|
|
||||||
<div className="space-y-1 flex-1">
|
|
||||||
<Label htmlFor="mcp-auto-approve" className="text-sm font-medium cursor-pointer">
|
|
||||||
Auto-approve MCP tool calls
|
|
||||||
</Label>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
When enabled, the AI agent can use MCP tools without permission prompts.
|
|
||||||
</p>
|
|
||||||
{mcpAutoApproveTools && (
|
|
||||||
<p className="text-xs text-amber-600 flex items-center gap-1 mt-1">
|
|
||||||
<ShieldAlert className="h-3 w-3" />
|
|
||||||
Bypasses normal permission checks
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-start gap-3">
|
|
||||||
<Switch
|
|
||||||
id="mcp-unrestricted"
|
|
||||||
checked={mcpUnrestrictedTools}
|
|
||||||
onCheckedChange={async (checked) => {
|
|
||||||
onUnrestrictedChange(checked);
|
|
||||||
await syncSettingsToServer();
|
|
||||||
}}
|
|
||||||
data-testid="mcp-unrestricted-toggle"
|
|
||||||
className="mt-0.5"
|
|
||||||
/>
|
|
||||||
<div className="space-y-1 flex-1">
|
|
||||||
<Label htmlFor="mcp-unrestricted" className="text-sm font-medium cursor-pointer">
|
|
||||||
Unrestricted tool access
|
|
||||||
</Label>
|
|
||||||
<p className="text-xs text-muted-foreground">
|
|
||||||
When enabled, the AI agent can use any tool, not just the default set.
|
|
||||||
</p>
|
|
||||||
{mcpUnrestrictedTools && (
|
|
||||||
<p className="text-xs text-amber-600 flex items-center gap-1 mt-1">
|
|
||||||
<ShieldAlert className="h-3 w-3" />
|
|
||||||
Agent has full tool access including file writes and bash
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{hasAnyEnabled && (
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
'rounded-md border border-amber-500/30 bg-amber-500/10 p-3 mt-2',
|
|
||||||
'text-xs text-amber-700 dark:text-amber-400'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<p className="font-medium mb-1">Security Note</p>
|
|
||||||
<p>
|
|
||||||
These settings reduce security restrictions for MCP tool usage. Only enable if you
|
|
||||||
trust all configured MCP servers.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -21,16 +21,7 @@ interface PendingServerData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useMCPServers() {
|
export function useMCPServers() {
|
||||||
const {
|
const { mcpServers, addMCPServer, updateMCPServer, removeMCPServer } = useAppStore();
|
||||||
mcpServers,
|
|
||||||
addMCPServer,
|
|
||||||
updateMCPServer,
|
|
||||||
removeMCPServer,
|
|
||||||
mcpAutoApproveTools,
|
|
||||||
mcpUnrestrictedTools,
|
|
||||||
setMcpAutoApproveTools,
|
|
||||||
setMcpUnrestrictedTools,
|
|
||||||
} = useAppStore();
|
|
||||||
|
|
||||||
// State
|
// State
|
||||||
const [isAddDialogOpen, setIsAddDialogOpen] = useState(false);
|
const [isAddDialogOpen, setIsAddDialogOpen] = useState(false);
|
||||||
@@ -938,10 +929,6 @@ export function useMCPServers() {
|
|||||||
return {
|
return {
|
||||||
// Store state
|
// Store state
|
||||||
mcpServers,
|
mcpServers,
|
||||||
mcpAutoApproveTools,
|
|
||||||
mcpUnrestrictedTools,
|
|
||||||
setMcpAutoApproveTools,
|
|
||||||
setMcpUnrestrictedTools,
|
|
||||||
|
|
||||||
// Dialog state
|
// Dialog state
|
||||||
isAddDialogOpen,
|
isAddDialogOpen,
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
import { Plug } from 'lucide-react';
|
import { Plug } from 'lucide-react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { useMCPServers } from './hooks';
|
import { useMCPServers } from './hooks';
|
||||||
import {
|
import { MCPServerHeader, MCPToolsWarning, MCPServerCard } from './components';
|
||||||
MCPServerHeader,
|
|
||||||
MCPPermissionSettings,
|
|
||||||
MCPToolsWarning,
|
|
||||||
MCPServerCard,
|
|
||||||
} from './components';
|
|
||||||
import {
|
import {
|
||||||
AddEditServerDialog,
|
AddEditServerDialog,
|
||||||
DeleteServerDialog,
|
DeleteServerDialog,
|
||||||
@@ -20,10 +15,6 @@ export function MCPServersSection() {
|
|||||||
const {
|
const {
|
||||||
// Store state
|
// Store state
|
||||||
mcpServers,
|
mcpServers,
|
||||||
mcpAutoApproveTools,
|
|
||||||
mcpUnrestrictedTools,
|
|
||||||
setMcpAutoApproveTools,
|
|
||||||
setMcpUnrestrictedTools,
|
|
||||||
|
|
||||||
// Dialog state
|
// Dialog state
|
||||||
isAddDialogOpen,
|
isAddDialogOpen,
|
||||||
@@ -98,15 +89,6 @@ export function MCPServersSection() {
|
|||||||
onAdd={handleOpenAddDialog}
|
onAdd={handleOpenAddDialog}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{mcpServers.length > 0 && (
|
|
||||||
<MCPPermissionSettings
|
|
||||||
mcpAutoApproveTools={mcpAutoApproveTools}
|
|
||||||
mcpUnrestrictedTools={mcpUnrestrictedTools}
|
|
||||||
onAutoApproveChange={setMcpAutoApproveTools}
|
|
||||||
onUnrestrictedChange={setMcpUnrestrictedTools}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{showToolsWarning && <MCPToolsWarning totalTools={totalToolsCount} />}
|
{showToolsWarning && <MCPToolsWarning totalTools={totalToolsCount} />}
|
||||||
|
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
|
|||||||
@@ -230,8 +230,6 @@ export async function syncSettingsToServer(): Promise<boolean> {
|
|||||||
keyboardShortcuts: state.keyboardShortcuts,
|
keyboardShortcuts: state.keyboardShortcuts,
|
||||||
aiProfiles: state.aiProfiles,
|
aiProfiles: state.aiProfiles,
|
||||||
mcpServers: state.mcpServers,
|
mcpServers: state.mcpServers,
|
||||||
mcpAutoApproveTools: state.mcpAutoApproveTools,
|
|
||||||
mcpUnrestrictedTools: state.mcpUnrestrictedTools,
|
|
||||||
promptCustomization: state.promptCustomization,
|
promptCustomization: state.promptCustomization,
|
||||||
projects: state.projects,
|
projects: state.projects,
|
||||||
trashedProjects: state.trashedProjects,
|
trashedProjects: state.trashedProjects,
|
||||||
@@ -336,12 +334,10 @@ export async function loadMCPServersFromServer(): Promise<boolean> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const mcpServers = result.settings.mcpServers || [];
|
const mcpServers = result.settings.mcpServers || [];
|
||||||
const mcpAutoApproveTools = result.settings.mcpAutoApproveTools ?? true;
|
|
||||||
const mcpUnrestrictedTools = result.settings.mcpUnrestrictedTools ?? true;
|
|
||||||
|
|
||||||
// Clear existing and add all from server
|
// Clear existing and add all from server
|
||||||
// We need to update the store directly since we can't use hooks here
|
// We need to update the store directly since we can't use hooks here
|
||||||
useAppStore.setState({ mcpServers, mcpAutoApproveTools, mcpUnrestrictedTools });
|
useAppStore.setState({ mcpServers });
|
||||||
|
|
||||||
console.log(`[Settings Load] Loaded ${mcpServers.length} MCP servers from server`);
|
console.log(`[Settings Load] Loaded ${mcpServers.length} MCP servers from server`);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -1438,8 +1438,6 @@ export class HttpApiClient implements ElectronAPI {
|
|||||||
headers?: Record<string, string>;
|
headers?: Record<string, string>;
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
}>;
|
}>;
|
||||||
mcpAutoApproveTools?: boolean;
|
|
||||||
mcpUnrestrictedTools?: boolean;
|
|
||||||
};
|
};
|
||||||
error?: string;
|
error?: string;
|
||||||
}> => this.get('/api/settings/global'),
|
}> => this.get('/api/settings/global'),
|
||||||
|
|||||||
@@ -491,8 +491,6 @@ export interface AppState {
|
|||||||
|
|
||||||
// MCP Servers
|
// MCP Servers
|
||||||
mcpServers: MCPServerConfig[]; // List of configured MCP servers for agent use
|
mcpServers: MCPServerConfig[]; // List of configured MCP servers for agent use
|
||||||
mcpAutoApproveTools: boolean; // Auto-approve MCP tool calls without permission prompts
|
|
||||||
mcpUnrestrictedTools: boolean; // Allow unrestricted tools when MCP servers are enabled
|
|
||||||
|
|
||||||
// Prompt Customization
|
// Prompt Customization
|
||||||
promptCustomization: PromptCustomization; // Custom prompts for Auto Mode, Agent, Backlog Plan, Enhancement
|
promptCustomization: PromptCustomization; // Custom prompts for Auto Mode, Agent, Backlog Plan, Enhancement
|
||||||
@@ -777,8 +775,6 @@ export interface AppActions {
|
|||||||
setAutoLoadClaudeMd: (enabled: boolean) => Promise<void>;
|
setAutoLoadClaudeMd: (enabled: boolean) => Promise<void>;
|
||||||
setEnableSandboxMode: (enabled: boolean) => Promise<void>;
|
setEnableSandboxMode: (enabled: boolean) => Promise<void>;
|
||||||
setSkipSandboxWarning: (skip: boolean) => Promise<void>;
|
setSkipSandboxWarning: (skip: boolean) => Promise<void>;
|
||||||
setMcpAutoApproveTools: (enabled: boolean) => Promise<void>;
|
|
||||||
setMcpUnrestrictedTools: (enabled: boolean) => Promise<void>;
|
|
||||||
|
|
||||||
// Prompt Customization actions
|
// Prompt Customization actions
|
||||||
setPromptCustomization: (customization: PromptCustomization) => Promise<void>;
|
setPromptCustomization: (customization: PromptCustomization) => Promise<void>;
|
||||||
@@ -980,8 +976,6 @@ const initialState: AppState = {
|
|||||||
enableSandboxMode: false, // Default to disabled (can be enabled for additional security)
|
enableSandboxMode: false, // Default to disabled (can be enabled for additional security)
|
||||||
skipSandboxWarning: false, // Default to disabled (show sandbox warning dialog)
|
skipSandboxWarning: false, // Default to disabled (show sandbox warning dialog)
|
||||||
mcpServers: [], // No MCP servers configured by default
|
mcpServers: [], // No MCP servers configured by default
|
||||||
mcpAutoApproveTools: true, // Default to enabled - bypass permission prompts for MCP tools
|
|
||||||
mcpUnrestrictedTools: true, // Default to enabled - don't filter allowedTools when MCP enabled
|
|
||||||
promptCustomization: {}, // Empty by default - all prompts use built-in defaults
|
promptCustomization: {}, // Empty by default - all prompts use built-in defaults
|
||||||
aiProfiles: DEFAULT_AI_PROFILES,
|
aiProfiles: DEFAULT_AI_PROFILES,
|
||||||
projectAnalysis: null,
|
projectAnalysis: null,
|
||||||
@@ -1632,19 +1626,6 @@ export const useAppStore = create<AppState & AppActions>()(
|
|||||||
const { syncSettingsToServer } = await import('@/hooks/use-settings-migration');
|
const { syncSettingsToServer } = await import('@/hooks/use-settings-migration');
|
||||||
await syncSettingsToServer();
|
await syncSettingsToServer();
|
||||||
},
|
},
|
||||||
setMcpAutoApproveTools: async (enabled) => {
|
|
||||||
set({ mcpAutoApproveTools: enabled });
|
|
||||||
// Sync to server settings file
|
|
||||||
const { syncSettingsToServer } = await import('@/hooks/use-settings-migration');
|
|
||||||
await syncSettingsToServer();
|
|
||||||
},
|
|
||||||
setMcpUnrestrictedTools: async (enabled) => {
|
|
||||||
set({ mcpUnrestrictedTools: enabled });
|
|
||||||
// Sync to server settings file
|
|
||||||
const { syncSettingsToServer } = await import('@/hooks/use-settings-migration');
|
|
||||||
await syncSettingsToServer();
|
|
||||||
},
|
|
||||||
|
|
||||||
// Prompt Customization actions
|
// Prompt Customization actions
|
||||||
setPromptCustomization: async (customization) => {
|
setPromptCustomization: async (customization) => {
|
||||||
set({ promptCustomization: customization });
|
set({ promptCustomization: customization });
|
||||||
@@ -2933,8 +2914,6 @@ export const useAppStore = create<AppState & AppActions>()(
|
|||||||
skipSandboxWarning: state.skipSandboxWarning,
|
skipSandboxWarning: state.skipSandboxWarning,
|
||||||
// MCP settings
|
// MCP settings
|
||||||
mcpServers: state.mcpServers,
|
mcpServers: state.mcpServers,
|
||||||
mcpAutoApproveTools: state.mcpAutoApproveTools,
|
|
||||||
mcpUnrestrictedTools: state.mcpUnrestrictedTools,
|
|
||||||
// Prompt customization
|
// Prompt customization
|
||||||
promptCustomization: state.promptCustomization,
|
promptCustomization: state.promptCustomization,
|
||||||
// Profiles and sessions
|
// Profiles and sessions
|
||||||
|
|||||||
@@ -71,8 +71,6 @@ export interface ExecuteOptions {
|
|||||||
maxTurns?: number;
|
maxTurns?: number;
|
||||||
allowedTools?: string[];
|
allowedTools?: string[];
|
||||||
mcpServers?: Record<string, McpServerConfig>;
|
mcpServers?: Record<string, McpServerConfig>;
|
||||||
mcpAutoApproveTools?: boolean; // Auto-approve MCP tool calls without permission prompts
|
|
||||||
mcpUnrestrictedTools?: boolean; // Allow unrestricted tools when MCP servers are enabled
|
|
||||||
abortController?: AbortController;
|
abortController?: AbortController;
|
||||||
conversationHistory?: ConversationMessage[]; // Previous messages for context
|
conversationHistory?: ConversationMessage[]; // Previous messages for context
|
||||||
sdkSessionId?: string; // Claude SDK session ID for resuming conversations
|
sdkSessionId?: string; // Claude SDK session ID for resuming conversations
|
||||||
|
|||||||
@@ -359,10 +359,6 @@ export interface GlobalSettings {
|
|||||||
// MCP Server Configuration
|
// MCP Server Configuration
|
||||||
/** List of configured MCP servers for agent use */
|
/** List of configured MCP servers for agent use */
|
||||||
mcpServers: MCPServerConfig[];
|
mcpServers: MCPServerConfig[];
|
||||||
/** Auto-approve MCP tool calls without permission prompts (uses bypassPermissions mode) */
|
|
||||||
mcpAutoApproveTools?: boolean;
|
|
||||||
/** Allow unrestricted tools when MCP servers are enabled (don't filter allowedTools) */
|
|
||||||
mcpUnrestrictedTools?: boolean;
|
|
||||||
|
|
||||||
// Prompt Customization
|
// Prompt Customization
|
||||||
/** Custom prompts for Auto Mode, Agent Runner, Backlog Planning, and Enhancements */
|
/** Custom prompts for Auto Mode, Agent Runner, Backlog Planning, and Enhancements */
|
||||||
@@ -535,10 +531,6 @@ export const DEFAULT_GLOBAL_SETTINGS: GlobalSettings = {
|
|||||||
enableSandboxMode: false,
|
enableSandboxMode: false,
|
||||||
skipSandboxWarning: false,
|
skipSandboxWarning: false,
|
||||||
mcpServers: [],
|
mcpServers: [],
|
||||||
// Default to true for autonomous workflow. Security is enforced when adding servers
|
|
||||||
// via the security warning dialog that explains the risks.
|
|
||||||
mcpAutoApproveTools: true,
|
|
||||||
mcpUnrestrictedTools: true,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Default credentials (empty strings - user must provide API keys) */
|
/** Default credentials (empty strings - user must provide API keys) */
|
||||||
|
|||||||
Reference in New Issue
Block a user