Repository navigation
v0.30.0
v0.30.0 — browser tabs, custom apps, ranked tool search
The browser toolset moves from one page per user to one Chromium per user with as many tabs as the work needs. Two agents, or one agent with subagents, each open their own tab and stop stepping on each other. search_tools now understands how agents phrase a task. Workbench also stops being limited to its built-in plugins. A user can point it at any MCP server as a custom app, and its tools appear beside the native ones. Ships as a release candidate first: it touches the routing path behind CLUSTER_ENABLED, adds a table, and changes the /mcp request path.
Features
- Browser: tabs.
browser_startnow opens a new tab and returns itssession_id; every other tool acts on that tab.browser_tabslists open tabs,browser_closecloses one and leaves the browser up. Tabs share the profile and cookie jar, so a login in one is visible in the others. Cap per user:BROWSER_TAB_LIMIT(default 8). - Browser: routing key derived from the bearer. The replica-affinity header is set by the server from the authenticated user; the agent no longer carries a routing key in
session_id. See the finding. - Custom apps: register any MCP server. On the Apps page, New custom app takes a name and an MCP server URL. Workbench discovers the server's OAuth metadata, registers itself as a client (dynamic client registration), and runs the OAuth 2.1 flow per user. The server's tools are exposed as
appName__toolnext to the native plugins, throughsearch_toolsandexecute_tools. Custom apps are per user: nobody else sees or can connect to an app you add. Streamable HTTP is used with an SSE fallback for older servers; sessions are cached, and tool calls get a 120s timeout (10 min ceiling with progress). See the finding. - Custom apps: connect on create. The dialog's button reads Connect. It registers the app and goes straight into its OAuth, showing Registering… then Connecting… while the dialog stays up, so the human no longer has to find the new app and press Connect. The trigger is now a square
+button in the Apps toolbar, with a tooltip. - MCP:
search_toolsranks by word, forgives typos. Search was a literal substring match on the whole query. On the built-in catalog, 10 of 12 common agent phrasings (create jira issue,send email,list github pull requests, …) returned[], and hits came back unranked and unlimited. Now the query and every tool's name, integration and description are split into words and stemmed. A word matches exactly, by prefix, or through a small synonym list (ticket→ issue,email→ gmail,pull request↔pr). A word no tool uses is first corrected to the nearest one that does (emial→ email). Name matches outrank integration matches, which outrank description matches, and rare words count more. All 12 phrasings, and five misspelt ones, now put the right tool first. Built-in and custom-app tools are ranked together. Each row gains ascore, the top 10 come back by default, and a new optionallimitgoes up to 50. It costs about 0.5 MB of cached word lists and 1–3 ms per query. See the finding. - MCP:
curl_sessiontoken lifetime. Callers can passexpiresInSeconds, an integer from 60 to 3600 (default 900). The cap bounds how long a leaked token stays live, since there is no revoke. The response'sexpiresInechoes the lifetime minted. - Portal: one-time link page in one column. The create form is down to one field; the expiry moved into a compact popover beside Create link. Once minted, the link takes the value's place with Copy beside it. Home gets a vault quick-access card.
- Portal: copy the MCP endpoint from Home. The Connect your agent card has an icon-only copy button beside the URL.
Fixes
- REST:
POST /rest/browseris now routed to the owning replica. It ran wherever it landed, which underCLUSTER_ENABLEDcould start a second Chromium on the shared profile. - Custom apps: a custom app can no longer loop back to workbench. This workbench's own
/mcppassed the SSRF guard (its public URL is not private), so it could be added as a custom app. Its tools are then the meta-tools, andexecute_toolscould call itself through it. Adding one is now refused when the server's protected-resource metadata names this instance, which also catches another hostname, a CNAME or an IP. At call time every outbound custom-app request carriesX-Workbench-Via, the chain of instances it has passed through./mcpanswers508 Loop Detectedwhen the chain already names this instance, which also catches A → B → A across two deployments, or when the chain reaches 4 hops. Apps registered before this release are covered by the call-time check. See the finding. - Vault: one-time links survive chat link previews. Pasting a one-time link into Slack spent it, because the redeem was a
GETand Slack's unfurl was that firstGET. Teams, Discord, iMessage and mail scanners do the same. OnlyPOST /api/vault/otl/:tokenspends the token now. A browserGETgets a no-script page with a Download secret button, and any otherGETgets405 Allow: POST. The link's preview card reads "Open once, then it burns." See the finding. - Vault: the one-time link card now renders in Slack. The page served its
og:tags only whenAcceptcontainedtext/html. Slack's unfurler sendsAccept: */*, so it got the 405 and chat showed no card. Known link-preview bots (Slack, Twitter/X, Facebook, Discord, Telegram, WhatsApp, LinkedIn, Teams, …) are now served the page byUser-Agent. This only picks page vs 405, and noGETspends the token. Verified against real Slack through a tunnelled build. - Google Drive: Shared Drive files and folders are reachable. Drive v3 hides Shared Drive items unless each call opts in. A Shared Drive id 404'd as "File not found" and listing a Shared Drive folder returned
[]. Everygoogle-drivetool now sendssupportsAllDrives=true, and list and search also sendincludeItemsFromAllDrives=true. No scope change, no reconnect. See the finding.
Compatibility
- Callers of one-time links must
POST.curl "$URL"now fails with a 405; usecurl -X POST "$URL". - A
session_idminted by a pre-v0.30browser_startkeeps working for this release and drives the default tab. The fallback is removed in v0.31. BAD_SESSION_KEYis gone; the new errors areBROWSER_TAB_NOT_FOUNDandBROWSER_TAB_LIMIT.browser_live_urlno longer takessession_id.- The portal live view shows the default tab.
- Schema: a
custom_appstable is created on boot (SQLite and PostgreSQL). It is additive; rolling back leaves it unused. - bcryptjs 2 → 3. New API-key hashes are written as
$2b$instead of$2a$. Existing$2a$hashes still verify, and bcryptjs 2.4.3 reads$2b$, so existing keys keep working and a rollback does not lock anyone out. search_toolsreturns at most 10 tools by default. Before, it returned every match. An agent that enumerated a whole plugin with one keyword ("gitlab") should passlimit: 50. Rows gain ascorefield; clients that ignore unknown fields are unaffected.- Toolchain: TypeScript 7,
@types/node26. The MCP SDK, Fastify and the portal's React Query/Router get patch updates.
Commits since v0.30.0-rc.1
feat(mcp): correct misspelt search words before matching them(0594ce0) — rc.4feat(mcp): rank search_tools results by word, with typos and synonyms(80651cf) — rc.4fix(vault): show the one-time link card to Slack and other unfurlers(b10f424) — rc.3fix(custom-apps): refuse a custom app that loops back to workbench(47f6e81)feat(portal): copy button beside the MCP endpoint on Home(7f245d9)feat(portal): keep the custom app modal up while it connects(c48afa2)feat(portal): connect a custom app as soon as it is created(54010fa)feat(portal): icon-only New custom app button with a tooltip(234c6c7)fix(vault): shorten one-time link card copy(43e4950)feat(mcp): let curl_session callers choose the token lifetime(4736e4b)fix(google-drive): reach Shared Drive files and folders(44db919)feat(portal): one-time link page in one column; vault quick access on Home(9c3c038)fix(vault): one-time links survive chat link previews; redeem is POST(8cc58dd)feat: register external MCP servers as custom apps (#107)(00a52d2)chore(deps): bump bcryptjs and @types/bcryptjs(9ef563d), plus TypeScript 7,@types/node26 and grouped dependency/actions updates
Full diff: v0.27.0...v0.30.0