Why your use case is a strong fit for WebMCP

I built Steward because token approvals are how wallets get drained quietly. You approve a contract once, forget it, and years later it can still move your tokens. Auditing that is exactly the kind of tedious, high-stakes work people want to hand to an agent. But nobody should hand an agent their keys.

That constraint is what makes this a WebMCP problem rather than an MCP problem. A server-side tool can read the same public chain data. It can never finish the job, because the signature has to happen where the wallet lives: in the browser, on the page. With WebMCP the agent can scan, explain, and stage a fix, and then the wallet's own signing popup takes over. Neither the page nor the agent can draw that popup, click it, or skip it. stage_revoke returns awaiting_user_confirmation, and everything between that string and the confirm button in your wallet is the product.

Also I love the idea of the agent researching the market and assesing tokens with proposing "swap" transactions for the user, all the user has to do is "sign" or "approve" the transaction.

How it creates a better user experience

Today, checking your approvals means digging through explorer logs, decoding allowances, and guessing whether an unknown spender contract is safe. Most people never do it. With Steward, "is my wallet safe?" is a conversation.

The agent calls scan_approvals on any address or ENS name, no wallet connected. It walks the worst results with explain_approval, where every point in the risk score is listed, so there is no black box. It can ask discover_tokens what is moving on Base, run assess_token on anything it finds (GoPlus contract checks, DexScreener liquidity, a Honeypot.is sell simulation, and it refuses to give a verdict on fewer than two sources), check check_gas, and then stage a revoke, a transfer, or a KyberSwap-quoted swap into one review queue. If the token it wants to buy is rated high-risk, the page refuses the swap until the agent has told you why and you have said yes.

You see a plain-language summary and the exact calldata, and you sign only what you accept. If you have no wallet extension at all, you still get the full audit on any pasted address. That was a deliberate choice for judges opening this in a browser without MetaMask.

What people and agents can do together that was difficult or impossible before

An agent can now work on a live wallet, research a token's exit safety across three sources, and prepare real transactions, with zero delegated authority.

The current industry answer to agents with money is delegation: session keys, agent wallets, spending caps. Those bound the damage, but a prompt-injected agent still complies within its budget, and the security literature treats that as an open problem. Steward gives the agent nothing to delegate. No keys, no session key, no policy to write, no server enclave. The agent does the reading across thousands of on-chain events. The page checks every claim with a live allowance() read, because Approval events can be forged by any contract. You spend your attention on one cryptographic yes or no.

It also assumes the chain is hostile. Token names are attacker-controlled text, so every name and symbol is normalised, stripped of hidden characters, and wrapped in ⟦UNTRUSTED⟧ fences before an agent reads it. The demo wallet contains a token whose name is a prompt injection, with a zero-width space hiding the word "instructions" from keyword filters. Steward catches it, fences it, flags it, and scores the approval critical.

Briefly explain how you implemented WebMCP

Eight tools are registered with document.modelContext.registerTool() through a small React hook I wrote, useStewardTool (GoogleChromeLabs' use-webmcp-tool was the prior art for its shape). The hook is where the rules live, so no tool can forget them: the standard MCP result envelope, a roughly 1.5K-character output budget measured across the whole envelope, and an automatic quarantine notice on every tool annotated untrustedContentHint. Read tools carry readOnlyHint. The three write tools (stage_revoke, stage_transfer, stage_swap) do not, and they can only stage into the review queue. Execution requires the user's wallet.

Registration is AbortController-managed and survives React StrictMode. Browsers without WebMCP get a local fallback registry that says plainly that no agent is present. I verified against native Chrome (--enable-features=WebMCPTesting, Chrome 151 and 152) with a CDP harness in the repo, scripts/verify-native-webmcp.ts, which checks that the real ModelContext holds all eight tools and can execute them. There are 51 tests around the quarantine, the swap gate, and the agent-facing formatting.

Challenges I ran into

  • Approval events are forgeable, so logs are only leads. Every candidate pair is checked with a live Multicall3 allowance() read, and only nonzero allowances survive. For vitalik.eth that is 2,293 candidate pairs from logs and 2,191 still live.
  • Keyless data sources drift fast. Base's Blockscout went from usable to a ten-request budget during the week, so Base scanning moved to bounded eth_getLogs windows on the public RPC, and every failure shows as "live scan unavailable" instead of a blank page.
  • I found a caching bug while staging the demo wallet: I set an allowance on-chain, confirmed it, and production kept serving the empty scan from before. Empty results were cached as long as full ones. They now expire in a minute.
  • The first swap gate treated a missing risk verdict as clean. That is the wrong default for money. It now fails closed, and the gate is a pure function with its own tests.
  • An adversarial review found two ways past the quarantine: fullwidth confusable characters that folded into a clean attack after normalisation, and keywords split by invisible characters. Detection now runs on the folded and stripped text as well as the raw bytes.

What I learned

Prompt injection through on-chain metadata is one token deploy away, not hypothetical. Setting untrustedContentHint is necessary but not enough; the page has to fence the bytes itself so the protection holds in an agent that ignores hints. And the safest consent design is the one with nothing to delegate: when the only signing surface is the user's own wallet popup, there is no policy for a steered agent to comply within.

Honesty notes

The demo address 0x1111…1111 is a fixture, hostile token included, and the UI labels it as demo data, so the headline demo survives any third-party outage. Every other address is scanned live. Claude Code helped me research, build, review, and write, and every commit it touched says so. The name, the thesis, the scope cuts, and every signature were mine. All code was written inside the submission period (first commit 2026-08-26). MIT licensed.

Built With

Share this project:

Updates

Submission history