sessionat-sdk
v0.1.0
Published
TypeScript SDK to script and automate the Sessionat browser over its built-in MCP server — read your workspaces and 100% local visit analytics, drive tabs, and automate any page from Node. Requires the free Sessionat browser (https://sessionat.com).
Maintainers
Readme
@sessionat/sdk
Script and automate the Sessionat browser from Node & TypeScript.
Sessionat is a free, open-source, privacy-first browser with a built-in MCP server — so AI assistants and your own code can drive it. This SDK is the typed, ergonomic client for that server: read your workspaces and 100%-local visit analytics, open and control tabs, and automate any page — all over a local loopback connection that never leaves your machine.
🧩 Requires the Sessionat browser. Download the free app at sessionat.com, then enable the MCP server in
chrome://sessionat-mcp/.
import { Sessionat } from "@sessionat/sdk";
const browser = await Sessionat.connect(); // zero-config auto-discovery
// Your private, local analytics — no approval needed:
const top = await browser.analytics.topSites({ range: "7d", orderBy: "active_seconds" });
console.log(top.top_sites); // [{ host, visit_count, active_seconds, category }]
// Drive the browser (one-time approval in chrome://sessionat-mcp/):
await browser.tabs.open("https://news.ycombinator.com");
await browser.page.waitFor({ text: "Hacker News" });
const shot = await browser.page.screenshot(); // { data: base64, mimeType: "image/png" }Install
npm i @sessionat/sdk
# or the unscoped alias: npm i sessionat-sdk- Zero runtime dependencies. Uses the built-in
fetch(Node 18+). - Ships ESM + CommonJS + full TypeScript types.
Why
Sessionat is built around three pillars — Arc-style workspaces, a local private analytics dashboard, and a built-in MCP server. The MCP server already lets Claude Desktop, Cursor, Codex, Claude Code, Windsurf and VS Code connect with one click. This SDK gives you the same surface from code:
- 📊 Query your own data — top sites, focus categories, time-of-day buckets, full visit history — all stored locally, nothing uploaded.
- 🗂️ Manage workspaces — list, inspect, and create Arc-style workspaces.
- 🤖 Automate the live browser — open/close/focus tabs, navigate, read page text, click, type, scroll, wait, screenshot, even cross-origin iframes and canvas apps.
Learn more about the browser at sessionat.com.
How connect() finds your browser
When Sessionat is running with MCP enabled it writes a small discovery file (mcp.json) into its profile directory containing the loopback port and a bearer token. Sessionat.connect() finds it automatically — scanning the default profile locations and health-probing each candidate so it picks the live one.
You can also be explicit:
// Explicit endpoint (copy from chrome://sessionat-mcp/ → "Other" tab):
await Sessionat.connect({ url: "http://127.0.0.1:54321/mcp", token: "…" });
// Point at a specific profile or discovery file:
await Sessionat.connect({ profileDir: "/path/to/Sessionat/Default" });
await Sessionat.connect({ discoveryFile: "/path/to/mcp.json" });Or via environment variables (great for CI / scripts):
| Variable | Purpose |
|---|---|
| SESSIONAT_MCP_URL + SESSIONAT_MCP_TOKEN | Use this exact endpoint, skip discovery. |
| SESSIONAT_MCP_DISCOVERY | Path to an mcp.json discovery file. |
| SESSIONAT_PROFILE_DIR | A profile dir; <dir>/mcp.json is read. |
Write tools need one-time approval
Reading analytics is open. But every write / automation tool (opening tabs, clicking, typing, even listing open tabs) is gated behind a one-time, per-client approval — Sessionat's deliberate guard against prompt-injection. The first such call throws:
import { SessionatWriteApprovalError } from "@sessionat/sdk";
try {
await browser.tabs.open("https://example.com");
} catch (err) {
if (err instanceof SessionatWriteApprovalError) {
// → Open chrome://sessionat-mcp/ in Sessionat, enable write tools, retry.
console.error(err.message);
}
}API
Connection: Sessionat.connect(options?) → Sessionat. Properties: serverInfo, url, port, source.
browser.workspaces
| Method | Tool | Notes |
|---|---|---|
| list() | sessionat_list_workspaces | all workspaces + active id |
| active() | sessionat_get_active_workspace | active workspace + its tabs |
| get(workspaceId) | sessionat_get_workspace | one workspace by id |
| create({ name, color?, icon? }) | sessionat_create_workspace | write |
browser.analytics
| Method | Tool |
|---|---|
| topSites({ range?, workspaceId?, limit?, orderBy? }) | sessionat_get_top_sites |
| categories({ range?, workspaceId? }) | sessionat_get_category_breakdown |
| buckets({ bucket, range?, workspaceId? }) | sessionat_get_visit_buckets |
browser.visits
| Method | Tool |
|---|---|
| list({ range?, workspaceId?, limit? }) | sessionat_get_visits |
| search(query, { range?, workspaceId?, limit? }) | sessionat_search_visits |
| forHost(host, { range?, workspaceId?, limit? }) | sessionat_get_visits_for_host |
range is "today" | "7d" | "30d" | "all". orderBy is "visits" | "active_seconds". bucket is "hour" | "day_of_week" | "day".
browser.tabs (all write-gated)
| Method | Tool |
|---|---|
| list() | sessionat_list_open_tabs |
| active() | sessionat_get_active_tab |
| open(url, { background? }) | sessionat_open_url |
| focus(index) | sessionat_focus_tab |
| close(index) | sessionat_close_tab |
browser.page (all write-gated)
| Method | Tool |
|---|---|
| navigate(url) | sessionat_navigate_active_tab |
| text({ maxChars?, rootSelector?, iframeSelector?, frameUrlMatch? }) | sessionat_get_page_text |
| click({ selector?, text?, textExact?, frameUrlMatch? }) | sessionat_click |
| type({ selector?, fieldLabel?, text, submit?, frameUrlMatch? }) | sessionat_type |
| waitFor({ selector?, text?, textExact?, timeoutMs? }) | sessionat_wait_for |
| scroll({ selector?, dx?, dy?, position? }) | sessionat_scroll |
| outline({ limit?, rootSelector?, frameUrlMatch? }) | sessionat_get_dom_outline |
| screenshot({ maxWidth? }) → { data, mimeType } | sessionat_screenshot |
| frames() | sessionat_list_frames |
| pressKey(key, { modifiers? }) | sessionat_press_key |
| typeKeys(text) | sessionat_press_key (text mode) |
click and outline pierce shadow DOM (Reddit, Slack, Polymer). frameUrlMatch drives cross-origin iframes (Stripe, OAuth, embeds). typeKeys sends real trusted key events for canvas apps (Google Docs, Figma).
Low-level escape hatch
const tools = await browser.listTools(); // raw tool definitions
const result = await browser.callTool("sessionat_get_top_sites", { range: "30d" });
const raw = await browser.request("ping"); // any JSON-RPC methodErrors
All extend SessionatError:
SessionatConnectionError— browser not found / unreachable.SessionatAuthError— bearer token rejected (HTTP 401).SessionatWriteApprovalError— write tool used before approval (.toolName,.clientId).SessionatToolError— a tool ran but returned an error (.toolName).SessionatRpcError— JSON-RPC protocol error (.code,.data).
Examples
See examples/: quickstart.mjs (read analytics) and automate-search.mjs (drive a page).
Privacy
The connection is loopback-only (127.0.0.1), bearer-token authenticated, and your visit data never leaves your device — that's a core product guarantee of Sessionat. This SDK makes no outbound network calls of its own beyond the local browser.
Links
- 🌐 Browser & download: sessionat.com
- 📦 Source: github.com/dublyo/sessionat
- 🧠 Model Context Protocol: modelcontextprotocol.io
License
MIT © Sessionat
