Inspiration

Invoice XML is boring until one malformed value stops a real workflow. Poland's FA(3) format is strict and large. Inspecting it by eye is difficult. An agent is useful when the fault is two placeholder values buried inside an official template. That does not mean the agent should quietly decide that the repaired document is acceptable. I built PrzedWysylka WebMCP Lab to test a narrower contract. The agent should do the mechanical work against the state already visible in the browser. The reviewer should see the source and proposed bytes. The same screen should show the validation result and final page control.

What it does

The app opens an official parameterized FA(3) template at Revision 0. The template is intentionally invalid. Its two placeholders fail canonical XSD validation. The page registers exactly six WebMCP tools:

  • get_workspace_status
  • list_official_assets
  • read_official_asset
  • select_official_asset
  • stage_exact_replacements
  • validate_workspace During the demo, the agent selects the template and validates Revision 0. It reads the relevant source lines, then stages two exact replacements. After validating the pending proposal, the page displays its diff and a SHA-256 proof bound to those proposed bytes. There is no WebMCP approval or rejection tool. The review buttons stay in the visible interface. In the demo workflow, I click Approve changes myself. The store creates Revision 1 and runs canonical validation again. An ordinary browser still gets the complete manual workbench. It also gets an honest WebMCP unavailable status instead of a polyfill pretending that the native API exists.

Why WebMCP fits this job

Without WebMCP, an agent would have to scrape diagnostics from the DOM and infer which file or revision is active. It would then scroll through XML and type into an editor. That is fragile. A remote MCP server would create another copy of state outside the review surface. Here, each tool calls the same asset registry and validator used by the UI. It also uses the page's replacement engine and workspace store. The agent receives bounded structured results. The reviewer watches the same state change in the page. WebMCP is the working interaction contract rather than a decorative endpoint. The missing capabilities matter too. Approval and rejection are not registered as WebMCP tools. Neither are upload, download or submission. The accurate claim is narrow: approval is a visible UI action and is absent from the WebMCP surface. The app does not claim to identify who operates the browser.

How I built it

The frontend uses React 19 with TypeScript 6 on Vite 8. It feature-detects document.modelContext and registers imperative tools through document.modelContext.registerTool(). The definitions have static descriptions and JSON input schemas. Callback inputs pass through Zod before they reach application state. The callbacks reuse existing application services instead of maintaining an agent-only model. Validation runs in the browser through libxml2 compiled to WebAssembly. The deployed app has no application backend or account system. It also has no analytics and no customer upload path. Every successful tool response is limited to 1,500 serialized characters. Catalog results are paged. Source cursors preserve the locked files' CRLF/LF/bare-CR delimiters. A caller cannot place a cursor inside a UTF-16 surrogate pair. Validation returns the complete finding count with compact summaries and explicit truncation metadata. Proposal proof is owned by the store. validate_workspace hashes the exact content snapshot it checked. The record includes the asset and proposal IDs plus the base revision. It also stores the document generation, validation result and proposed SHA-256. Approval fails closed if any part of that identity is stale.

Challenges

Registering six functions was the easy part. Keeping async work tied to the same browser state took most of the effort. Cancellation exposed a subtle bug. A dependency could ignore its AbortSignal and resolve late, after the caller had already cancelled. I had to invalidate pending work immediately and check state again after every awaited boundary. A cancelled validation can no longer persist a result or revive an obsolete proof. Byte fidelity caused a different class of failures. Many official files use CRLF, while some contain bare carriage returns. Normalizing line endings made a source excerpt look harmless but broke exact replacement. The reader now slices the original decoded text and returns a recoverable cursor without splitting newline pairs or Unicode surrogate pairs. Evidence also needed a hard boundary. The deterministic Playwright suite uses an injected, standards-shaped harness. That proves callback behavior but not native browser support. A separate production smoke uses Chrome's real document.modelContext; it records nativeModelContext: true and injectedHarness: false. The report does not pretend that an automated Playwright click was a human approval.

Verification

The native production smoke registers all six tools and executes every callback. It confirms that the WebMCP surface contains no approval tool. Every proof location resolves to the same value, from proposal preflight through the final applied draft: a41799c158deca1bca74e1a158a0748f401721b0ed4b5731ef1a38cfa087c3f0 The offline corpus gate checks all 55 locked first-party records. Browser WASM validation agrees with an independent xmllint gate on 28 expected-valid and 16 expected-invalid FA(3) records. The committed suite covers shared state and cancellation. It tests proposal gating and the visible approval path as separate boundaries. Other checks exercise the ordinary-browser fallback. Responsive layouts and Axe audits run too. The evidence files are committed under docs/assets/. They record the exact browser build and production artifact identity used for the smoke instead of relying on a screenshot label.

What I learned

WebMCP works best when a page exposes capabilities it already owns. Creating a parallel agent backend would have weakened this project because the reviewer and agent could disagree about selection or revision state. I also learned that small protocol details become product behavior very quickly. Output limits and cancellation are not cleanup tasks. Neither are cursor semantics or proof identity. If any one of them is vague, the approval screen can look trustworthy while referring to old or incomplete work.

What's next

The same approach could support other regulated schemas where an agent prepares a repair but the final review stays visible. User-provided documents would require a separate privacy design and threat model before I add an upload path. The six-tool competition surface will remain narrow.

Built With

Share this project:

Updates

Submission history