Causeway
AI meets browser. Data flows between.
Architecture
Three layers. One direction.
Claude Code
AI sends tool calls over stdio. Navigate, click, screenshot, read — structured MCP, not natural language.
stdioCauseway
Rust binary translates MCP tool calls into CDP commands. Pure functions: data in, JSON pair out. No runtime.
WebSocketBrowser
Chromium executes via Chrome DevTools Protocol. Real browser, real rendering, real JavaScript. Not a simulation.
CDPThe Problem
Browser automation is broken.
Framework Theatre
Selenium. Playwright. Puppeteer. Install Node. Install the framework. Install the browser driver. Configure timeouts. Handle race conditions. Your test is three lines. Your infrastructure is three hundred.
Headless Hallucinations
Headless browsers render pages that real browsers don't. Missing fonts, broken layouts, phantom elements. You're testing a simulation. When it passes in CI and fails in production, the headless layer is the ghost.
Extension Parasites
Extensions that "add AI to browsing" want your profile, your cookies, your history. They ship bundled Chromium or inject scripts into every page. The AI sees what the extension allows.
AI-Blind
An AI told to "check the website" has no browser. It can fetch HTML, but it can't execute JavaScript, render CSS, click buttons, or see what a human sees. The gap between HTTP and browsing is immense.
The Answer
What if AI could just use a browser?
Causeway is a Rust binary. Not a framework, not an extension, not a library — a standalone executable. It speaks MCP over stdio to your AI, and CDP over WebSocket to your browser.
One binary. Two protocols. Complete browser control.
Causeway doesn't simulate a browser. It drives the real one you're already using — your tabs, your bookmarks, your session. The AI sees exactly what you see.
45 Tools
Everything an AI needs to browse.
Navigation
Go to any URL. Returns the page title.
Navigate back in history.
Navigate forward in history.
Get current page URL and title.
SPA-aware. Three detection layers.
Tabs
Enumerate all open tabs with IDs.
Activate any tab by target ID.
Open a new tab, optionally with a URL.
Close a tab by its target ID.
Interaction
Click any element by CSS selector.
Click an element by its visible text.
Double-click to select or trigger.
Type into inputs character by character.
Fill multiple form fields at once.
Choose a dropdown value.
Submit a form element.
Hover to reveal menus and tooltips.
Press keyboard keys like Enter or Tab.
Press shortcuts with modifier keys.
Drag between selectors or coordinates.
Set files on an input. No OS picker.
Respond to alerts, confirms, prompts.
Reading
Capture the visible page as PNG.
Screenshot a specific element.
Extract all visible text content.
Read text from a specific element.
Structural DOM tree at configurable depth.
Find matching elements with metadata.
Read any attribute from an element.
Page accessibility tree snapshot.
Waiting
Block until an element appears in DOM.
Wait until specific text appears.
Browser Control
Scroll the page by pixel coordinates.
Set browser viewport dimensions.
Emulate mobile devices or viewports.
Save the page as a PDF file.
Download any file to a local path.
Clear cache, cookies, localStorage.
Data & Debug
Run JavaScript in the page context.
Get browser cookies with full details.
Set a browser cookie by URL or domain.
Read buffered console output.
List captured network requests.
DOM count, heap size, layout stats.
Find tools declared by the page itself.
Real Code
Data in. JSON pair out.
/// Navigate to a URL.
pub fn navigate(url: &str) -> (&'static str, Value) {
("Page.navigate", json!({ "url": url }))
}
/// Capture a screenshot as base64 PNG.
pub fn screenshot() -> (&'static str, Value) {
("Page.captureScreenshot", json!({ "format": "png" }))
}
/// Send a CDP command and await the response.
pub async fn execute(
conn: &CdpConnection,
command: (&str, Value),
) -> Result<Value, CdpError> {
let (method, params) = command;
send(conn, method, params).await
}
async fn navigate(
&self,
Parameters(NavigateParams { url }): Parameters<NavigateParams>,
) -> Result<CallToolResult, McpError> {
cdp::execute(&self.conn, commands::navigate(&url))
.await
.map_err(|e| McpError::internal_error(
format!("Navigate failed: {e}"), None
))?;
// ...
}
[browser]
executable = "C:\\...\\brave.exe"
port = 9222
restore_session = true
dedicated_profile = false
Architecture
No objects. No state. Just data.
Traditional Automation
- Browser driver object
- Session manager
- Wait strategy classes
- Page object model
- Element wrapper classes
- Configuration objects
- Test runner framework
7+ layers between you and the browser.
Causeway
- Functions return
(&str, Value) execute()sends over WebSocket
Data in. JSON pair out.
No framework. No runtime. No abstractions between you and the protocol.
License
Sovereign software.
Causeway is free to use. Connect it to Claude Code, Cursor, or any MCP client. Your browser, your automation, your rules.
Get in Touch