Rosetta
Turn the traffic of an undocumented web system into a verified, safety-gated MCP server, and get an honest report of what could not be automated.
Web systems without API documentation are difficult to automate. Browser requests depend on hidden CSRF tokens, rotating nonces, and record-version headers that have to be traced and handled by hand. Rosetta turns a HAR capture of that traffic into MCP tools, tests them against a sandbox, repairs supported failures, and reports the actions that still need human work.
HAR capture ──▶ data-flow graph ──▶ MCP tools ──▶ verify ⇄ repair loop ──▶ deployable server + report
(what the (which value (plumbing (tested against a (and a list of what
browser did) came from where) hidden) sandbox, regression- needs a human)
gated)
What it does on the demo system
The demo target (demo/portal.py) is a deliberately awkward "treasury ops" portal with seven real-world quirks: a login CSRF field hidden in HTML, a session CSRF header, an ETag precondition, single-use rotating nonces, a strict decimal-string format, rate limiting, and a request signature computed by front-end JavaScript.
| Requests in the capture | 9 |
| Raw endpoints found | 9 |
| Tools the agent sees | 5 (plus 1 quarantined) |
| Token bindings hidden from the agent | 7 (CSRF, ETag, nonce) |
| Verification, first generation | 5 / 12 cases pass |
| Verification, after the repair loop | 10 / 10 cases pass (2 iterations, ~4.5 s, 97 upstream calls) |
| Repairs applied automatically | 3: retry on 429, "latest value" tracking for single-use nonces, decimal-string coercion |
| Escalated to a human | 1: create_payout, whose X-Sig header is not derivable from traffic |
10/10 is over the tools that remain enabled. The quarantined tool is counted as unresolved, not as a pass. Three consecutive runs gave identical numbers. The first-pass figure depends on timing (rate limiting), so expect it to vary a little on other machines; the loop's outcome does not.
The agent-facing result, from a real MCP client:
agent sees 5 tools: list_payouts, get_payout, approve_payout, get_ledger, cancel_payout
approve_payout (no confirm) -> POST /api/payouts/P-1002/approve [preview only]
approve_payout(P-1001, 1250, confirm=true) -> approved # a number, coerced to "1250.00"
approve_payout(P-1002, 3400.0, confirm=true) -> approved # nonce rotated correctly between calls
create_payout -> tool create_payout is quarantined: header 'x-sig' is high-entropy, has no source ...
How it works
flowchart TD
s0("ingest") --> s1("analyze") --> s2["synthesize"]
s2 -->|verify| s3["verify_repair"] --> s4["emit"]
s2 -->|skip| s4
1. Data-flow graph. For every value in every request, Rosetta looks for the earlier response it was copied from. Those links form a graph between endpoints (below for the demo). Echoes (a response repeating the id you just sent) are ignored. Business fields such as amounts are never auto-linked, only plumbing-shaped values are.
flowchart LR
login("GET /login") -->|"_csrf"| post_login{{"POST /login"}}
session("GET /api/session") -->|"csrf, x-nonce"| approve["POST …/approve"]
list["GET /api/payouts"] -.->|"choose an id"| detail["GET …/{payout_id}"]
detail -->|"etag → if-match"| approve
approve -->|"x-nonce"| create["POST /api/payouts (needs human)"]
create -->|"x-nonce"| cancel["POST …/cancel"]
2. Tools, not endpoints. The graph collapses plumbing: approve_payout(payout_id, approved_amount) fetches the CSRF token, the record's current ETag and the nonce itself. One tool per business action rather than one per HTTP call, which Speakeasy's lessons from 50 production servers flag as a problem. A planner rule keeps this safe: a state-changing endpoint is never run automatically as a prerequisite.
3. Verify and repair, with a contract. The first generation is deliberately optimistic. It is then tested against a sandbox with cases from the capture (replay), from live data (the same tool on a different record), and agent-style inputs (numbers where the capture had numeric strings). Failures are diagnosed from evidence, and a proposer suggests a typed patch:
| Role | Who | Authority |
|---|---|---|
| Evaluator | plain code (verify.py) |
defines "pass"; cannot be overridden |
| Proposer | heuristics, or Claude for what the rules cannot classify | proposes patches from a fixed, validated schema |
| Gate | loop.regression_gate |
keeps a patch only if nothing that passed now fails and the pass count rises |
| Stop | converged · no proposals · no progress · budget · iteration cap | whatever is still failing is escalated to a human, with evidence |
4. Ship. ir.json (secret-free), a stdio MCP server.py, a Dockerfile, an MCP client config snippet, the data-flow graph, the loop trace, and REPORT.md.
One finding worth stating plainly: replaying the captured
create_payoutrequest passes, because the capture's exact bytes carry a valid signature. Only a request with different content exposes that the signature cannot be reproduced. A replay-only test suite would have shipped a tool that fails on every real use. Rosetta therefore mixes in different inputs.
Safety model
- State-changing tools return a preview unless
confirm=true; nothing is sent upstream on a preview. - Outbound requests are limited to allow-listed hosts, including across redirects.
- Captured secrets never enter the IR, the report, the trace or tool output. The emit step scans every output file against the capture's secrets and fails the build on a match. Credentials come from environment variables at run time.
- Write tools are only exercised by the verifier against a sandbox (loopback, or explicitly declared).
- Every call is appended to an audit log (
ROSETTA_AUDIT_LOG) with the tool, status and a hash of the arguments. - Upstream error text is treated as untrusted data, including when it is shown to a model.
Run it
pip install -e ".[dev]"
python -m demo.run_demo # portal + capture + build + agent session, ~6 s
pytest # 69 tests, ~8 s
# on your own capture (Chrome DevTools: Network → Save all as HAR)
export ROSETTA_USERNAME=... ROSETTA_PASSWORD=...
rosetta build capture.har --name myapp --target https://sandbox.example.com \
--reset-url https://sandbox.example.com/__reset --allow-writes --sandbox
rosetta serve out/ir.json # stdio MCP server
rosetta graph capture.har # data-flow graph as Mermaid
--brain claude lets Claude propose repairs for failures the rules cannot classify (needs ANTHROPIC_API_KEY or an ant auth login profile).
Limitations, stated plainly
- Developed and measured against one purpose-built target. The heuristics are real, but how they fare on messy third-party systems is unproven.
- Capture is HAR in, JSON and form bodies only. No multipart, GraphQL, MFA or OAuth flows. The recorder in
rosetta.captureis for scripted sessions; a Playwright recorder for live sites is not included. - Provenance is value matching. A value that is transformed before reuse (beyond simple prefixes like
Bearer) is not linked. - Path templating is pattern-based (numeric, UUID,
P-1001-style ids). Slugs are not detected. - Verifying write tools needs a resettable sandbox. Without one, writes are reported as unverified.
ClaudeBrainis tested against a stub client (request shape, constraint handling, degradation). It has not been run against the live API.- Signatures computed in client-side JavaScript are escalated, not solved. Reading the JS bundle is the obvious next step.
Layout
src/rosetta/ har · templating · schema · dataflow (provenance graph) · synth · ir · runtime
verify · diagnose · repair · probe · brain · claude_brain · loop
stategraph · pipeline · emit · report · server · cli
demo/ portal (target), make_capture, run_demo
tests/ analysis · synthesis · runtime safety · loop contract · Claude brain · state graph · e2e · MCP
docs/ ARCHITECTURE.md
See docs/ARCHITECTURE.md for the design decisions and algorithms.
Related work
Integuru builds a request dependency graph from a HAR and generates code for a goal; tools like har-mcp and FastMCP's OpenAPI import turn traffic and specs into MCP servers. Rosetta's emphasis is what comes after generation: testing the result against a sandbox, repairing it under a regression gate, gating writes, and saying clearly what it could not do.
MIT licensed.