Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 175 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,16 @@ cc-types = { path = "crates/cc-types" }
cc-bootstrap = { path = "crates/cc-bootstrap" }
cc-auth = { path = "crates/cc-auth" }
cc-skills = { path = "crates/cc-skills" }
cc-config = { path = "crates/cc-config" }
cc-utils = { path = "crates/cc-utils" }
cc-mcp = { path = "crates/cc-mcp" }
cc-services = { path = "crates/cc-services" }
cc-computer-use = { path = "crates/cc-computer-use" }
cc-compact = { path = "crates/cc-compact" }
cc-sandbox = { path = "crates/cc-sandbox" }
cc-permissions = { path = "crates/cc-permissions" }
cc-browser = { path = "crates/cc-browser" }
cc-session = { path = "crates/cc-session" }

# Daemon HTTP server
axum = { version = "0.8", features = ["ws"] }
Expand Down
21 changes: 21 additions & 0 deletions crates/cc-browser/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "cc-browser"
version = "0.1.0"
edition = "2021"
description = "Chrome / browser MCP bridge for cc-rust"

[dependencies]
anyhow = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
tokio = { workspace = true }
tracing = { workspace = true }
parking_lot = { workspace = true }
chrono = { workspace = true }
dirs = { workspace = true }

cc-mcp = { workspace = true }
cc-config = { workspace = true }

[dev-dependencies]
tempfile = { workspace = true }
131 changes: 131 additions & 0 deletions crates/cc-browser/src/detection.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
//! Pure browser-MCP detection primitives.
//!
//! Split out of the root crate's `browser::detection` in Phase 4 (issue #73)
//! so cc-browser sub-modules (`permissions`, `tool_rendering`) can call
//! these helpers without pulling in `Arc<dyn Tool>` — the registry walker
//! (`detect_browser_servers` / `detect_browser_tools`) stays in the root
//! crate until `Tool` moves out in Phase 5.

use std::collections::HashSet;

/// MCP tool-name prefix for all MCP-wrapped tools.
pub const MCP_PREFIX: &str = "mcp__";

/// Known browser-automation tool basenames.
///
/// Intentionally generous — different servers use different names for
/// essentially the same actions (`click` vs `browser_click`, `navigate`
/// vs `goto`). We match on basename only, so the leading `mcp__{server}__`
/// has already been stripped.
pub const BROWSER_TOOL_BASENAMES: &[&str] = &[
// Navigation / tabs
"navigate",
"navigate_page",
"goto",
"tabs_create",
"tabs_create_mcp",
"tabs_close",
"tabs_close_mcp",
"tabs_context",
"tabs_context_mcp",
"new_page",
"close_page",
"switch_browser",
"select_page",
"list_pages",
// Page reading
"read_page",
"get_page_text",
"take_snapshot",
"snapshot",
"get_page",
// DOM / element interaction
"click",
"browser_click",
"double_click",
"hover",
"drag",
"press_key",
"type_text",
"fill",
"fill_form",
"form_input",
"select",
// File upload
"upload_file",
"file_upload",
// JavaScript execution
"evaluate_script",
"javascript_tool",
"evaluate",
// Console / network observability
"get_console_message",
"list_console_messages",
"read_console_messages",
"get_network_request",
"list_network_requests",
"read_network_requests",
// Screenshots / visual
"take_screenshot",
"screenshot",
// Misc
"wait_for",
"find",
"resize_page",
"resize_window",
"emulate",
"handle_dialog",
];

/// Parse an MCP tool name into `(server, action)` if the action matches a
/// recognized browser basename.
pub fn extract_browser_action(tool_name: &str) -> Option<(&str, &str)> {
let rest = tool_name.strip_prefix(MCP_PREFIX)?;
let (server, action) = rest.split_once("__")?;
if BROWSER_TOOL_BASENAMES.contains(&action) {
Some((server, action))
} else {
None
}
}

// ---------------------------------------------------------------------------
// Process-wide registry of browser server names
// ---------------------------------------------------------------------------
//
// Populated once at startup after MCP discovery + tool registration, then
// consulted by the system-prompt assembler, the permission decision path,
// and `/mcp list`.

static BROWSER_SERVERS: parking_lot::RwLock<Option<HashSet<String>>> =
parking_lot::RwLock::new(None);

/// Install the set of browser MCP server names for the rest of the process.
///
/// Call this once after MCP discovery + tool registration. Subsequent calls
/// overwrite the registry.
pub fn install_browser_servers(servers: HashSet<String>) {
*BROWSER_SERVERS.write() = Some(servers);
}

/// Snapshot the current set of browser server names (empty if not installed).
pub fn browser_servers_snapshot() -> HashSet<String> {
BROWSER_SERVERS.read().clone().unwrap_or_default()
}

/// Check whether a server name is registered as a browser server. Consults the
/// global registry and falls back to `false` if the registry was never installed.
pub fn is_browser_server(name: &str) -> bool {
match BROWSER_SERVERS.read().as_ref() {
Some(set) => set.contains(name),
None => false,
}
}

/// Clear the browser-server registry — tests only. `#[doc(hidden)]` keeps
/// it out of rustdoc; the `pub` visibility lets tests in the root crate
/// exercise detection flows with a clean registry.
#[doc(hidden)]
pub fn clear_browser_servers_for_tests() {
*BROWSER_SERVERS.write() = None;
}
Loading
Loading