aster/chrome_mcp/
native_host.rs1use std::path::PathBuf;
4use tokio::fs;
5
6use super::types::*;
7
8pub fn get_platform() -> Platform {
10 #[cfg(target_os = "macos")]
11 {
12 Platform::MacOS
13 }
14
15 #[cfg(target_os = "windows")]
16 {
17 Platform::Windows
18 }
19
20 #[cfg(target_os = "linux")]
21 {
22 if let Ok(release) = std::fs::read_to_string("/proc/version") {
24 if release.to_lowercase().contains("microsoft")
25 || release.to_lowercase().contains("wsl")
26 {
27 return Platform::Wsl;
28 }
29 }
30 Platform::Linux
31 }
32
33 #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "linux")))]
34 {
35 Platform::Unknown
36 }
37}
38
39pub fn get_native_hosts_directory() -> Option<PathBuf> {
41 let home = dirs::home_dir()?;
42
43 match get_platform() {
44 Platform::MacOS => Some(
45 home.join("Library")
46 .join("Application Support")
47 .join("Google")
48 .join("Chrome")
49 .join("NativeMessagingHosts"),
50 ),
51 Platform::Linux => Some(
52 home.join(".config")
53 .join("google-chrome")
54 .join("NativeMessagingHosts"),
55 ),
56 Platform::Windows => {
57 let app_data = std::env::var("APPDATA")
58 .map(PathBuf::from)
59 .unwrap_or_else(|_| home.join("AppData").join("Local"));
60 Some(app_data.join("Claude Code").join("ChromeNativeHost"))
61 }
62 _ => None,
63 }
64}
65
66pub fn get_claude_config_dir() -> PathBuf {
68 dirs::home_dir()
69 .unwrap_or_else(|| PathBuf::from("."))
70 .join(".aster")
71}
72
73pub fn get_socket_path() -> String {
75 let username = std::env::var("USER")
76 .or_else(|_| std::env::var("USERNAME"))
77 .unwrap_or_else(|_| "unknown".to_string());
78 let socket_name = format!("aster-mcp-browser-bridge-{}", username);
79
80 #[cfg(windows)]
81 return format!("\\\\.\\pipe\\{}", socket_name);
82
83 #[cfg(not(windows))]
84 return std::env::temp_dir()
85 .join(socket_name)
86 .to_string_lossy()
87 .to_string();
88}
89
90pub fn generate_native_host_manifest(wrapper_script_path: &str) -> serde_json::Value {
92 serde_json::json!({
93 "name": NATIVE_HOST_NAME,
94 "description": "Aster Browser Extension Native Host",
95 "path": wrapper_script_path,
96 "type": "stdio",
97 "allowed_origins": [
98 format!("chrome-extension://{}/", CHROME_EXTENSION_ID)
99 ]
100 })
101}
102
103pub fn generate_wrapper_script(command: &str) -> String {
105 match get_platform() {
106 Platform::Windows => format!(
107 "@echo off\nREM Chrome native host wrapper script\n{}\n",
108 command
109 ),
110 _ => format!(
111 "#!/bin/bash\n# Chrome native host wrapper script\nexec {}\n",
112 command
113 ),
114 }
115}
116
117pub fn is_chrome_integration_supported() -> bool {
119 matches!(
120 get_platform(),
121 Platform::MacOS | Platform::Linux | Platform::Windows
122 )
123}
124
125pub async fn is_chrome_integration_configured() -> bool {
127 let hosts_dir = match get_native_hosts_directory() {
128 Some(d) => d,
129 None => return false,
130 };
131
132 let manifest_path = hosts_dir.join(format!("{}.json", NATIVE_HOST_NAME));
133 fs::metadata(&manifest_path).await.is_ok()
134}
135
136pub fn get_mcp_tool_names() -> Vec<String> {
138 vec![
139 "mcp__claude-in-chrome__javascript_tool".to_string(),
140 "mcp__claude-in-chrome__read_page".to_string(),
141 "mcp__claude-in-chrome__find".to_string(),
142 "mcp__claude-in-chrome__form_input".to_string(),
143 "mcp__claude-in-chrome__computer".to_string(),
144 "mcp__claude-in-chrome__navigate".to_string(),
145 "mcp__claude-in-chrome__resize_window".to_string(),
146 "mcp__claude-in-chrome__gif_creator".to_string(),
147 "mcp__claude-in-chrome__upload_image".to_string(),
148 "mcp__claude-in-chrome__get_page_text".to_string(),
149 "mcp__claude-in-chrome__tabs_context_mcp".to_string(),
150 "mcp__claude-in-chrome__tabs_create_mcp".to_string(),
151 "mcp__claude-in-chrome__update_plan".to_string(),
152 "mcp__claude-in-chrome__read_console_messages".to_string(),
153 "mcp__claude-in-chrome__read_network_requests".to_string(),
154 "mcp__claude-in-chrome__shortcuts_list".to_string(),
155 "mcp__claude-in-chrome__shortcuts_execute".to_string(),
156 ]
157}
158
159pub fn should_enable_chrome_integration(cli_chrome_flag: Option<bool>) -> bool {
161 if cli_chrome_flag == Some(false) {
163 return false;
164 }
165
166 if cli_chrome_flag == Some(true) {
168 return true;
169 }
170
171 if let Ok(env_value) = std::env::var("ASTER_ENABLE_CHROME") {
173 if env_value == "1" || env_value == "true" {
174 return true;
175 }
176 if env_value == "0" || env_value == "false" {
177 return false;
178 }
179 }
180
181 false
182}
183
184#[derive(Debug)]
186pub struct SetupResult {
187 pub success: bool,
188 pub message: String,
189 pub manifest_path: Option<PathBuf>,
190 pub wrapper_path: Option<PathBuf>,
191}
192
193pub async fn setup_chrome_native_host(command: &str) -> Result<SetupResult, String> {
195 if !is_chrome_integration_supported() {
197 return Ok(SetupResult {
198 success: false,
199 message: "Chrome integration is not supported on this platform".to_string(),
200 manifest_path: None,
201 wrapper_path: None,
202 });
203 }
204
205 let hosts_dir = get_native_hosts_directory()
207 .ok_or_else(|| "Failed to get native hosts directory".to_string())?;
208
209 fs::create_dir_all(&hosts_dir)
211 .await
212 .map_err(|e| format!("Failed to create native hosts directory: {}", e))?;
213
214 let wrapper_ext = if get_platform() == Platform::Windows {
216 "bat"
217 } else {
218 "sh"
219 };
220 let wrapper_path = hosts_dir.join(format!("{}.{}", NATIVE_HOST_NAME, wrapper_ext));
221
222 let wrapper_content = generate_wrapper_script(command);
224 fs::write(&wrapper_path, &wrapper_content)
225 .await
226 .map_err(|e| format!("Failed to write wrapper script: {}", e))?;
227
228 #[cfg(unix)]
230 {
231 use std::os::unix::fs::PermissionsExt;
232 let perms = std::fs::Permissions::from_mode(0o755);
233 std::fs::set_permissions(&wrapper_path, perms)
234 .map_err(|e| format!("Failed to set wrapper script permissions: {}", e))?;
235 }
236
237 let manifest_path = hosts_dir.join(format!("{}.json", NATIVE_HOST_NAME));
239 let manifest = generate_native_host_manifest(&wrapper_path.to_string_lossy());
240 let manifest_json = serde_json::to_string_pretty(&manifest)
241 .map_err(|e| format!("Failed to serialize manifest: {}", e))?;
242
243 fs::write(&manifest_path, &manifest_json)
244 .await
245 .map_err(|e| format!("Failed to write manifest: {}", e))?;
246
247 #[cfg(windows)]
249 {
250 setup_windows_registry(&manifest_path)?;
251 }
252
253 Ok(SetupResult {
254 success: true,
255 message: "Chrome native host installed successfully".to_string(),
256 manifest_path: Some(manifest_path),
257 wrapper_path: Some(wrapper_path),
258 })
259}
260
261#[cfg(windows)]
263fn setup_windows_registry(manifest_path: &PathBuf) -> Result<(), String> {
264 use winreg::enums::*;
265 use winreg::RegKey;
266
267 let hkcu = RegKey::predef(HKEY_CURRENT_USER);
268 let path = format!(
269 "Software\\Google\\Chrome\\NativeMessagingHosts\\{}",
270 NATIVE_HOST_NAME
271 );
272
273 let (key, _) = hkcu
274 .create_subkey(&path)
275 .map_err(|e| format!("Failed to create registry key: {}", e))?;
276
277 let manifest_str: String = manifest_path.to_string_lossy().to_string();
278 key.set_value("", &manifest_str)
279 .map_err(|e| format!("Failed to set registry value: {}", e))?;
280
281 Ok(())
282}
283
284pub async fn uninstall_chrome_native_host() -> Result<(), String> {
286 let hosts_dir = get_native_hosts_directory()
287 .ok_or_else(|| "Failed to get native hosts directory".to_string())?;
288
289 let manifest_path = hosts_dir.join(format!("{}.json", NATIVE_HOST_NAME));
291 if fs::metadata(&manifest_path).await.is_ok() {
292 fs::remove_file(&manifest_path)
293 .await
294 .map_err(|e| format!("Failed to remove manifest: {}", e))?;
295 }
296
297 let wrapper_ext = if get_platform() == Platform::Windows {
299 "bat"
300 } else {
301 "sh"
302 };
303 let wrapper_path = hosts_dir.join(format!("{}.{}", NATIVE_HOST_NAME, wrapper_ext));
304 if fs::metadata(&wrapper_path).await.is_ok() {
305 fs::remove_file(&wrapper_path)
306 .await
307 .map_err(|e| format!("Failed to remove wrapper script: {}", e))?;
308 }
309
310 #[cfg(windows)]
312 {
313 uninstall_windows_registry()?;
314 }
315
316 Ok(())
317}
318
319#[cfg(windows)]
321fn uninstall_windows_registry() -> Result<(), String> {
322 use winreg::enums::*;
323 use winreg::RegKey;
324
325 let hkcu = RegKey::predef(HKEY_CURRENT_USER);
326 let path = format!(
327 "Software\\Google\\Chrome\\NativeMessagingHosts\\{}",
328 NATIVE_HOST_NAME
329 );
330
331 let _ = hkcu.delete_subkey(&path);
333 Ok(())
334}