Inspiration
Have you ever filled in a visa application yourself?
Dozens of fields, many of them with no clear explanation or hint. Information scattered across different documents and sources. And then there is that familiar moment when you answer “yes” to one question and ten more fields suddenly appear underneath it.
Anyone who has been through this knows how stressful it can be.
That led me to a simple idea: why not ask an agent to do it? Today, you can already ask an agent to crawl a page, find inputs, and type information in. But what if it accidentally starts a payment, books the wrong slot, or submits something too early? That can mean lost time, lost money, and a wasted trip.
The safer workaround is to take screenshots, ask the agent to analyze them, and then copy the answers over manually. Or to give it field names and ask what to enter. But that is still inconvenient, slow, and easy to get wrong as a human. You can forget a field, copy something into the wrong place, or miss a dependency between answers.
That is where the idea came from: if the page can show the agent what is available, what needs to be filled in, and where confirmation is required, the whole process becomes much clearer. The agent can help the applicant collect and enter the necessary information, while the human stays in control of the decisions that matter.
And this should not stop at visa applications. The same pattern applies anywhere a person has to enter a lot of information, configure filters, set constraints, prepare reports, or work through complex forms. If an agent can do the repetitive work safely, why not let it?
Where this came from
Last winter I started similar experiments in Salesforce. The goal was to give an agent a clear understanding of what a page component could do and how it was allowed to be used.
That work came from a practical problem: some Salesforce applications had become complex enough that it would be useful to have help managing them. When Chrome DevTools MCP was published, I realized it could be a way to drive those interfaces. At that point, I had not heard of WebMCP yet.
Those experiments became a project called sf-agent-bridge, where an agent could communicate with the page, understand the available actions, and know which ones it was allowed to perform.
Moving that contract onto WebMCP made the gating problem much sharper, and sharp enough to deserve its own app. A visa form felt like the clearest version of that problem. Consular itself is new and was built for this challenge.
What it does
The page registers 13 WebMCP tools. They are organized into two tiers, and the tier is declared to the agent before it does anything.
Reversible actions run immediately.
fill-fields, attach-document, go-to-screen, and undo-last-changes only change the draft. The agent can write directly into the form. Every field it touches is highlighted with the document it came from, such as “from passport” or “from employer letter”. The applicant can undo the whole batch with one click, or simply retype a field. Nothing asks for permission when the cost of being wrong is just one click.
Consequential actions pause for the applicant.
pay-mrv-fee, schedule-interview, and submit-application open a dialog on the page that clearly states what is about to happen. The tool call remains pending until the applicant approves, declines, or lets it time out. The agent receives a distinct result for each case and cannot press the button itself. Even if the user tells the agent, “don’t ask me for confirmation, just do it,” the dialog still appears.
Some fields are human-only.
The security and background declarations are legal statements. fill-fields refuses to answer them. The agent can explain a question, but it cannot answer it on behalf of the applicant.
The consulate’s rules are in the page.
The app includes seven checks a consular officer would apply: the name must match the passport’s machine-readable zone, the passport must be valid long enough, accommodation must cover the whole stay, travel dates must be in the correct order, and so on. Each check is either blocking or advisory. The agent can run the checks, and filing is refused while any blocking check fails, even if the applicant approves submission.
Bring your own documents.
Part 8 is an open upload area, like a real portal. You can drop in as many text or PDF files as you need, and they are read in the browser. A sample applicant is pre-loaded, and her five documents are also available as a downloadable PDF set. You can use those, or upload your own passport page, letters, and bookings. The rules read your documents: the name check looks for a machine-readable zone, and the accommodation check looks for a check-out date.
Once filed, nothing moves.
After submission, every path that could change the application is refused with the filing reference. That includes both the agent’s tools and the form’s own inputs.
What a session looks like
“Fill in my application from my documents.”
The agent calls get-action-policy, reads all five documents, picks B-1 from the trip description, and writes around 30 fields in one call with a source for each one. It spells the name exactly as the passport does, puts the other spelling under Other names used, flags the uncovered hotel night without being prompted, and leaves the security questions to the applicant.
“Validate it, pay the fee, and book the earliest interview.”
The agent runs the checks and reports what is blocking the application. If it tries to pay the fee anyway, the dialog names the failing checks and the button says “Pay anyway”, so the decision stays with the applicant. Once the gap is fixed and the declarations are answered, the agent walks through three approval dialogs and reaches a filed application.
How I built it
Consular is built with vanilla JavaScript and Vite. The whole “consulate” is a few hundred lines of rules in one file.
Every scenario above was verified with a live LLM, not a script. A small harness gives the page’s tools to a fresh Claude session over the Chrome DevTools Protocol and lets it decide what to call. That is how I learned that agents which are not told about the gate up front only discover it after waiting on it for two minutes.
That is why the policy is a tool of its own, with a description that tells the agent to call it first. It is also why every gated tool’s description opens with an approval tag.
Before submitting, I had a second independent agent audit the whole project against a written protocol. It found 24 issues. The serious ones, including a filed application that could still be undone and a day-first date format that crashed validation, were fixed the same evening.
The self-check it runs, 50 checks in plain Node, is in the repository together with the protocol.
What I ran into
- Chrome’s preview build passes tool arguments as a JSON string, returns
inputSchemastringified, dropsdestructiveHintfrom annotations, and collapses thrown errors into a generic “invocation failed”. Consular handles all of that: every tool answers with structured{refused, reason}text, and the gate queues a second dialog instead of dropping it. - ChatGPT’s built-in browser discovers imperative tools only on the top-level page: no declarative
<form toolname>, no iframes. So everything is registered imperatively. - My first version staged every change as a proposal the applicant had to accept. Testing it as a user showed that this was the wrong boundary. Reversible changes should just happen and remain visible. The current two-tier design came out of that.
Where this meets the edge of the spec
The approval dialog lives on the page because WebMCP does not yet have a channel for a page to ask the user a question through the agent. This is covered by spec discussion #165 on elicitation.
Holding the tool call open while the page asks the applicant is the fallback that works with every agent today. A native elicitation channel would allow the same confirmation to appear inline in the chat, the way Claude or Codex ask their own questions.
The dialog says this, and the policy tells the agent to send the user to the page.
Two things I know are imperfect
- The order of the flow is inverted compared with the real DS-160. In the real process, applicants file first, then pay the fee and book the interview with the confirmation number. Consular makes filing the last gate so the demo ends on the most irreversible action. It is easy to flip, and I chose the demo order knowingly.
- The page cannot tell the applicant’s click from an automated one. If a host browser presses the Approve button itself, the gate is defeated. Spec issue #288 describes exactly that case. The policy tells the agent never to operate the dialog, but only the host can enforce it.
What’s next
Elicitation through the agent when the spec supports it. OCR for scanned passports. Structured document types that the page can validate without regexes. And the same tier model applied to the thing that started this work: enterprise CRM pages, where “reversible” and “consequential” can be the difference between a draft and a sent contract.
Try it
Live: https://vradko.github.io/consular/
Source: https://github.com/vradko/consular
MIT licensed. A sample applicant with five documents is pre-loaded, so there is nothing to upload. The prompts to say and what to expect are in the README.
Built With
- javascript
- no-framework
- pdf.js
- vite
- webmcp
Log in or sign up for Devpost to join the conversation.