Driving JBrowse Web from a browser agent
JBrowse Web publishes three things on window, unconditionally and in
production: window.JBrowseSession is the live session model,
window.JBrowseRootModel the root model above it, and window.jb the helper
library.
An agent that can run JavaScript in the page, such as a browser extension, already has everything it needs. Nothing has to be installed and no server is involved, because the page it is reading is the app.
This is the same jb library JBrowse Desktop serves over
MCP, reached from inside the page instead of over a socket.
What is the same
The library, and the way a session goes. Orient first, because a person can
click between calls. Find track ids in the catalog rather than guessing them,
build views from the same spec JSON that &session=spec- URLs take
(URL query parameter API), read the data as live Feature objects and aggregate in
code, restyle a shown track in place and read back what landed, then wait for
drawing before looking:
jb.sessionSummary()
jb.listTracks('clinvar')
jb.loadSessionSpec({
views: [
{
type: 'LinearGenomeView',
assembly: 'hg38',
loc: 'chr17:7,668,421-7,687,490',
tracks: ['hg38-ncbiRefSeqCurated', 'hg38-clinvarMain'],
},
],
})
jb.getFeatures({ trackId: 'hg38-clinvarMain' })
jb.trackModel('hg38-clinvarMain').applyDisplaySettings({ height: 220 })
jb.waitReady(30000)
The traps are the same too, and each renders as a plausible looking browser with
something quietly missing: a data file spelling reference names differently from
the assembly answers nothing (jb.getFeatures renames for you); an unknown
settings key is dropped (jb.describeSlots lists what a display accepts); a
track over its fetch size limit raises no error and is reported only under
notReady by jb.waitReady; and jb.loadSessionSpec replaces the session, so
re-read it through jb.session. The full reference is
Driving the live JBrowse session and Recipes for driving JBrowse from an agent is the same library at
work, one ask per section.
What is different
There is no Node, so there are no local files. A browser cannot run
samtools, build an index, or read a path. jb.addTrack takes a URL and refuses
a local path rather than adding a track that cannot read. Work that needs a real
file belongs in JBrowse Desktop or in a shell, and
Hosted genomes and tracks for agents covers what is already loadable with no setup at
all.
The data host has to allow the request. A tab is subject to CORS where an Electron app is not, so a file that loads in Desktop may be unreachable from a page. This is a property of the host serving the data, not of JBrowse.
A read runs on the thread that draws. jb.getFeatures uses the main thread
adapter deliberately, because the alternative serializes every feature across
the worker boundary before any limit can apply. The page therefore stops
repainting while a large read runs. Aggregate in code and return the answer
rather than the features.
A read has a size gate, like a display does. A display refuses to fetch over
its own limit and paints the reason. jb.getFeatures asks the same index-only
question first and throws rather than returning a short answer that looks like a
whole one:
region too large for jb.getFeatures: the largest region is ~8200000 bytes
against a limit of 5000000. Narrow the region, or pass an explicit byteLimit
if you mean to pull this much.
Narrow the region, or raise the ceiling for a read you mean to be big by passing
byteLimit alongside trackId and loc.
jb.require needs waking first. It serves the pinned ABI module names
external plugins link against, and that registry is loaded lazily because
installing it eagerly would put most of the UI toolkit into every page load.
Call await jb.ensureRequire() once before the first jb.require(...).
A session that already loaded a runtime plugin has the registry installed already, so this costs nothing there.
jb.loadSessionSpec replaces the session. In Desktop that is the same as
opening a file. In a browser it also rewrites the URL and stores a new session,
and the session it replaced is not recoverable from the page. Prefer adding to
the open session where that will do.
From the Claude in Chrome extension
The extension's JavaScript tool evaluates in the page's own world, so
window.jb is simply there. A session that opened hg38 at a gene, added a
four-sample GEO bigWig set as one stacked track, derived a ratio track from it
and audited a zoom used nothing but jb, the live session and the page's own
fetch. What the extension changes is the calling convention:
- The value is the last expression. The examples on the MCP page are
function bodies. In the extension, end the snippet with the value, or wrap the
body in
(async () => { ... })(). - One evaluation has a fixed time budget, about 45 seconds, and the code
keeps running when it expires.
jb.loadSessionSpecsettles the new session before it answers, which on a cold hosted config can outlive the budget. Call it on its own, and readjb.sessionSummary()on the next call. - Results are sanitized on the way back. Nested objects are cut off past a
few levels, long strings are clipped, and a string that looks like base64 is
replaced. Return flat, pre-sliced values, or a
JSON.stringifyof what you need. - Its screenshot knows nothing about rendering. Call
jb.waitReady()first, then screenshot, and readnotReadyfrom the settle result for the tracks a picture cannot show as missing. - Wait for the page. The app assigns
window.jbafter its first render, so poll for it after navigating.
Anything jb.addTrack cannot express, such as several files under one
MultiWiggleAdapter, is a hand-written config through
session.addSessionTrackConf, the same as in Desktop.
Is this safe
window.jb grants no privilege the page did not already have. It runs at the
page's own origin with the user's own session, and every byte of it is in the
bundle regardless. Anything it does, script on that origin could already do
through window.JBrowseSession.
What it does change is how easy the app is to drive on purpose, and that cuts both ways. A hostile track description, or a hostile page in another tab, can try to instruct an agent that is reading it, and a convenient library makes an injected instruction more likely to succeed. That risk arrives with the agent rather than with this library, and the mitigation is the same as for any code executing agent tool: your client's approval prompts, and not pointing an agent with write access at data you do not trust.