Widgets for agents, designed with one
Inspiration
widgentic started from a simple observation: agents are good at fetching data and bad at showing it. Every MCP tool result was a wall of JSON or a paragraph of prose. So we built widgets for agents: a payload contract { kind, data, hints?, meta? }, a built-in catalog (card, table and tree), a template DSL that is data rather than code, and an MCP server that renders any of it inline in claude.ai, Claude Desktop, VS Code Copilot Chat and ChatGPT.
Then we noticed the two authors never met. An agent could read the authoring guide and draft a perfect widget definition, but it had no way to put it anywhere. A person had to copy the JSON, paste it into the designer, fix it, and save. We had made that separation a rule on purpose: there is no agent write path. API keys travel into prompt-injectable hosts, so they only read. People save.
WebMCP lets us keep the rule and close the gap. A page registers tools; the agent in the person's own browser calls them, under the person's session, while the person watches. The designer the person is looking at becomes the agent's tool. The agent drafts, the person saves (or tells the agent to), and every agent with a key renders the result.
What we built
The product, before the challenge
- Three open-source npm packages under
@widgentic:core(contract, adapters, mapper, catalog, theming, template DSL, actions, in-place reactive rendering),designer(embeddable widget, theme, schema and action designers, also as custom elements),mcp(the tool-output convention, server assembly, per-principal store, secrets). Zero runtime dependencies; MIT. Docs at docs.widgentic.dev, source at github.com/widgentic/widgentic. - An MCP server with eight tools (
list_widgets,list_schemas,list_actions,list_themes,list_theme_tokens,get_authoring_guide,render_widget,execute_action) whose widgets mount inline through MCP Apps and update in place as data changes. The host matrix records what each host does. - widgentic.dev: sign in, design widgets, themes, shared data schemas, actions and secrets, mint read-only API keys, and see every save on the next tool call. Actions are declared as data:
promptproposes a message,httpruns server-side behind an SSRF guard with secrets injected by name and never shown again. - A self-host example: one container image, the authoring app plus the MCP endpoint over a single SQLite file.
For the challenge
@widgentic/webmcp(beta, published with provenance): the mounted designers as fourteen WebMCP tools. Read the draft, check a definition without touching it, load a definition, set example data, set the preview theme, load or merge a theme entry, load a schema or an action, read the authoring guide, list the theme tokens. One call after mounting; a reported no-op in a browser without a model context. Read tools carryreadOnlyHint. No tool saves anything. After the first live round the tools gained the full authoring guide and a dry-run check, so an agent drafting through them follows the same rules as one reading the MCP guide. Co-author with a browser agent documents it.- The loop on real hosts. demo.widgentic.dev is the unmodified self-host image running as a single Azure Container App, with
/mcpforwarded so one origin serves both surfaces. widgentic.dev adopted the same package the same day. - A Chrome origin-trial token on both origins, so a plain Chrome or Edge exposes the tools without a flag. ChatGPT Desktop's browser lists all fourteen under "Available site tools", and ChatGPT renders the saved widgets on web, mobile and desktop.
How we built it
Spec-first, with OpenSpec: every capability has requirements and scenarios, every scenario has a test that asserts rendered content rather than CSS class names, and each change is archived with its design decisions. The repository enforces its own rules in the test gate: dependency direction between packages, browser-safety of the browser packages, a snapshot of every public export so surface changes are reviewed diffs. Releases are Changesets with npm provenance. Our own hosts live in a private repository that consumes the published packages the way anyone else would.
We built it with an AI coding agent as a pair, which is fitting for a project about agents and people co-authoring. The commits carry that attribution.
Three decisions shaped the whole project, and the WebMCP layer inherited them:
- Templates are data, not code. No expressions; data selects and the author supplies every literal. That is what makes a template safe to store from an untrusted author and safe for an agent to draft.
- Refuse at the door, never lose a save. Invalid entries are rejected on write with a structured code and re-validated on read. The WebMCP tools relay the designer's own errors so the agent can fix its draft.
- The person saves. Keys read; sessions write. For WebMCP that meant getters resolved on every call (hosts remount designers on tab switches), results that are structured and never thrown, and no save tool at all. A host agent that can operate the page may still click Save when the person asks it to; our tools never do, and the draft is on screen first.
What we learned
- MCP is becoming a UI protocol, not only a tool protocol. When we started, inline rendering was an experiment in one host. Within months the MCP Apps standard was shared by claude.ai, Claude Desktop, VS Code Copilot Chat and ChatGPT, and the vendor-specific way kept working only as an alias. Building on the open shape and treating each host's extensions as optional was the right bet.
- Same protocol, different manners. Hosts agree on the wire and disagree on everything around it: what they advertise, when they confirm, how they cache, how they treat a proposed message. A host matrix of verified behaviour became a product artifact in its own right, and "never guess a host, probe it" became a rule.
- Templates as data are what make agents welcome. No expressions means an untrusted author cannot execute anything, a validator can say exactly what is wrong, and an agent can draft a definition from the same rules a person edits by. Every guide and tool description is derived from the validators' own constants, so what the agent is told is what the code enforces.
- Trust has to be architectural. Agents read, people write. Keys that travel into prompt-injectable places must not be able to author; secrets are referenced by name and injected at execution. The boundary held under WebMCP without a new rule: the agent's tools act on the person's live session and never save; a host agent that can operate the page may click Save when the person asks, with the draft visible first.
- The browser is becoming an agent host, and the page is the interface. WebMCP turns the UI a person already uses into the agent's tool surface, so both see the same preview and the same diagnostics, and the copy-paste gap disappears. The standard is still moving — surfaces, argument shapes and permission models differ between browsers — so the layer has to feature-detect, stay thin, and put the collaboration boundary into the words the agent reads.
- Spec-first is what makes pair-building with an AI fast and safe. Requirements and scenarios written before code gave the agent a contract, gave us a review surface, and left a record of every decision. The challenge work landed in about two days because the shape of the system was already written down.
Challenges
- Cold starts. Scale-from-zero on Container Apps measured 33 seconds end to end, long enough for MCP hosts to give up. One always-on replica and a readiness probe fixed it.
- Live data never migrates itself. A persisted-shape change once crash-looped a deploy against existing documents. Every shape change now ships with a normalization seam and an old-shape regression test before it deploys.
- Guarding server-side actions. Node's
BlockListchecks IPv4 addresses against mapped-IPv6 rules, so a naive rule blocked every IPv4; embedded addresses are re-checked instead. Redirects are refused, bodies are capped, and the frame never touches the network. - First publish of a new package. Trusted publishing binds to an existing package, so
@widgentic/webmcpneeded a one-time bootstrap token, minted and deleted the same night. - The designer's public surface. Its handle drops sample data on load, so the example-data tool writes the persisted
dataExampleinstead, which turned out to be the better target. Diagnostics reach a host only through a subscription, so the package subscribes once per handle and marks the first read as derived. - Teaching the contract where the agent reads it. In the first live round the agent used the tools readily but drafted worse than after the MCP authoring guide, because the descriptions named shapes and not rules. The fix was a guide tool, a dry-run check tool and a DSL summary on every editing tool, shipped as 0.2.0 the same morning.
- Time. The challenge window was ten days; the WebMCP package, the demo deployment, the production adoption and the docs took about two. Spec-first paid for itself there.
Built With
- azure-container-apps
- azure-cosmos-db
- azure-key-vault
- bicep
- chatgpt
- claude-code
- cloudflare
- css
- docker
- esbuild
- github-actions
- html
- json-schema
- mcp
- mcp-apps
- mintlify
- node.js
- npm
- openspec
- sqlite
- typescript
- vitest
- web-components
- webmcp
Log in or sign up for Devpost to join the conversation.