Install
$ agentstack add mcp-spliffspliff70-wq-websense-mcp ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
WebSense MCP
[](https://github.com/spliffspliff70-wq/websense-mcp/releases/latest) [](LICENSE) [](extension/manifest.json)
> Non-vision, AI-native web automation via the Semantic Action Graph. No screenshots, no CDP, no bot detection.
Quick Start
git clone https://github.com/spliffspliff70-wq/websense-mcp && cd websense-mcp && npm install- Load the extension:
chrome://extensions→ Developer mode → Load unpacked → select
extension/. It auto-connects to the WebSocket hub on port 38401 (no launcher page).
- Register the MCP server in your client (stdio, or
--httpfor the streamable-HTTP transport):
{ "mcpServers": { "websense": { "command": "node", "args": ["src/server.js"] } } }
- Call any tool (e.g.
explore_page). Flow:MCP → WS → Extension → Content Script → DOM.
Bridge port: default 38401. Override with --port (server) and the matching PORT constant in extension/offscreen.js (extension). If the port is already taken, the hub logs a warning and the server keeps running (MCP still works) — the bridge just isn't claimed. Run multiple isolated servers with different --port values.
> Chrome-only. WebSense is built, tested and supported on Chrome / Chromium (MV3, offscreen > WS bridge). It has never been developed or tested on Firefox, and there is no Firefox code in > this repo. Do not expect it to work there.
Architecture
MCP Client (Claude / Cline / Cursor / Hermes)
↔ stdio
WebSense MCP Server (src/server.js)
↔ WebSocket localhost:38401
Chrome Extension (extension/)
├── background.js service worker, tab management
├── offscreen.js WebSocket client, auto-reconnect
└── websense-cs.js SAG extraction + native DOM interaction (CSP-safe)
↔ chrome.runtime.sendMessage
Live DOM
Tools (31) — call websense_guide first
> Count verified 2026-09-21 by tools/list against the RUNNING server > (POST http://127.0.0.1:9222/mcp, streamable HTTP JSON-RPC): 31 tools. > The same 31 reg(server, …) names are in src/server.js. Anything in these > docs that says 20/21/29/43/61 tools is stale.
Guide & Status
websense_guide · status (kind:page|bridge|doctor|downloads)
Exploration
explore_page (compact:list, intent:find, goal:goal-filter, preload:lazy, incremental:delta-since-last-scan)
Page Map (lossless, addressable)
page_snapshot (LOSSLESS inventory of the page held server-side; returns only the small INDEX — counts + sliceable dimensions. Nothing is cut: not interactive-only, not in-viewport-only, and it is scroll-stable) · page_slice (fetch ONE slice at full fidelity: tag|role|region|vp|interactive|query — every record carries a usable locator)
Read
read (format:text|content|markdown|diff|scrollextract|preload)
Interact
click (ref|xy, mode:click|hover|rightclick|drag) · type_text (ref+text|fields:[]) · form (state|select|toggle|upload) · scroll (direction|y|intoView) · press_key
Element Intel
reveal (kind:dropdown|tabs|accordion) · inspect (kind:element|geometry|relation)
Tabs & Navigation
navigate · tabs (list|switch|close|bind|frames|windows|focus|move|transfer|switchread)
Wait
wait (conditions ANDed | event mode)
Page Control
evaluate (script|query) · main_world (compiled fn in the page MAIN world — F12-insider path for reads/writes the isolated world can't do) · screenshot · dialog (accept|dismiss|keystroke)
Session & Network
session (reset|map|mermaid) · network_log · console_log (captured console + JS errors) · cookies (list/get/set/clear metadata — never values) · clipboard (copy|read)
AX Bridge
ax (state|read|click|type) — for canvas SPAs & chrome:// pages
REAL Input (genuine OS-level, for synthetic-ignoring widgets)
real_activate_tab (UIA tab-pill click) · real_click (SendInput at viewport x,y) · real_paste (OS click + clipboard + Ctrl+V)
Extension maintenance
respawn_offscreen (recreate the offscreen doc so current on-disk code loads) · extension_reload (chrome.runtime.reload + reconnect wait)
> Each tool absorbed 2-10 old one-verb tools. Full absorption table in websense_guide. All 65 original capabilities are callable — just through the consolidated tool with a mode/format/action/kind parameter instead of a separate tool name.
Dialog handling
- JS dialogs (
alert/confirm/prompt) are captured, including the ones the page itself
raises — a MAIN-world hook shadows the three functions (the content script's isolated-world copy was never called by page code, so dialogs used to go unseen and Chrome auto-dismissed them in a background tab). status lists them in pendingDialogs (waiting) and recentDialogs (already fired, with the outcome). Resolve with dialog action:"accept"|"dismiss" (+ value for prompt); the answer really reaches the page's promise. Confirm/prompt auto-answer after 30s so a page can never wedge.
- DOM modals (
[role=dialog], most in-app modals) are closed by ref;status.hasModal/
dialogCount come from a visibility-blind scan (a hidden modal still counts).
- OS-level dialogs (HTTP basic-auth, proxy-auth, print): can't be intercepted by JS.
dialog keystroke:true key:"enter"|"escape"injects a global keystroke through Windows control (PowerShellSendKeys). This is the windows-control bridge. - File picker: handled by
form action:"upload"(DataTransfer API) — no OS dialog.
Iframes / frames (the other gap vs. a human — now closed)
tabs action:"frames"returns every frame in the target tab (passtabId; omit it and you
get your bound tab) with its frameId and URL.
- Pass
frameIdto any element tool (explore_page({frameId}),click({ref, frameId}),type_text({ref, frameId}), …) to target a specific iframe. This unlocks Gmail compose, Notion, Figma, and any site that renders key UI inside child frames. read(format:"text") and element labels now include CSS::before/::aftercontent (icon-font glyphs, counters) thatinnerTextmisses.
Cross-browser
- Chrome / Edge / Opera: load
extension/manifest.json(MV3, offscreen WS bridge).
Key Features
- CSP-Safe (30/31 tools): native DOM functions in the content script's isolated world. No eval, no string-to-code. Works on LinkedIn, GitHub, Google — any strict-CSP site. (
evaluateis the only eval-based tool — and itsscriptmode is blocked by the extension's own MV3 CSP on every page, so use itsquerymode ormain_world;dialog keystroke:trueis a Windows-control keystroke, andmain_worlduses Chrome's userScripts MAIN-world path which is CSP-proof by design.) - React-Compatible: native prototype value setters bypass React's value tracker, then
input/changeevents are dispatched. - No Bot Detection: real Chrome profile, cookies, fingerprint. No CDP, no
navigator.webdriver, no headless. - No Vision: all structured JSON; no screenshots, no vision model.
- Action-Typed Elements: every element classified by action type with predicted effects.
- Exploration Graph: persistent navigation map with Mermaid export.
- Frame-Aware: targets iframes via
frameId; no DOM region is unreachable.
Token cost — measured, not estimated
Real payloads from the three sites this tool is used on most (2026-09-25):
| call | x.com | Reddit | GitHub | |---|---|---|---| | explore_page{full:true} | 183 KB / 726 ms | 147 KB / 132 ms | 79 KB / 34 ms | | page_snapshot (lossless index) | 1.8 KB / 147 ms | 1.8 KB / 81 ms | 1.5 KB / 33 ms | | page_slice (25 records) | 8.4 KB | 8.1 KB | 7.8 KB | | explore_page{intent:"…"} | ~4 KB / 29 ms | ~4 KB / 30 ms | ~4 KB / 4 ms |
So the index is 53-102x smaller than a full explore, and intent is cheaper still. Start with intent when you know what you want, page_snapshot + page_slice when you need to map the page, and reserve full:true for small pages (forms, settings) where you genuinely want every action. A single full:true on a large SPA is the most expensive call in the toolset.
Background by default
Page ops never take the foreground. The complete list of things that do:
real_activate_tab,real_click,real_paste— OS-input by design.tabs action:"focus"andaction:"move".- One automatic case: if the target tab's Chrome window is minimized or collapsed,
its viewport is 0×0 and every read comes back empty, so WebSense restores that window to read the page and then reports windowRestored: true with a note saying why. It never raises a window that is merely in the background or occluded.
- Things with no background path at all: native file dialogs (OS open/save), OS-level
print dialogs, and attaching a real file to a composer via a custom dropzone — that attaches a realm-local File that never uploads, so use real_paste.
Known limitations
dialogcannot answer a native dialog raised while the tab is hidden — Chrome auto-dismisses
those before the page can be reached. A MAIN-world hook records them either way (status.recentDialogs), so you can see that one fired, but the answer comes from the hook's auto-resolution rather than from you. Activate the tab first if the answer matters.
evaluatescript mode runs your JS and returns its value. The isolated-world path
(new Function) is blocked by the extension's own MV3 CSP, so it transparently re-routes through the MAIN world (chrome.userScripts, no eval) and reports via:"main_world". This works on every page — it is not a strict-site limitation. evaluate{query:{…}} is still the lighter path for plain DOM reads.
- Refs are stable:
E#refs are assigned in viewport order on the first scan and then held by
element identity (per-element cache + a data-websense-ref attribute), so they survive re-explores, scrolls, and framework re-renders. Measured: 41/41 unchanged across a full re-explore, 0 changed after a scroll, 0 after a re-render, and a stale ref healed onto a replacement node with the same label and no id/class. A ref only dies when its element leaves the DOM with nothing to heal from. CSS-selector refs (#id) are still the safest choice for anything long-lived or across navigations.
- One profile, per-tab isolation: concurrent jobs share one Chrome profile — there is no
cookie/storage isolation between them, so scope work with tabs action:"bind" + explicit tabId. Session state (map/history) is per-session: session action:"reset" clears only your own history, and one job's steps never appear in another's map.
- Logged-in sites (LinkedIn etc.): must already be authenticated in that Chrome profile;
navigateopens a fresh tab that needs an existing session cookie. - Canvas/WebGL content (Telegram web, TradingView, chrome:// pages): use
ax action:"read"to see the native accessibility tree, thenax action:"click"|"type"to interact. Fallback:screenshot+ vision. axtool uses chrome.debugger — stable Chrome compatible, shows a warning banner while attached. Requires explicit tabId.
v1.4.5 (2026-09-25) — twelve defects, three of them false promises
- Truth fixes: the action
effectverdict wasunverifiablefor every relayed action (it never
unwrapped the relay envelope); it no longer recommends a real OS click for a merely unmeasurable action; and the DELTA block no longer claims mutated:false means the action failed (it only means no interactive-element fingerprint changed).
- Now working (previously never did):
network_logcaptures real page traffic (MAIN-world
hook), wait{selector} succeeds, reveal kind:"dropdown" resolves, status kind:"doctor" reports a live service worker, navigate/tabs frames honor tabId, batch type_text counts are honest, extension_reload really reloads, and respawn_offscreen stops reporting false failures.
- Privacy: password/OTP values are masked on every surface that can echo them.
- Docs: the in-tool guide,
MODEL_PROMPT.md(now generated from the guide, with a test that
fails on drift) and this README all state the measured behavior. See CHANGELOG.md for the full list.
v2.1-latchproof (2026-08-15)
- Multi-slot concurrency (
hub.js): request correlator is nowMap— concurrent sessions no longer clobber each other. - Latch-proof routing (
background.js):chrome.tabs.onActivated/onRemovedevents keep the tab registry live; 0×0 viewport self-heal. - Honest interaction (
websense-cs.js):type_textverify-persist — reportsconfirmed/revertedinstead of phantom success. - AX bridge (
offscreen.js):axtool (state/read/click/type) viachrome.debugger(CDP Accessibility domain). Stable Chrome compatible, no dev-channel flags. - windows-control DPI-aware (
uia_common.py): physical→logical coordinate scaling fixes clicks on scaled displays.
Testing
# Regression suite (127 tests: hub, delta, guide-truth guards — no Chrome needed)
npm test
# Full end-to-end live test (needs Chrome + extension loaded)
node test/mcp-client-test.js
# Keep MODEL_PROMPT.md in sync with the in-tool guide (runs inside npm test)
node tools/export-guide.mjs --check
File Structure
websense/
├── package.json
├── src/
│ ├── server.js # MCP server with 31 consolidated tools
│ ├── hub.js # WebSocket hub
│ ├── session.js # Exploration map + diff engine
│ └── mermaid.js # Mermaid export
├── extension/
│ ├── manifest.json # Chrome MV3
│ ├── background.js # Service worker (tab mgmt, offscreen lifecycle)
│ ├── offscreen.js # WebSocket client (auto-reconnect)
│ ├── offscreen.html
│ └── websense-cs.js # SAG extraction + native DOM interaction
└── test/
└── mcp-client-test.js # End-to-end MCP client test
Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: spliffspliff70-wq
- Source: spliffspliff70-wq/websense-mcp
- License: MIT
- Homepage: https://github.com/spliffspliff70-wq/websense-mcp#readme
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.