🪿 goshawk
A browser in the agent's hand. 9 tools. One verdict per action. Zero guessing.
Navigate, read, click, fill, log in, scrape. Pure Go, single binary, no Node, no Python, no Docker. Built on Chrome DevTools Protocol + MCP. Works with Claude Code, Cursor, OpenCode, Pi, anything that speaks MCP.
go install github.com/dondai1234/goshawk/v4/cmd/goshawk@latest
Install · The 9 tools · Self-diagnosing verdicts · Comparison · Token cost · What goshawk can't do
Why goshawk
Most browser MCP servers give the agent a raw page snapshot and let it figure out what happened. The agent burns 3 to 5 calls investigating: did the click work? is the page blank? what went wrong? goshawk closes that loop.
- 🔬 Self-diagnosing verdicts: every action returns a confidence-scored verdict. When something goes wrong, the verdict explains why and what to do next. "did you mean Sign in?" when you type "Sgn in". "errors visible: Email is required. Fix and retry" when a form fails. Zero extra tool calls, no LLM in the loop, pure heuristics.
- 🪶 9 tools, ~3.4K tokens: dense a11y-tree snapshots (ref-lines, not aria dumps), intent-first actions, one verdict per call. More capability than tools shipping 6K+ tokens.
- 🦅 Intent-first:
act intent="Sign in"finds the button.act fields={"Username":"john","Password":"hunter2"}fills a whole form in one call. Name what you want, not how to find it. - 🛡️ Self-healing refs: refs from
see/findsurvive re-renders. When React re-creates a node with the same role+name, goshawk auto-heals the ref. No stale-selector failures. - 🍪 Cookie banners auto-dismissed: on every navigate. Consent redirects auto-recovered. The a11y tree comes back clean, not cluttered with "Accept cookies" overlays.
- 📄 Blank page detection: when a page loads empty (slow SPA, rate-limit, JS not hydrated), the verdict says
BLANK PAGEwith what to try. One call, not five. - 🔐 One-call login:
login username= password=handles single-step and multi-step, verifies the resulting state, detects 2FA, SSO redirects, remember-me, forgot-password. Reports state, not HTTP status. - 🎭 Named profiles: isolated cookies, auth, storage. Switch identities in one call. Logins survive restarts with persistent profiles.
- 🦿 Single Go binary: 14 MB. No Node runtime, no Python venv, no Docker.
go installand done.
goshawk is for the agent. You install it once; the agent drives the browser through 9 tools.
🧰 The 9 tools
| Tool | One-liner |
|---|---|
nav |
Navigate to a URL and get an orientation back: page type, auth, primary actions WITH refs, counts. Auto-dismisses cookie banners, detects CHALLENGE and BLANK PAGE. waitSelector= for slow SPAs. |
see |
Inspect the current page. Levels: brief, refs, text, outline (CSS selectors for js), full, shot. |
act |
Interact: click, fill, select, hover, key, upload, or batch form fill (fields={}). Returns a self-diagnosing verdict. Don't re-see after. |
js |
Run JavaScript, get clean JSON. Helpers: $(sel), text(x), table(sel), links(sel), wait(fn,ms), more. |
find |
Locate elements: role=/text= filters the a11y tree, selector="css" queries the DOM directly. |
tabs |
List, switch, close, label tabs. |
history |
Session action log. errors=true for failures, last=N for recent steps. |
session |
reset (relaunch), clear (wipe cookies+storage), profile (named identities: create/switch/export/import). |
login |
One-call login. Detects fields, fills, submits, verifies state. Handles multi-step. Reports: logged in / 2FA / CHALLENGE / error / SSO. |
🔬 Self-diagnosing verdicts
The feature that sets goshawk apart. Three layers, zero extra tool calls:
Layer 1: "Did you mean?"
When act intent="Sgn in" finds no exact match, goshawk returns the 3 closest matches by Levenshtein distance:
no element named "Sgn in" found; did you mean: "Sign in" (button, r4), "Sign in to GitHub" (heading, r2)?
The agent retries with the right name. No see + find + see investigation loop.
Layer 2: "What blocked it?"
When an action fires but confidence is uncertain or likely, goshawk scans the page for visible error messages, HTML5 validation failures, and blocking modals:
verdict: [uncertain] no visible effect
errors visible: "Email is required". Fix those fields and retry
modal "Login Required" opened - call see to inspect
Layer 3: "What now?"
Each diagnostic is an actionable suggestion, not just a description. The agent knows exactly what to do next without another call.
Pure heuristics. No LLM in the loop. No extra API calls. Works across React, Vue, Angular, vanilla.
🎯 Confidence-scored verdicts
Every act returns a verdict with a confidence tag:
| Tag | Meaning | When |
|---|---|---|
[confirmed] |
DOM changed or XHR 2xx fired | The action definitely worked |
[likely] |
Content shifted or XHR fired | Something happened, verify if critical |
[uncertain] |
No visible effect | Check with see or the diagnostics |
Navigation is always confirmed. Bot challenges are always uncertain. No more guessing whether a click landed.
📝 Batch form filling
Fill an entire form in one call:
act fields={"Username":"john","Password":"hunter2","Remember me":"true","Country":"United States"}
Each label is resolved to a form field (a11y name + DOM fallback). The type is auto-detected: text, checkbox, radio, select, slider, file. The right action is performed for each. One re-snapshot, validation errors reported.
🔐 One-call login
login username="john" password="hunter2" url="https://example.com/login"
Detects the username + password + submit fields, fills them, submits, and verifies the resulting state. Handles:
- Single-step (username + password on one page)
- Multi-step (username first, then password appears)
- 2FA (stops and reports what's needed)
- SSO redirects (reports the domain)
- Remember-me checkbox (reports, doesn't auto-toggle)
- Forgot-password link (reports it)
- OAuth buttons (reports, doesn't auto-click)
Verifies state, not HTTP status. A silent failure is reported, not hidden.
🛡️ Self-healing refs
Refs from see and find are stable across re-renders. When a framework re-creates a DOM node with the same role + name (React key change, Vue v-if toggle), goshawk searches the current tree and auto-heals the ref. No stale-selector failures mid-flow.
Refs are per-tab and cleared on navigation. They survive re-renders within a page.
🍪 Cookie banners and consent redirects
Cookie/consent banners are auto-dismissed on every navigate. The a11y tree comes back clean, not cluttered with overlay elements.
If a site redirects to a full-page consent statement (common for EU users), goshawk detects the redirect, tries broader dismissal, and re-navigates to the original URL. If it can't recover, the verdict says CONSENT REDIRECT: original -> current with what to try.
📄 Blank page detection
When a page loads but renders empty (slow SPA hydration, rate-limiting, JS error), goshawk detects it and reports clearly:
BLANK PAGE: no interactive elements or headings detected.
The page may be a slow SPA still loading, may require JS execution,
or may have a rendering issue. Use js to check or see in a moment.
Before: the agent sees interactive: 0, calls see, then find, then see full, then js. 4 to 5 wasted calls. After: one call, the verdict explains what happened.
waitSelector= on nav gives a native waitForSelector for slow SPAs: wait for #dashboard to render before returning the orientation.
🎭 Named profiles
Isolated browser identities. Different logins, cookies, storage. Switch in one call.
session mode=profile action=create name="work"
session mode=profile action=switch name="work"
session mode=profile action=list
session mode=profile action=export # dump cookies+storage as JSON
session mode=profile action=import data="..." # restore from JSON
By default, goshawk persists to <os config dir>/goshawk so logins and cookies survive server restarts. The agent doesn't re-login each run. Use --no-persist for throwaway sessions.
🚀 Install
go install github.com/dondai1234/goshawk/v4/cmd/goshawk@latest
Then add to your MCP client config:
{
"mcpServers": {
"goshawk": {
"command": "goshawk",
"args": ["mcp"]
}
}
}
Requires Chrome or Chromium installed. goshawk connects via CDP.
| Flag | Default | Purpose |
|---|---|---|
--headless |
true |
--headless=false for real GPU fingerprint (hard anti-bot) |
--user-data-dir |
<config>/goshawk |
Persistent profile dir. Logins survive restarts. |
--no-persist |
false |
Throwaway temp profile |
--proxy-server |
none | Proxy URL. Residential proxy fixes most IP-reputation blocks. |
--user-agent |
Chrome default | Override User-Agent |
--viewport |
1366,768 |
Window size W,H |
--no-stealth |
false |
Disable anti-detection patches |
--no-cookie-dismiss |
false |
Disable cookie banner auto-dismiss |
--no-eval |
false |
Disable the js tool |
--op-timeout |
20s |
Per-operation timeout |
--idle-timeout |
10m |
Auto-close Chrome after idle period |
--allow-insecure-schemes |
false |
Allow file://, data:, etc. |
📊 Comparison
| goshawk | Playwright MCP | Browser-use | Puppeteer MCP | |
|---|---|---|---|---|
| Language | Go (single binary) | Node.js | Python | Node.js |
| Tools | 9 | 17+ | varies | 10+ |
| Token cost | ~3.4K | ~6K+ | varies | ~5K+ |
| Self-diagnosing verdicts | yes | no | no (uses LLM) | no |
| Confidence scoring | yes | no | no | no |
| Batch form fill | yes (fields=) |
no | no | no |
| Named profiles | yes | no | no | no |
| Self-healing refs | yes | no | no | no |
| Cookie banner auto-dismiss | yes | no | no | no |
| Blank page detection | yes | no | no | no |
| One-call login | yes | no | no | no |
| Intent-first actions | yes | no (selectors) | yes (LLM) | no (selectors) |
| Per-call LLM | no | no | yes | no |
| Runtime deps | Chrome only | Node + Playwright | Python + Playwright | Node + Puppeteer |
🪙 Token cost
Most browser MCP servers cost 5 to 8K tokens just to exist. goshawk's 9 tools cost ~3.4K tokens at tools/list (measured from the live MCP JSON response, 13623 chars). The connect-time instructions (~66 tokens) are injected once at handshake, not repeated every turn.
A nav orientation on a typical page returns ~60 to 80 tokens. A see refs on a page with 10 interactive elements returns ~40 tokens. Dense by design: ref-lines, not aria dumps.
🔧 Things to know first
| Thing | What to know |
|---|---|
| Chrome must be installed | goshawk connects via CDP to Chrome/Chromium. No Chrome, no browser. |
| One profile per process | Chrome locks the profile dir. Run concurrent clients with separate --user-data-dir paths. |
| 20s op timeout | If a page hasn't loaded in 20s, it's dead. Raise --op-timeout for known slow targets. |
| Headless may trigger bot detection | Use --headless=false for sites with aggressive anti-bot. A residential proxy (--proxy-server) is the #1 fix for IP blocks. |
js tool can be disabled |
Start with --no-eval to block arbitrary page JS. The other 8 tools still work. |
🚧 What goshawk can't do
| Limit | What happens instead |
|---|---|
| Hard CAPTCHAs (Turnstile, hCaptcha) | Reported as CHALLENGE in the verdict. The agent knows to stop, not retry blindly. |
| Shadow DOM piercing | js with $(sel) reaches open shadow roots. Closed shadow roots need selector= on act or find. |
| Iframe access | Same-origin iframes are reachable. Cross-origin iframes are blocked by browser security. |
| No screenshots in headless | see shot captures the page, but some sites render differently headless. Use --headless=false for visual fidelity. |
| Rate-limited sites | HN, Reddit, and others may serve "Sorry" pages under heavy headless use. The BLANK PAGE verdict catches this. |
No comments yet
Be the first to share your take.