tron automate: Obscura-first fetch router, and the hosted OpenMCP relay at tronbrowser.dev/mcp/tron - #97
Merged
Merged
Conversation
…sted OpenMCP relay at /mcp/tron `tron automate` is one MCP server with an engine switch: Obscura (its own renderer + V8, tens of ms and ~30 MB on a light page) renders first, and the Chromium session takes over for JS-heavy pages, bot walls and logins, where a warm Chromium is 3-10x faster. Measured on the same pages before deciding: Obscura wins cold start and tiny pages, loses badly on GitHub/Wikipedia. SDK (packages/sdk/src/mcp): - obscura.ts: MCP client driving `obscura mcp` over stdio. Stdio on purpose: Obscura's HTTP transport stalls every request after the first on a keep-alive connection by ~4 s. - automate.ts: fetch_page (markdown|text|links|html, engine=auto|obscura| chromium, budget, wall/empty detection with fallback) + engine_status; an in-page markdown walker for the Chromium path. - chromium.ts: DirectChromium spawns a Chromium binary itself (headless, throwaway profile, kernel-picked DevTools port, isolated tab per call, idle shutdown, private-host resolver rules) for servers and boxes without the launcher. - http.ts: Streamable-HTTP transport (plain JSON, batches, 202 for notifications, optional bearer) + the OpenMCP descriptor at /.well-known/openmcp.json. - automate-bin.ts: `tron automate [mcp|serve|fetch|status]`; `tron mcp` gains fetch_page too. CLI/installer (apps/web/public/install.sh): `tron automate` case; the installer downloads the pinned Obscura release (two ~100 MB binaries, so not in the tarball) next to the launcher on install and upgrade, `ensure-obscura` subcommand, TB_NO_OBSCURA_INSTALL / TRONBROWSER_OBSCURA_VERSION. Hosted relay (services/api/src/mcp): tronbrowser.dev/mcp/tron, keyless and stateless (fetch_page + screenshot_page only), fresh tab per call, public targets only (name and resolved address), 3 concurrent, 30/min per caller, 1 MiB body, 400k chars, 45 s Obscura budget. Static descriptor at /.well-known/openmcp.json lists it on openmcp.logicsrc.com once registered. Dockerfile moves to bookworm with Caddy copied in, builds the API as a pnpm workspace member, and bakes in ungoogled-chromium 152.0.7977.82-1 (portable Linux build) and Obscura 0.2.2. Caddy proxies /mcp/* to the API. Tests: router (fallback on wall/error/timeout/missing, pinned engines, formats, truncation), Obscura client, HTTP transport + descriptor, DirectChromium (live half skips without a browser), CLI heredoc, installer hooks, relay guard (private ranges, DNS rebinding, rate limiter) and routes. Smoke script: services/api/scripts/mcp-smoke.mjs. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018WGZpo8on6XLGYnccXV3dP
| info "Installed Obscura $OBSCURA_VERSION to $obdest" | ||
| return 0 | ||
| fi | ||
| warn "Couldn't install Obscura; 'tron automate' will use Chromium for every page. Retry with: curl -fsSL $INSTALL_URL | sh -s -- ensure-obscura" |
| expect(src.startsWith('(() => {')).toBe(true); | ||
| expect(src.trimEnd().endsWith('})()')).toBe(true); | ||
| // It must parse as JavaScript; a syntax slip here breaks every Chromium markdown fetch. | ||
| expect(() => new Function('return ' + src)).not.toThrow(); |
ThreatCrush Security Scan51 finding(s) MEDIUM: 32 | LOW: 19
…and 1 more. Full results in the Security tab. Snippets are redacted; ThreatCrush never prints matched credential material. |
…VE_TEST=1) GitHub runners carry a Chrome that cannot start under the default sandbox, so 'binary present' picked the live half up in CI and it timed out. The live test now runs only when asked for. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018WGZpo8on6XLGYnccXV3dP
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
One MCP server with an engine switch, local and hosted.
Local:
tron automate. Obscura renders first (its own engine + V8: tens of milliseconds and ~30 MB on a light page); the Chromium session takes over for JS-heavy pages, bot walls and logins, where a warm Chromium is 3 to 10x faster than Obscura. Measured on the same pages before building (Obscura 63 ms on example.com vs Chrome 774 ms cold; Chrome 1.1 s on a GitHub page vs Obscura 17 s).tron automate(stdio MCP),tron automate serve(HTTP + OpenMCP descriptor),tron automate fetch <url>,tron automate statusfetch_page(markdown|text|links|html, engine=auto|obscura|chromium, budget, wall/empty detection) andengine_status;tron mcpgainsfetch_pagetooensure-obscura,TB_NO_OBSCURA_INSTALL,TRONBROWSER_OBSCURA_VERSIONDirectChromium: the SDK can spawn a Chromium binary itself (server, container, box without the launcher)Hosted:
tronbrowser.dev/mcp/tron. The same router in the API as a public OpenMCP relay, keyless and stateless:fetch_page+screenshot_pageonly, a fresh tab per call, public targets only (name and resolved address), 3 concurrent, 30/min per caller, 1 MiB body, 400k chars, 45 s Obscura budget. Descriptor at/.well-known/openmcp.json(static, served by Caddy, so the catalog marks it verified).The Dockerfile moves to bookworm with Caddy copied in, builds the API as a pnpm workspace member, and bakes in ungoogled-chromium 152.0.7977.82-1 (portable Linux build) and Obscura 0.2.2. Caddy proxies
/mcp/*to the API.Why stdio for Obscura
Obscura's HTTP MCP transport answers the first request on a connection in under 60 ms and then stalls every later request on that keep-alive connection by ~4 s. Over stdio there is no stall. (The house relays that talk to Obscura over HTTP may be paying this per tool call; separate follow-up.)
Tests
pnpm lint,pnpm typecheck,pnpm testall green (sdk 50, api 79, web 20). Live checks on the dev box:tron automate fetchthrough both engines,tron automate servehandshake + descriptor, andservices/api/scripts/mcp-smoke.mjsagainst the relay (Obscura fetch, Chromium fetch, links, private target refused, PNG screenshot, no tab left open).After merge
https://tronbrowser.dev/mcp/tronandhttps://tronbrowser.dev/.well-known/openmcp.json.openmcp add https://tronbrowser.dev(orPOST https://openmcp.logicsrc.com/v1/relays {"url":"https://tronbrowser.dev"}).tron automatereach users.🤖 Generated with Claude Code
https://claude.ai/code/session_018WGZpo8on6XLGYnccXV3dP