Edgewright

Guide and tutorials

Work the rules with an agent.

Edgewright turns your redirect and rewrite rules into a live console a human and an AI agent work the same table, over WebMCP. Test any URL, catch the rule that shadows another, translate a config to another host, then prove nothing breaks, all in the browser.

Console

Edit rules, test a URL, audit the set, translate between hosts. Open

Preflight

Paste a config and the routes that must work, get one pass or fail for CI. Open

Studio

Migrate a config to another host and prove every URL still resolves. Open

All three run on one engine, @edgewright/core. Everything runs client-side. There is no server, no account and no data leaves the page, so the in-page tool surface is the only way an agent acts on this state. The tools an agent calls and the buttons you click run identical code through one shared store.

What you need: a browser with WebMCP

Open any of the three apps in a browser that speaks WebMCP:

  1. ChatGPT's in-app browser, or
  2. Google Chrome 149+ with chrome://flags/#enable-webmcp-testing set to Enabled, then relaunch.

The status pill in the top bar reads WebMCP connected once the page has registered its tools. You do not need an agent to use Edgewright: every tool has a matching button, so the whole app works by hand. The agent path and the button path go through the same store.

Tutorial 1: fix a shadowed redirect by hand (Console)

The Console loads with a real bug already in place. It has four rules. The first that matches a path wins:

/shop/*          ->  /products/:splat         301
/shop/sale-2026  ->  /promotions/summer-sale  301
/blog/*          ->  /news/:splat             301
/app/*           ->  /app/index.html          200

/shop/sale-2026 should reach the sale, but the broad /shop/* above it matches first, so the specific rule below never runs.

  1. In the Request simulator, type /shop/sale-2026 and press Test. The trace shows it hits rule #1 and lands on /products/sale-2026, which does not exist.
  2. Press Run audit. It names the problem: Rule #2 (/shop/sale-2026): Shadowed by rule #1 (/shop/*), which already matches /shop/sale-2026.
  3. Drag rule #2 above rule #1 or press its up arrow. Order is the fix.
  4. Press Test again. It now lands on /promotions/summer-sale. Run the audit once more and it reads clean.
  5. Press Export for the corrected config, ready to paste back into your project.

Tutorial 2: let an agent fix it (Console, over WebMCP)

Open the Console in a WebMCP browser and point an agent at the page:

My /shop/sale-2026 link 404s in production. Test it, find out why and fix it.

The agent works the same rules through the WebMCP tools. A verified run calls, in order:

  1. test_url {"url":"/shop/sale-2026"} (read-only): the request lands on /products/sale-2026.
  2. audit_rules (read-only): rule #2 shadowed by rule #1 (/shop/*).
  3. reorder_rule {"from":2,"to":1}: moves the specific rule up. This tool changes rules, so it has no readOnlyHint and the agent confirms before running it.
  4. test_url again: now 301 redirect -> /promotions/summer-sale. Fixed.
  5. export_config (read-only): the clean config to paste back.

You watch every call and its real output on the same page. Not a chatbot in a side panel, but an agent operating the console you are looking at.

Tutorial 3: gate a config before it ships (Preflight)

Preflight answers one question: does this config still route every URL that must keep working? It is built to run in CI or for an agent to run over WebMCP.

  1. Open Preflight and press Load sample. It loads the four-rule config and two expected routes: /shop/sale-2026 -> /promotions/summer-sale and /blog/post-1 -> /news/post-1.
  2. Press Run preflight. The verdict is FAIL, 1 of 2 routes failed, because /shop/sale-2026 landed on /products/sale-2026 expected /promotions/summer-sale.
  3. The result comes with a PR comment block and a JSON artifact, so a CI step or an agent can post the reason and set an exit code.

Over WebMCP an agent runs the same gate: load_config with the config, set_expectations with the routes, run_gate for the verdict, then export_report {"format":"markdown"} for the PR comment. Fix the order in the Console, reload here and the gate turns green.

Tutorial 4: migrate hosts and prove nothing breaks (Studio)

Studio moves a config from one host to another and proves the move is safe by replaying every URL through both.

  1. Open Studio and press Sample to load a Netlify config, with Vercel as the target.
  2. Press Migrate. It translates the rules into vercel.json, keeps the redirect model and the statuses, then notes anything that cannot carry across.
  3. Press Verify equivalence. It replays the source URLs through both configs and reports EQUIVALENT. 5/5 URLs resolve identically. Safe to ship. If any URL resolved differently it would name it.

Over WebMCP: load_source, set_target, migrate, then verify_migration. The verdict is the same one the button produces, because both call the one engine.

Tool reference

Each app registers its own tool catalog on document.modelContext. Read-only tools carry readOnlyHint, so an agent runs them without a confirmation prompt; tools that change state omit it, so the agent asks first.

Console (15 tools)

ToolRead-onlyArgumentsWhat it does
test_urlyesurl (+ country, language, role, cookies)Trace a path: winning rule, status, destination, loop detection.
audit_rulesyesnoneFlag shadowed rules, unreachable rules, loops and ignored lines.
export_configyesnoneSerialize the rules to the active platform's native format.
translateyestargetTranslate to netlify, vercel, cloudflare or nextjs, with loss notes.
bulk_testyesurls[], sitemapTest many paths at once (or a sitemap.xml body), failures first.
test_pathyespattern, urlCompile one pattern and report the match and captures.
explain_ruleyespositionExplain the rule at a 1-based position in plain English.
list_platformsyesnoneList the platforms Edgewright can parse, test and export.
audit_headersyesplatform, text (+ path)Audit response headers, flag weak security headers and CSP.
add_rulenosource, destination (+ status)Append a redirect (301/302/307/308) or rewrite (200).
edit_rulenoposition (+ source, destination, status)Change a rule at a 1-based position.
remove_rulenopositionDelete the rule at a 1-based position.
reorder_rulenofrom, toMove a rule. Order is authoritative, so this fixes a shadowed rule.
import_confignotextReplace all rules from a pasted config.
set_platformnoplatformSwitch platform and translate the current rules into it.

Preflight (5 tools)

ToolRead-onlyArgumentsWhat it does
load_confignotext (+ platform)Load a redirect config into Preflight.
set_expectationsnoexpectations[] of {path, destination?, status?}Set the routes that must keep working.
run_gateyesnoneResolve every expected route, audit the rules, return one pass or fail.
explain_failureyesnoneExplain why the last gate failed, route by route.
export_reportyesformat (markdown or json)Export the last result as a PR comment or a CI artifact.

Studio (6 tools)

ToolRead-onlyArgumentsWhat it does
load_sourcenotext (+ platform)Load the source config to migrate.
set_targetnoplatformSet the platform to migrate to.
migratenononeTranslate the source config to the target, with notes on anything lost.
verify_migrationyesnoneReplay every source URL through both configs and report equivalence.
audit_sourceyesnoneAudit the source for shadowed rules, unreachable rules and loops.
export_reportyesformat (markdown or config)Export the migration as a report or the target config.

For developers

The engine is plain ES modules with no build and no dependencies, so the code the apps ship is the code the tests exercise. It runs the same in the browser and in Node.

npm test        # 148 tests: fixtures, export round-trips, audit and loop cases
npm run serve   # serve the static apps at http://localhost:8080

Chrome's testing surface is document.modelContext: getTools() returns the registered tools and executeTool(tool, args) runs one. Two things bite: executeTool wants the tool object from getTools() rather than its name. The arguments are passed as a JSON string, not an object.

const tools = await document.modelContext.getTools();
const testUrl = tools.find((t) => t.name === 'test_url');
const trace = await document.modelContext.executeTool(
  testUrl,
  JSON.stringify({ url: '/shop/sale-2026' }),  // args are a JSON string
);

The full guide, the architecture and the end-to-end tests that drive all this against the live site live in the repo: GUIDE.md and e2e/.

Live: Console · Preflight · Studio. Source and an MIT license: github.com/zkasuran/edgewright. Built for the OpenAI WebMCP Challenge.