Просмотр исходного кода

More safety around always allowing MCP

Matt Rubens 1 год назад
Родитель
Сommit
23efdeaf35

+ 2 - 0
src/core/prompts/system.ts

@@ -633,6 +633,8 @@ npm run build
 
 5. Install the MCP Server by adding the MCP server configuration to the settings file located at '${await mcpHub.getMcpSettingsFilePath()}'. The settings file may have other MCP servers already configured, so you would read it first and then add your new server to the existing \`mcpServers\` object.
 
+IMPORTANT: Regardless of what else you see in the settings file, you must not set any defaults for the \`alwaysAllow\` array in the newly added MCP server.
+
 \`\`\`json
 {
   "mcpServers": {

+ 10 - 0
src/core/webview/ClineProvider.ts

@@ -67,6 +67,7 @@ type GlobalStateKey =
 	| "allowedCommands"
 	| "soundEnabled"
 	| "diffEnabled"
+	| "alwaysAllowMcp"
 
 export const GlobalFileNames = {
 	apiConversationHistory: "api_conversation_history.json",
@@ -456,6 +457,10 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 						await this.updateGlobalState("alwaysAllowBrowser", message.bool ?? undefined)
 						await this.postStateToWebview()
 						break
+					case "alwaysAllowMcp":
+						await this.updateGlobalState("alwaysAllowMcp", message.bool)
+						await this.postStateToWebview()
+						break
 					case "askResponse":
 						this.cline?.handleWebviewAskResponse(message.askResponse!, message.text, message.images)
 						break
@@ -904,6 +909,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			alwaysAllowWrite,
 			alwaysAllowExecute,
 			alwaysAllowBrowser,
+			alwaysAllowMcp,
 			soundEnabled,
 			diffEnabled,
 			taskHistory,
@@ -921,6 +927,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			alwaysAllowWrite: alwaysAllowWrite ?? false,
 			alwaysAllowExecute: alwaysAllowExecute ?? false,
 			alwaysAllowBrowser: alwaysAllowBrowser ?? false,
+			alwaysAllowMcp: alwaysAllowMcp ?? false,
 			uriScheme: vscode.env.uriScheme,
 			clineMessages: this.cline?.clineMessages || [],
 			taskHistory: (taskHistory || [])
@@ -1017,6 +1024,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			alwaysAllowWrite,
 			alwaysAllowExecute,
 			alwaysAllowBrowser,
+			alwaysAllowMcp,
 			taskHistory,
 			allowedCommands,
 			soundEnabled,
@@ -1053,6 +1061,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			this.getGlobalState("alwaysAllowWrite") as Promise<boolean | undefined>,
 			this.getGlobalState("alwaysAllowExecute") as Promise<boolean | undefined>,
 			this.getGlobalState("alwaysAllowBrowser") as Promise<boolean | undefined>,
+			this.getGlobalState("alwaysAllowMcp") as Promise<boolean | undefined>,
 			this.getGlobalState("taskHistory") as Promise<HistoryItem[] | undefined>,
 			this.getGlobalState("allowedCommands") as Promise<string[] | undefined>,
 			this.getGlobalState("soundEnabled") as Promise<boolean | undefined>,
@@ -1107,6 +1116,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 			alwaysAllowWrite: alwaysAllowWrite ?? false,
 			alwaysAllowExecute: alwaysAllowExecute ?? false,
 			alwaysAllowBrowser: alwaysAllowBrowser ?? false,
+			alwaysAllowMcp: alwaysAllowMcp ?? false,
 			taskHistory,
 			allowedCommands,
 			soundEnabled,

+ 1 - 0
src/shared/ExtensionMessage.ts

@@ -47,6 +47,7 @@ export interface ExtensionState {
 	alwaysAllowWrite?: boolean
 	alwaysAllowExecute?: boolean
 	alwaysAllowBrowser?: boolean
+	alwaysAllowMcp?: boolean
 	uriScheme?: string
 	allowedCommands?: string[]
 	soundEnabled?: boolean

+ 1 - 0
src/shared/WebviewMessage.ts

@@ -29,6 +29,7 @@ export interface WebviewMessage {
 		| "cancelTask"
 		| "refreshOpenRouterModels"
 		| "alwaysAllowBrowser"
+		| "alwaysAllowMcp"
 		| "playSound"
 		| "soundEnabled"
 		| "diffEnabled"

+ 3 - 3
webview-ui/src/components/chat/ChatView.tsx

@@ -37,7 +37,7 @@ interface ChatViewProps {
 export const MAX_IMAGES_PER_MESSAGE = 20 // Anthropic limits to 20 images
 
 const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryView }: ChatViewProps) => {
-	const { version, clineMessages: messages, taskHistory, apiConfiguration, mcpServers, alwaysAllowBrowser, alwaysAllowReadOnly, alwaysAllowWrite, alwaysAllowExecute, allowedCommands } = useExtensionState()
+	const { version, clineMessages: messages, taskHistory, apiConfiguration, mcpServers, alwaysAllowBrowser, alwaysAllowReadOnly, alwaysAllowWrite, alwaysAllowExecute, alwaysAllowMcp, allowedCommands } = useExtensionState()
 
 	//const task = messages.length > 0 ? (messages[0].say === "task" ? messages[0] : undefined) : undefined) : undefined
 	const task = useMemo(() => messages.at(0), [messages]) // leaving this less safe version here since if the first message is not a task, then the extension is in a bad state and needs to be debugged (see Cline.abort)
@@ -803,11 +803,11 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
 			(alwaysAllowReadOnly && clineAsk === "tool" && isReadOnlyToolAction()) ||
 			(alwaysAllowWrite && clineAsk === "tool" && isWriteToolAction()) ||
 			(alwaysAllowExecute && clineAsk === "command" && isAllowedCommand()) ||
-			(clineAsk === "use_mcp_server" && isMcpToolAlwaysAllowed())
+			(alwaysAllowMcp && clineAsk === "use_mcp_server" && isMcpToolAlwaysAllowed())
 		) {
 			handlePrimaryButtonClick()
 		}
-	}, [clineAsk, enableButtons, handlePrimaryButtonClick, alwaysAllowBrowser, alwaysAllowReadOnly, alwaysAllowWrite, alwaysAllowExecute, messages, allowedCommands, mcpServers])
+	}, [clineAsk, enableButtons, handlePrimaryButtonClick, alwaysAllowBrowser, alwaysAllowReadOnly, alwaysAllowWrite, alwaysAllowExecute, alwaysAllowMcp, messages, allowedCommands, mcpServers])
 
 	return (
 		<div

+ 3 - 2
webview-ui/src/components/mcp/McpToolRow.tsx

@@ -5,9 +5,10 @@ import { vscode } from "../../utils/vscode"
 type McpToolRowProps = {
 	tool: McpTool
 	serverName?: string
+	alwaysAllowMcp?: boolean
 }
 
-const McpToolRow = ({ tool, serverName }: McpToolRowProps) => {
+const McpToolRow = ({ tool, serverName, alwaysAllowMcp }: McpToolRowProps) => {
 	const handleAlwaysAllowChange = () => {
 		if (!serverName) return;
 		
@@ -33,7 +34,7 @@ const McpToolRow = ({ tool, serverName }: McpToolRowProps) => {
 					<span className="codicon codicon-symbol-method" style={{ marginRight: "6px" }}></span>
 					<span style={{ fontWeight: 500 }}>{tool.name}</span>
 				</div>
-				{serverName && (
+				{serverName && alwaysAllowMcp && (
 					<VSCodeCheckbox
 						checked={tool.alwaysAllow}
 						onChange={handleAlwaysAllowChange}

+ 4 - 3
webview-ui/src/components/mcp/McpView.tsx

@@ -17,7 +17,7 @@ type McpViewProps = {
 }
 
 const McpView = ({ onDone }: McpViewProps) => {
-	const { mcpServers: servers } = useExtensionState()
+	const { mcpServers: servers, alwaysAllowMcp } = useExtensionState()
 	// const [servers, setServers] = useState<McpServer[]>([
 	// 	// Add some mock servers for testing
 	// 	{
@@ -126,7 +126,7 @@ const McpView = ({ onDone }: McpViewProps) => {
 				{servers.length > 0 && (
 					<div style={{ display: "flex", flexDirection: "column", gap: "10px" }}>
 						{servers.map((server) => (
-							<ServerRow key={server.name} server={server} />
+							<ServerRow key={server.name} server={server} alwaysAllowMcp={alwaysAllowMcp} />
 						))}
 					</div>
 				)}
@@ -152,7 +152,7 @@ const McpView = ({ onDone }: McpViewProps) => {
 }
 
 // Server Row Component
-const ServerRow = ({ server }: { server: McpServer }) => {
+const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer, alwaysAllowMcp?: boolean }) => {
 	const [isExpanded, setIsExpanded] = useState(false)
 
 	const getStatusColor = () => {
@@ -260,6 +260,7 @@ const ServerRow = ({ server }: { server: McpServer }) => {
 												key={tool.name}
 												tool={tool}
 												serverName={server.name}
+												alwaysAllowMcp={alwaysAllowMcp}
 											/>
 										))}
 									</div>

+ 25 - 0
webview-ui/src/components/settings/SettingsView.tsx

@@ -25,6 +25,8 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
 		setAlwaysAllowExecute,
 		alwaysAllowBrowser,
 		setAlwaysAllowBrowser,
+		alwaysAllowMcp,
+		setAlwaysAllowMcp,
 		soundEnabled,
 		setSoundEnabled,
 		diffEnabled,
@@ -50,6 +52,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
 			vscode.postMessage({ type: "alwaysAllowWrite", bool: alwaysAllowWrite })
 			vscode.postMessage({ type: "alwaysAllowExecute", bool: alwaysAllowExecute })
 			vscode.postMessage({ type: "alwaysAllowBrowser", bool: alwaysAllowBrowser })
+			vscode.postMessage({ type: "alwaysAllowMcp", bool: alwaysAllowMcp })
 			vscode.postMessage({ type: "allowedCommands", commands: allowedCommands ?? [] })
 			vscode.postMessage({ type: "soundEnabled", bool: soundEnabled })
 			vscode.postMessage({ type: "diffEnabled", bool: diffEnabled })
@@ -195,7 +198,29 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
 							color: "var(--vscode-errorForeground)",
 						}}>
 						⚠️ WARNING: When enabled, Cline will automatically perform browser actions without requiring approval. This is potentially very dangerous and could lead to unwanted system modifications or security risks. Enable only if you fully trust the AI and understand the risks.<br/><br/>NOTE: The checkbox only applies when the model supports computer use.
+					</p>
+				</div>
 
+				<div style={{ marginBottom: 5 }}>
+					<VSCodeCheckbox
+						checked={alwaysAllowMcp}
+						onChange={(e: any) => {
+							setAlwaysAllowMcp(e.target.checked)
+							vscode.postMessage({ type: "alwaysAllowMcp", bool: e.target.checked })
+						}}>
+						<span style={{ fontWeight: "500" }}>Always approve MCP tools</span>
+					</VSCodeCheckbox>
+					<p
+						style={{
+							fontSize: "12px",
+							marginTop: "5px",
+							padding: "8px",
+							backgroundColor: "var(--vscode-errorBackground)",
+							border: "1px solid var(--vscode-errorBorder)",
+							borderRadius: "4px",
+							color: "var(--vscode-errorForeground)",
+						}}>
+						⚠️ WARNING: When enabled, you can set individual MCP tools to auto-approve in the MCP Servers view. A tool will only be auto-approved if both this setting and the tool's individual "Always allow" checkbox are enabled. This is potentially very dangerous and could lead to unwanted system modifications or security risks. Enable only if you fully trust the AI and understand the risks.
 					</p>
 				</div>
 

+ 2 - 0
webview-ui/src/context/ExtensionStateContext.tsx

@@ -25,6 +25,7 @@ export interface ExtensionStateContextType extends ExtensionState {
 	setAlwaysAllowWrite: (value: boolean) => void
 	setAlwaysAllowExecute: (value: boolean) => void
 	setAlwaysAllowBrowser: (value: boolean) => void
+	setAlwaysAllowMcp: (value: boolean) => void
 	setShowAnnouncement: (value: boolean) => void
 	setAllowedCommands: (value: string[]) => void
 	setSoundEnabled: (value: boolean) => void
@@ -134,6 +135,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
 		setAlwaysAllowWrite: (value) => setState((prevState) => ({ ...prevState, alwaysAllowWrite: value })),
 		setAlwaysAllowExecute: (value) => setState((prevState) => ({ ...prevState, alwaysAllowExecute: value })),
 		setAlwaysAllowBrowser: (value) => setState((prevState) => ({ ...prevState, alwaysAllowBrowser: value })),
+		setAlwaysAllowMcp: (value) => setState((prevState) => ({ ...prevState, alwaysAllowMcp: value })),
 		setShowAnnouncement: (value) => setState((prevState) => ({ ...prevState, shouldShowAnnouncement: value })),
 		setAllowedCommands: (value) => setState((prevState) => ({ ...prevState, allowedCommands: value })),
 		setSoundEnabled: (value) => setState((prevState) => ({ ...prevState, soundEnabled: value })),