
Unblink
io.github.ChristopherDavenport由 GitHub 帳號 ChristopherDavenport 發布v0.24.0更新於 Oct 24, 2026
Pure-Go browser for AI: fetch pages, render JS without Chromium, return clean Markdown over MCP.
概覽
抓取網頁並以精簡、受 token 預算限制的 Markdown 回傳給助理,不需 Chromium 就能渲染 JavaScript。
- 可以做什麼
- 這是一個純 Go 撰寫的 MCP 伺服器:抓取 URL、解析 HTML5、可選地在自製 DOM 上執行頁面 JavaScript,移除導覽、廣告與隱藏文字,並在 token 預算內回傳精簡後的 Markdown。它提供 18 個工具,涵蓋閱讀、導覽、工作階段、表單、結構化資料、結構描述擷取、頁面檢查、網站探索以及可選搜尋。回傳內容預設會被標記為不可信資料。
- 適用情境
- 當助理需要低成本地閱讀、擷取或研究大量網頁,又不想安裝瀏覽器引擎時使用。它不適合截圖、像素級還原、端對端測試或對抗嚴格反爬;這些情境應搭配真正的瀏覽器工具。
- 執行需求
- 以 stdio 在本機執行,可用靜態二進位檔、Docker 映像,或以 Go 1.25 以上版本從原始碼建置。除啟用可選搜尋工具外不需帳號或 API 金鑰;搜尋需要 SearXNG 端點或透過 UNBLINK_SEARCH_API_KEY 提供 Brave 金鑰。需要網路存取。
安裝
在 SourceWeft 中
- 開啟 儀表板中的 Unblink,將其新增到工作區。
- 為需要使用其工具的對話啟用該服務。
Desktop only,透過 STDIO。 STDIO 服務會啟動本機處理程序,因此需要 SourceWeft 桌面主機。
其他 MCP 客戶端
參照 儲存庫 中的啟動說明。
README
unblink
[CI] [Release] [Go Reference] [License: MIT]
unblink turns any web page into clean, token-budgeted Markdown for an AI agent — with no browser to install. It's a pure-Go MCP server: no Chromium, no V8, no Node, no cgo. It fetches a page, parses HTML5, optionally runs the page's JavaScript against a hand-rolled DOM, throws away everything that exists only for human eyes — nav, ads, chrome, tracking — and hands the model clean Markdown under a token budget, over the Model Context Protocol (MCP).
An AI doesn't need pixels. It needs structured meaning.
Why unblink
Three things it does that a browser-driving tool doesn't — each measured
head-to-head against Playwright MCP, Charlotte, Obscura, and Lightpanda on
identical fixtures (full numbers,
reproduce with make crossbench):
- ~100× fewer tokens on real pages. A nav-heavy news portal costs 303 tokens read as a reduced article, versus 20,000–27,000 for a browser-tool snapshot of the same page (×86 Playwright, ×90 Charlotte, ×67 Lightpanda). You pay a page's latency once; you pay its tokens every time the model re-reads its context.
- No browser to install. One static binary — 27 MB on disk, ~29 MB idle RAM, ~18 ms cold start. With no Chromium (439 MB) and no embedded V8, it's ~5× smaller on disk than the from-scratch V8 engines and ~16× smaller than Chromium — there's no browser engine to carry.
- It hardens the boundary to your model. unblink is the only one of the five tools measured that fences web content as untrusted, strips hidden-instruction text (0 of 3 planted injection blocks leak, vs. 1–3 for the others), and defangs image-beacon exfiltration URLs to inert text — all on by default. See Safety.
See it reduce a page
A 65 KB regional-news portal — mega-menu, cookie banner, ad rail, trending
sidebar, multi-column footer sitemap — wrapped around one short story. read
returns just the story (~300 tokens), fenced as untrusted data:
A browser tool's accessibility-tree snapshot of that same page is ~92 KB /
~26,000 tokens — every menu, rail, and footer link included. (This is the
benchmark's noisy-portal fixture; the numbers are in
docs/comparison.md.)
Which tool when
unblink is not a browser-automation tool that speaks MCP; it's a semantic reduction tool. Reach for it to read, extract, and research the web for a model at minimal token cost, footprint, and attack surface. Reach for a real browser (Playwright MCP, Charlotte) for screenshots, pixel-perfect fidelity, E2E testing, or hostile anti-bot — the things unblink deliberately doesn't do. They compose: use unblink for the hundred pages an agent reads, a real browser for the one it must drive or see.
What it deliberately doesn't do
The forfeits are design decisions, not gaps, so they're stated up front:
- No pixels, ever — no screenshots, no visual verification, no layout
engine or geometry (
getBoundingClientRectreturns zeros). Permanent non-goals (ADR 0007). - goja is not V8. The flat-DOM engine renders mainstream React/Vue/Preact/Svelte/Lit apps (verified against real pinned framework bundles), but it's a tree-walking interpreter: a heavy or sprawling bundle widens the gap with real V8, and content that lives in canvas/WebGL/Workers won't materialize.
- An anti-bot ceiling.
--tls-mimicclears naive fingerprint checks; Turnstile-class interactive challenges and server-side proof-of-work are out of scope. - Not the idle-RAM floor. The from-scratch V8 engines (Obscura 8 MB, Lightpanda 15 MB idle) undercut unblink's 29 MB idle RSS — against those two, the on-disk size and the content-boundary hardening are the differentiators, not idle memory.
Status
v0.24.0. The full pipeline works end to end: 18 MCP tools covering reading,
navigation, sessions, forms, structured data, schema extraction, page
inspection, site discovery, and search. The JavaScript engine is on by
default (opt out with --disable-js); the static read path turns most
server-rendered pages into clean Markdown with zero JavaScript, and the engine
can runtime-load a WebExtension (e.g. uBlock Origin Lite) for ad/tracker
blocking. See docs/architecture.md for the full design
and its non-goals, and docs/comparison.md for how unblink
compares to other AI web-browsing tools (Playwright MCP, Charlotte, Obscura,
Lightpanda).
Requirements
The official MCP Go SDK requires Go ≥ 1.25. The Makefile sets
GOTOOLCHAIN=auto, so the go command downloads the toolchain pinned in
go.mod automatically — you do not need to install Go 1.25 yourself, and your
global go env is left untouched. (If you run go directly rather than via
make, prefix commands with GOTOOLCHAIN=auto.)
Install
Or run the multi-arch (amd64/arm64) Docker image — no Go toolchain needed:
Or download a prebuilt binary from the GitHub releases page, or build from source (see Build & run).
Use it with an MCP client
unblink speaks MCP over stdio, so any MCP-capable client launches it as a subprocess. For Claude Code:
For Claude Desktop (or any client using the mcpServers config shape), add
to claude_desktop_config.json:
JavaScript rendering is on by default; add --disable-js for the zero-JavaScript
static read path (lighter, still handles most server-rendered pages). Add flags
like --search-provider or --tls-mimic to args as needed — see
Configuration.
unblink is also listed in the MCP registry
as io.github.ChristopherDavenport/unblink, and the repo ships a Claude Code
plugin manifest (.claude-plugin/plugin.json)
pinned to the current release image.
Safety: the content boundary
unblink feeds untrusted web pages to a model that will act on what it reads, so
it hardens the boundary between the two — on by default. Every other tool
measured secures the browser; unblink also secures what crosses into the
model. On a fixture carrying three planted hidden-instruction blocks
(display:none, aria-hidden, off-screen) plus an image-beacon exfiltration
URL, unblink is the only one of five tools that leaks 0 of 3 and defangs the
beacon (measured;
the others leak 1–3):
- Untrusted-content fence. Returned content is wrapped in a provenance
[UNTRUSTED WEB CONTENT …]fence with a random marker, so injected imperatives read as data, not instructions. Human-hidden text and comments are stripped, and Markdown image beacons (— a zero-click data-exfil channel) are defanged to inert, auditable text. Defense-in-depth against indirect prompt injection, not a guarantee.--no-safe-outputopts out. - SSRF dial guard. Every fetch — primary, per-session, one-shot, and
page-JS subrequest — is blocked from private/loopback/link-local/metadata IPs
(including CGNAT
100.64.0.0/10), checked against the resolved address. On by default;--allow-private/--js-allow-privateopt out. - Origin-scoped credentials. Injected bearer/basic/custom headers are pinned to their origin and stripped on any cross-origin redirect, so a token can't leak to another host. Secrets resolve by env-var name and never appear in session state or logs (see Authentication).
- Browser security model over untrusted page JS. Because unblink runs the
page's own JavaScript, it applies the browser's defaults over it — Same-Origin
Policy, CORS (with preflight + redirect re-validation), CSP (incl. nonce
hiding), and SRI — all default-on, each with an ADR. The principle is gate the
page, not the operator: a blocked cross-origin request is still sent and logged
(visible via the
requeststool); only the page-JS read is denied.
See SECURITY.md for the full threat model.
Build & run
JavaScript rendering (pure Go, no cgo, no Chromium) runs a page's scripts against
a hand-rolled DOM over the parsed tree: inline and external scripts,
ES modules (<script type=module>, import/export, dynamic import(),
import maps — bundled with esbuild), window.fetch + XMLHttpRequest, DOM
events with full capture/bubble propagation (delegated listeners,
once/passive/{signal} options, AbortController, typed Event subclasses),
and document.cookie (backed by the session jar). Page-JS network requests are
guarded — requests to private/loopback/metadata IPs are blocked and a per-render
download budget applies (--js-no-network, --js-allow-private,
--js-max-bytes). A background pool of fresh runtimes keeps render latency low
(--js-prewarm, 0 disables); the per-render budget defaults to 5s (--js-timeout).
With a session, interact keeps a live runtime
alive for the page so JS state persists across calls (a true browser-tab session);
live runtimes are capped (--js-max-live, LRU torn down) and both
window.localStorage and window.sessionStorage persist per session (a session
is a tab), so SPA auth/state flows survive across calls. Common globals
that bundles use without feature-detection are covered: structuredClone, a
connection-less WebSocket stub (error→close), inert Worker, append-mode
document.write, and hashchange.
The engine (on by default) renders the mainstream SPA frameworks (React, Vue,
Preact, Svelte, Lit / web components) via a flat-DOM model — a real
Node/Element/HTMLElement prototype chain, MutationObserver, custom-element upgrade,
and an encapsulating, composed Shadow DOM: each shadow root is a detached subtree
(so page JS querySelector respects the boundary), and a compose pass flattens it —
resolving <slot> distribution — into the light tree for extraction. Events cross the
boundary correctly (composed path, target retargeting, composedPath()), and
declarative Shadow DOM (<template shadowrootmode>) renders on the static no-JS path.
Layout/geometry is constant-stubbed (no pixel layout engine), and canvas/WebGL,
Workers/WebSocket/IndexedDB, and Shadow-DOM style scoping
(:host/::slotted/::part) remain out of scope.
Try it
unblink speaks MCP over stdio. Point any MCP-capable client at the unblink
binary, or drive it by hand:
Tools
Every page tool accepts an optional session (any string — cookies and history
persist across calls; auto-created on first use), use_current (act on the
session's current page instead of fetching a URL), and render (run the page's
JavaScript first — on by default; pass render=false to skip it, or start the
server with --disable-js to turn JS off entirely).
Idle sessions are evicted (default 30 minutes, tune with --session-ttl /
--session-cap); an evicted id then errors with session_expired and must be
re-created via session(action=new) — credentials are never carried over
silently. Errors follow a stable error [code]: message convention
(bad_input, session_expired, no_current_page, js_required,
not_configured, blocked (SSRF guard), cursor_expired, timeout,
fetch_failed…), and every tool carries MCP annotations (read-only vs
state-changing) so hosts can gate sensitive actions. JS render diagnostics
(framework, js_errors, article_fallback) are reported in read/interact
results rather than logged away.
Tool selection
Every tool the server advertises costs the model context on each turn, so unblink exposes only tools that can actually do something, and lets you narrow that further:
- Unusable tools are hidden automatically.
searchisn't exposed without a--search-provider, andinteract/requests/consolearen't exposed under--disable-js— a tool that could only return an error is noise in the tool list. - Pick a subset with
--tools. Pass a comma-separated list of tool names and/or presets. Presets:core(read,browse,find),read-only(every read-only tool — no sessions, navigation, or form/interaction writes), andfull(everything, the default).--disable-toolssubtracts from the set.
A tool named explicitly that can't run (e.g. --tools interact with --disable-js)
is dropped with a stderr warning; unknown names are ignored with a warning.
Authentication
Reach gated pages and JSON APIs by attaching credentials. Prefer a session — set them once and they persist across calls, out of every per-call payload:
Then any page tool using session: "api" carries the credentials. read/browse
also accept one-shot headers/auth for a quick stateless gated GET.
- Secrets stay out of the transcript. Give a secret literally (
token/password) or, better, by env-var name (token_env/password_env) — unblink reads the value from its own environment. Sessionstatereports only a redactedauth_type/auth_scope, never the secret; credential query params are masked in logs. - Origin-scoped, no cross-origin leak. Credentials are pinned to the
url's origin: they are sent only there and are stripped on any cross-origin redirect (including a same-domain port change), so a bearer token can't be exfiltrated to another host. Aurlis required whenever you supply auth/headers/cookies.
Search
The search tool is opt-in — unblink stays fully self-contained until you
point it at a provider, so it never reaches an external service by default:
Without --search-provider the search tool isn't exposed at all (a tool that
could only return "not configured" is context cost with no value — see
Tool selection). The API key is read only from UNBLINK_SEARCH_API_KEY, sent
only as a request header, and never logged. Search only queries the provider —
result URLs are fetched later by read/browse through the SSRF-guarded path.
The map tool needs no configuration: it discovers URLs from a site's own
sitemap.xml and same-origin links, bounded by max_urls/max_depth.
Networking
The HTTP layer decodes brotli/gzip/deflate, can rate-limit per host
(opt-in politeness limiter, --rate-limit; off by default so throughput is
bounded by the site, not by unblink), retries transient failures
(429/5xx, honoring Retry-After), and logs
(structured, to stderr — --log-level). --tls-mimic presents a browser
fingerprint to get past naive anti-bot blocks: a Chrome JA3/JA4 ClientHello (utls)
plus best-effort Chrome-tuned HTTP/2 SETTINGS and request headers (sec-ch-ua,
Sec-Fetch-*). Full h2 fingerprint fidelity (SETTINGS order, window sizes,
pseudo-header order) and Cloudflare/Turnstile remain out of scope. Tunable:
--rate-limit,
--rate-burst, --retries.
Every page fetch also runs behind an SSRF dial guard, returned content is fenced
as untrusted, and injected credentials are origin-scoped — all on by default and
covered in Safety: the content boundary. Page
JavaScript additionally cannot read host files (require is disabled) or read
HttpOnly cookies via document.cookie.
robots.txt and llms.txt are surfaced as context, never enforced — unblink
reports a host's crawl rules (and allowed_for_us for the path) but never blocks
a fetch on them. browse folds in lightweight presence hints (host-cached, so
repeat browses are free); --no-site-hints disables that probe while the site
tool stays available.
Extensions (ad-blocking)
unblink can load WebExtensions at runtime to extend what it does with
best-in-class third-party tooling — most usefully, ad/tracker blocking. Point
--extension at an unpacked extension directory or a .xpi/.crx/.zip archive
(repeatable), or --extensions-dir at a folder of them:
unblink ships no extension code — you supply it — which keeps GPL-licensed extensions (uBlock Origin is GPL-3) fully separate from unblink's MIT source, the same way a browser loads a user-installed add-on (see ADR 0010).
Recommended: uBlock Origin Lite (MV3) — verified working. Download
uBOLite_*.chromium.zip from
uBlockOrigin/uBOL-home releases
and point --extension at it. unblink compiles its declarativeNetRequest rulesets
(EasyList, EasyPrivacy, uBlock filters — 18,249 rules) into its own host matcher
and evaluates them directly, so it blocks real ad/tracker requests (adscore.com,
…) while passing first-party/benign traffic — a full render in ~0.1 s, with no
service worker or in-extension filter compilation. (Full uBlock Origin — the
.xpi/.zip on gorhill/uBlock — is MV2 and never gets its filter engine ready in a
pure-Go interpreter, so it is not viable in-process; Lite is the answer. Privacy
Badger loads and fully initializes too.) unblink currently supports:
-
Network filtering — an extension's
declarativeNetRequeststatic rules cancel page-JavaScript requests to blocked ad/tracker hosts before they leave the process (visible in therequeststool as blocked). -
Cosmetic filtering — element-hiding CSS (content-script stylesheets /
insertCSS) removes ad markup from the extracted Markdown (unblink has no CSSOM, so adisplay:nonerule becomes physical node removal). -
Content scripts — an extension's
content_scriptsJS/CSS is injected into matching pages, with achrome/browserAPI surface (runtime,i18n,storage,scripting,tabs, …). -
Background worker + messaging — the extension's background scripts run on their own event loop, and
chrome.runtime.sendMessageround-trips between a content script and the background (uBlock's model: a content script asks the background which selectors to hide for the current host, then hides them). -
Extension resources & storage —
fetch(chrome.runtime.getURL(...))serves packaged files (web_accessible_resources);chrome.storage.local/syncpersist to disk and fireonChanged;declarativeNetRequestdynamic/session rules added at runtime take effect. -
MV2
webRequest— a Manifest-V2 extension's background can block/redirect requests from a blockingwebRequest.onBeforeRequestlistener (full uBlock Origin's model).
Extensions require the JavaScript engine (they are rejected under --disable-js);
extension JS runs in the same sandbox as page JS (heap/byte/SSRF guards apply). Still in
progress toward a fully stock uBlock Origin build: isolated content-script worlds, scriptlet
injection (##+js), and an IndexedDB/cacheStorage shim.
Configuration
All configuration is via CLI flags (pass them in your MCP client's args).
--version prints the build and exits.
License
Security
unblink runs untrusted page content (and, with JS enabled — the default — untrusted page JavaScript) as part of its job. See SECURITY.md for the threat model and how to report a vulnerability.
Contributing
See CONTRIBUTING.md for the build/test workflow, the eval gate, and the ADR process. Changes to the JS engine or dependency pins follow the architecture doc and ADRs.
來源:README.md,提交 d8372b8
工具
0版本歷史
1- v0.24.0最新Sep 16, 2026
評分
已滿足適用檢查項的 50%
該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。
- 近 30 天內可連通25 分
請保持端點能夠回應 MCP 握手;SourceWeft 每 30 天探測一次。
- 已宣告認證方式10 分
請在註冊中心條目中宣告憑證請求標頭,或保持端點能夠回應探測,以便判定其認證方式。
- 已列出工具15 分
請讓伺服器在無憑證時回應 tools/list,或發布可在沙箱中無憑證啟動的軟體包。
- 描述必需15 分
- 圖示10 分
- 文件15 分
- 授權條款10 分