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:
- ChatGPT's in-app browser, or
- Google Chrome 149+ with
chrome://flags/#enable-webmcp-testingset 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.
- In the Request simulator, type
/shop/sale-2026and press Test. The trace shows it hits rule #1 and lands on/products/sale-2026, which does not exist. - Press Run audit. It names the problem: Rule #2 (/shop/sale-2026): Shadowed by rule #1 (/shop/*), which already matches /shop/sale-2026.
- Drag rule #2 above rule #1 or press its up arrow. Order is the fix.
- Press Test again. It now lands on
/promotions/summer-sale. Run the audit once more and it reads clean. - 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:
test_url{"url":"/shop/sale-2026"}(read-only): the request lands on/products/sale-2026.audit_rules(read-only): rule #2 shadowed by rule #1 (/shop/*).reorder_rule{"from":2,"to":1}: moves the specific rule up. This tool changes rules, so it has noreadOnlyHintand the agent confirms before running it.test_urlagain: now301 redirect -> /promotions/summer-sale. Fixed.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.
- Open Preflight and press Load sample. It loads the four-rule config and two expected routes:
/shop/sale-2026 -> /promotions/summer-saleand/blog/post-1 -> /news/post-1. - Press Run preflight. The verdict is FAIL, 1 of 2 routes failed, because
/shop/sale-2026landed on/products/sale-2026expected/promotions/summer-sale. - 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.
- Open Studio and press Sample to load a Netlify config, with Vercel as the target.
- Press Migrate. It translates the rules into
vercel.json, keeps the redirect model and the statuses, then notes anything that cannot carry across. - 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)
| Tool | Read-only | Arguments | What it does |
|---|---|---|---|
test_url | yes | url (+ country, language, role, cookies) | Trace a path: winning rule, status, destination, loop detection. |
audit_rules | yes | none | Flag shadowed rules, unreachable rules, loops and ignored lines. |
export_config | yes | none | Serialize the rules to the active platform's native format. |
translate | yes | target | Translate to netlify, vercel, cloudflare or nextjs, with loss notes. |
bulk_test | yes | urls[], sitemap | Test many paths at once (or a sitemap.xml body), failures first. |
test_path | yes | pattern, url | Compile one pattern and report the match and captures. |
explain_rule | yes | position | Explain the rule at a 1-based position in plain English. |
list_platforms | yes | none | List the platforms Edgewright can parse, test and export. |
audit_headers | yes | platform, text (+ path) | Audit response headers, flag weak security headers and CSP. |
add_rule | no | source, destination (+ status) | Append a redirect (301/302/307/308) or rewrite (200). |
edit_rule | no | position (+ source, destination, status) | Change a rule at a 1-based position. |
remove_rule | no | position | Delete the rule at a 1-based position. |
reorder_rule | no | from, to | Move a rule. Order is authoritative, so this fixes a shadowed rule. |
import_config | no | text | Replace all rules from a pasted config. |
set_platform | no | platform | Switch platform and translate the current rules into it. |
Preflight (5 tools)
| Tool | Read-only | Arguments | What it does |
|---|---|---|---|
load_config | no | text (+ platform) | Load a redirect config into Preflight. |
set_expectations | no | expectations[] of {path, destination?, status?} | Set the routes that must keep working. |
run_gate | yes | none | Resolve every expected route, audit the rules, return one pass or fail. |
explain_failure | yes | none | Explain why the last gate failed, route by route. |
export_report | yes | format (markdown or json) | Export the last result as a PR comment or a CI artifact. |
Studio (6 tools)
| Tool | Read-only | Arguments | What it does |
|---|---|---|---|
load_source | no | text (+ platform) | Load the source config to migrate. |
set_target | no | platform | Set the platform to migrate to. |
migrate | no | none | Translate the source config to the target, with notes on anything lost. |
verify_migration | yes | none | Replay every source URL through both configs and report equivalence. |
audit_source | yes | none | Audit the source for shadowed rules, unreachable rules and loops. |
export_report | yes | format (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.