Cargo.lock 🔗
@@ -258,6 +258,7 @@ version = "0.1.0"
dependencies = [
"acp_thread",
"agent-client-protocol",
+ "agent_settings",
"agentic-coding-protocol",
"anyhow",
"collections",
Agus Zubiaga created
Claude will now respect the `agent.always_allow_tool_actions` setting
and will set it when "Always Allow" is clicked.
Release Notes:
- N/A
Cargo.lock | 1
crates/agent_servers/Cargo.toml | 1
crates/agent_servers/src/claude.rs | 3
crates/agent_servers/src/claude/mcp_server.rs | 70 ++++++++++++++++++---
4 files changed, 64 insertions(+), 11 deletions(-)
@@ -258,6 +258,7 @@ version = "0.1.0"
dependencies = [
"acp_thread",
"agent-client-protocol",
+ "agent_settings",
"agentic-coding-protocol",
"anyhow",
"collections",
@@ -19,6 +19,7 @@ doctest = false
[dependencies]
acp_thread.workspace = true
agent-client-protocol.workspace = true
+agent_settings.workspace = true
agentic-coding-protocol.workspace = true
anyhow.workspace = true
collections.workspace = true
@@ -111,7 +111,8 @@ impl AgentConnection for ClaudeAgentConnection {
})?;
let (mut thread_tx, thread_rx) = watch::channel(WeakEntity::new_invalid());
- let permission_mcp_server = ClaudeZedMcpServer::new(thread_rx.clone(), cx).await?;
+ let fs = project.read_with(cx, |project, _cx| project.fs().clone())?;
+ let permission_mcp_server = ClaudeZedMcpServer::new(thread_rx.clone(), fs, cx).await?;
let mut mcp_servers = HashMap::default();
mcp_servers.insert(
@@ -1,8 +1,10 @@
use std::path::PathBuf;
+use std::sync::Arc;
use crate::claude::tools::{ClaudeTool, EditToolParams, ReadToolParams};
use acp_thread::AcpThread;
use agent_client_protocol as acp;
+use agent_settings::AgentSettings;
use anyhow::{Context, Result};
use collections::HashMap;
use context_server::listener::{McpServerTool, ToolResponse};
@@ -11,8 +13,11 @@ use context_server::types::{
ToolAnnotations, ToolResponseContent, ToolsCapabilities, requests,
};
use gpui::{App, AsyncApp, Task, WeakEntity};
+use project::Fs;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
+use settings::{Settings as _, update_settings_file};
+use util::debug_panic;
pub struct ClaudeZedMcpServer {
server: context_server::listener::McpServer,
@@ -23,6 +28,7 @@ pub const SERVER_NAME: &str = "zed";
impl ClaudeZedMcpServer {
pub async fn new(
thread_rx: watch::Receiver<WeakEntity<AcpThread>>,
+ fs: Arc<dyn Fs>,
cx: &AsyncApp,
) -> Result<Self> {
let mut mcp_server = context_server::listener::McpServer::new(cx).await?;
@@ -30,6 +36,7 @@ impl ClaudeZedMcpServer {
mcp_server.add_tool(PermissionTool {
thread_rx: thread_rx.clone(),
+ fs: fs.clone(),
});
mcp_server.add_tool(ReadTool {
thread_rx: thread_rx.clone(),
@@ -102,6 +109,7 @@ pub struct McpServerConfig {
#[derive(Clone)]
pub struct PermissionTool {
+ fs: Arc<dyn Fs>,
thread_rx: watch::Receiver<WeakEntity<AcpThread>>,
}
@@ -141,6 +149,24 @@ impl McpServerTool for PermissionTool {
input: Self::Input,
cx: &mut AsyncApp,
) -> Result<ToolResponse<Self::Output>> {
+ if agent_settings::AgentSettings::try_read_global(cx, |settings| {
+ settings.always_allow_tool_actions
+ })
+ .unwrap_or(false)
+ {
+ let response = PermissionToolResponse {
+ behavior: PermissionToolBehavior::Allow,
+ updated_input: input.input,
+ };
+
+ return Ok(ToolResponse {
+ content: vec![ToolResponseContent::Text {
+ text: serde_json::to_string(&response)?,
+ }],
+ structured_content: (),
+ });
+ }
+
let mut thread_rx = self.thread_rx.clone();
let Some(thread) = thread_rx.recv().await?.upgrade() else {
anyhow::bail!("Thread closed");
@@ -148,8 +174,10 @@ impl McpServerTool for PermissionTool {
let claude_tool = ClaudeTool::infer(&input.tool_name, input.input.clone());
let tool_call_id = acp::ToolCallId(input.tool_use_id.context("Tool ID required")?.into());
- let allow_option_id = acp::PermissionOptionId("allow".into());
- let reject_option_id = acp::PermissionOptionId("reject".into());
+
+ const ALWAYS_ALLOW: &'static str = "always_allow";
+ const ALLOW: &'static str = "allow";
+ const REJECT: &'static str = "reject";
let chosen_option = thread
.update(cx, |thread, cx| {
@@ -157,12 +185,17 @@ impl McpServerTool for PermissionTool {
claude_tool.as_acp(tool_call_id).into(),
vec![
acp::PermissionOption {
- id: allow_option_id.clone(),
+ id: acp::PermissionOptionId(ALWAYS_ALLOW.into()),
+ name: "Always Allow".into(),
+ kind: acp::PermissionOptionKind::AllowAlways,
+ },
+ acp::PermissionOption {
+ id: acp::PermissionOptionId(ALLOW.into()),
name: "Allow".into(),
kind: acp::PermissionOptionKind::AllowOnce,
},
acp::PermissionOption {
- id: reject_option_id.clone(),
+ id: acp::PermissionOptionId(REJECT.into()),
name: "Reject".into(),
kind: acp::PermissionOptionKind::RejectOnce,
},
@@ -172,16 +205,33 @@ impl McpServerTool for PermissionTool {
})??
.await?;
- let response = if chosen_option == allow_option_id {
- PermissionToolResponse {
+ let response = match chosen_option.0.as_ref() {
+ ALWAYS_ALLOW => {
+ cx.update(|cx| {
+ update_settings_file::<AgentSettings>(self.fs.clone(), cx, |settings, _| {
+ settings.set_always_allow_tool_actions(true);
+ });
+ })?;
+
+ PermissionToolResponse {
+ behavior: PermissionToolBehavior::Allow,
+ updated_input: input.input,
+ }
+ }
+ ALLOW => PermissionToolResponse {
behavior: PermissionToolBehavior::Allow,
updated_input: input.input,
- }
- } else {
- debug_assert_eq!(chosen_option, reject_option_id);
- PermissionToolResponse {
+ },
+ REJECT => PermissionToolResponse {
behavior: PermissionToolBehavior::Deny,
updated_input: input.input,
+ },
+ opt => {
+ debug_panic!("Unexpected option: {}", opt);
+ PermissionToolResponse {
+ behavior: PermissionToolBehavior::Deny,
+ updated_input: input.input,
+ }
}
};