Skip to content

Commit 878bec8

Browse files
authored
Merge pull request #9865 from gitbutlerapp/kv-branch-52
Claude Code permission check MCP tool
2 parents ef5b2b9 + 871a7b5 commit 878bec8

File tree

5 files changed

+104
-0
lines changed

5 files changed

+104
-0
lines changed

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/but-claude/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,10 @@ gitbutler-command-context.workspace = true
2929
gitbutler-branch-actions.workspace = true
3030
tokio = { workspace = true, features = ["full"] }
3131
dirs.workspace = true
32+
rmcp.workspace = true
33+
tracing.workspace = true
34+
tracing-subscriber = { version = "0.3", features = [
35+
"env-filter",
36+
"std",
37+
"fmt",
38+
] }

crates/but-claude/src/mcp.rs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,92 @@
1+
use std::sync::{Arc, Mutex};
12

3+
use anyhow::Result;
4+
use rmcp::{
5+
Error as McpError, ServerHandler, ServiceExt,
6+
model::{
7+
CallToolResult, Content, Implementation, ProtocolVersion, ServerCapabilities, ServerInfo,
8+
},
9+
schemars, tool,
10+
};
11+
use tracing_subscriber::{self, EnvFilter};
12+
13+
pub async fn start() -> Result<()> {
14+
tracing_subscriber::fmt()
15+
.with_env_filter(EnvFilter::from_default_env().add_directive(tracing::Level::DEBUG.into()))
16+
.with_writer(std::io::stderr)
17+
.with_ansi(false)
18+
.init();
19+
20+
tracing::info!("Starting MCP server");
21+
22+
let client_info = Arc::new(Mutex::new(None));
23+
let transport = (tokio::io::stdin(), tokio::io::stdout());
24+
let service = Mcp::default().serve(transport).await?;
25+
let info = service.peer_info();
26+
if let Ok(mut guard) = client_info.lock() {
27+
guard.replace(info.client_info.clone());
28+
}
29+
service.waiting().await?;
30+
Ok(())
31+
}
32+
33+
#[derive(Debug, Clone, Default)]
34+
pub struct Mcp {}
35+
36+
#[tool(tool_box)]
37+
impl Mcp {
38+
#[tool(description = "Permission check - approve if the input contains allow, otherwise deny.")]
39+
pub fn approval_prompt(
40+
&self,
41+
#[tool(aggr)] request: PermissionRequest,
42+
) -> Result<CallToolResult, McpError> {
43+
let result = Ok(PermissionResponse {
44+
behavior: Behavior::Allow,
45+
updated_input: Some(request.input),
46+
message: None,
47+
});
48+
result.map(|outcome| Ok(CallToolResult::success(vec![Content::json(outcome)?])))?
49+
}
50+
}
51+
52+
#[derive(Debug, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
53+
#[serde(rename_all = "camelCase")]
54+
pub struct PermissionRequest {
55+
#[schemars(description = "The name of the tool requesting permission")]
56+
tool_name: String,
57+
#[schemars(description = "The input for the tool")]
58+
input: serde_json::Value,
59+
#[schemars(description = "The unique tool use request ID")]
60+
tool_use_id: String,
61+
}
62+
63+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, strum::Display)]
64+
pub enum Behavior {
65+
#[strum(serialize = "allow")]
66+
Allow,
67+
#[strum(serialize = "deny")]
68+
Deny,
69+
}
70+
71+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
72+
#[serde(rename_all = "camelCase")]
73+
pub struct PermissionResponse {
74+
behavior: Behavior,
75+
updated_input: Option<serde_json::Value>,
76+
message: Option<String>,
77+
}
78+
79+
#[tool(tool_box)]
80+
impl ServerHandler for Mcp {
81+
fn get_info(&self) -> ServerInfo {
82+
ServerInfo {
83+
instructions: Some("GitButler MCP server".into()),
84+
capabilities: ServerCapabilities::builder().enable_tools().build(),
85+
server_info: Implementation {
86+
name: "GitButler MCP Server".into(),
87+
version: "1.0.0".into(),
88+
},
89+
protocol_version: ProtocolVersion::LATEST,
90+
}
91+
}
92+
}

crates/but/src/args.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,5 +132,7 @@ pub mod claude {
132132
#[clap(alias = "post-tool-use")]
133133
PostTool,
134134
Stop,
135+
#[clap(alias = "pp")]
136+
PermissionPromptMcp,
135137
}
136138
}

crates/but/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ async fn main() -> Result<()> {
7575
metrics_if_configured(app_settings, CommandName::ClaudeStop, p).ok();
7676
Ok(())
7777
}
78+
claude::Subcommands::PermissionPromptMcp => but_claude::mcp::start().await,
7879
},
7980
Subcommands::Log => {
8081
let result = log::commit_graph(&args.current_dir, args.json);

0 commit comments

Comments
 (0)