The A2UI Catalog Now Renders Inside ChatGPT
The same a2uicatalog.ai connector that renders inside Claude now renders inside ChatGPT — the same URL, the same payloads, the same renderer. Nothing was adapted, ported, or re-packaged. The catalog was written against the open MCP Apps specification rather than against a particular chat client, and the second client came for free.
Here's how
No signup, no API key, nothing to install — but ChatGPT gates custom connectors behind a developer toggle.
Enable it once: Settings → Apps → Advanced settings → Developer mode. On Business and Enterprise workspaces an admin has to turn it on first, under Workspace Settings → Permissions & Roles.
Then add the connector:
Name: A2UI Catalog
URL: https://a2uicatalog.ai/mcp
Transport: Streamable HTTP
Auth: None
Then toggle it on inside the conversation, in the tool submenu. Saving a connector does not activate it — that one step accounts for most "it isn't working" reports.
Then ask for something real:
Using the A2UI catalog, render a
stat_cardwith value "1,234", label "Daily users", delta "+12%".
Why nothing had to change
MCP Apps — SEP-1865, co-authored by Anthropic and OpenAI, final since 26 January 2026 and now governed under the Linux Foundation — defines how a server ships interactive HTML and how a host renders it: ui:// resources, the text/html;profile=mcp-app media type, and a ui/* JSON-RPC bridge over postMessage into a sandboxed iframe.
The catalog already served exactly that. So did ChatGPT. The connector resolved the right view on the first attempt and the atoms painted.
The strongest single result is the one we expected to be least portable: a full-screen airspace playbook — a live ATC radar with rotating sweep, VOR beacons, approach paths and a traffic HUD — renders full-screen in both Claude and ChatGPT, from an identical payload against an identical bundle. Two independent implementations of the same specification, indistinguishable to the caller.
It is worth being precise about what that does and doesn't demonstrate. MCP Apps is a surface, not a transport. The transport underneath is ordinary Streamable HTTP. What a second host proves is that the rendering contract holds across implementations written by different vendors — the only claim that has ever mattered for a catalog whose premise is that one payload travels.
The one real constraint
Full-screen is not a property of the payload. height: "fullscreen" is a request, and it is only made on behalf of atoms that declare deep navigation — hub and playbook. An atom that sizes itself to the viewport but stands alone never triggers the request, so it gets 100vh inside an inline card, and its content is drawn correctly and painted off-screen.
That produced a memorable afternoon: a radar with no aircraft. The aircraft were there, computed and rendered, simply below the visible area. Hours went into the live ADS-B feed, which was innocent throughout — and which, separately, was genuinely returning zero aircraft for the box in question, so the one signal that might have contradicted the theory was itself flat.
The rule that falls out is simple: wrap full-page atoms in a playbook. The hosts were never the constraint; the trigger list was.
What the second host actually exposed
Reaching ChatGPT cost one connector URL. What it exposed was worth considerably more — three defects, all live, none visible from Claude:
A fix that shipped to nobody. A renderer change was deployed, verified on the live asset, and had no effect. MCP Apps hosts cache the UI template against its ui:// URI, and the server was still advertising the old one. Bundle deployed is not bundle reachable. The convention that would have caught it existed as a trailing code comment; it is now a release gate that fails the build if a changed bundle does not carry a version bump.
An instruction that read as a precondition. The server told callers to invoke identify_model first, before any other tool — while that tool's own description said skipping it was harmless. A literal reader resolves that contradiction by refusing to work, and one did, citing our own instructions as the blocker. A hard precondition on an optimisation is a single point of failure for everything behind it.
OAuth metadata that locked the door. RFC 9728 protected-resource metadata was published at the well-known root, where it describes the entire origin rather than the one endpoint it names. Clients connecting to the unauthenticated /mcp read it as "this origin is protected", looked for dynamic client registration, found none — deliberately, there is none — and refused to connect at all. The document carried a prose note explaining that /mcp needs no auth. No client parses prose. The fix was the URL.
The shape they share
None of these were new. All three had been live for a day or more, and every one was invisible because an existing connection never re-evaluates. Cached templates, cached instructions, cached grants: a working client keeps working precisely by not re-reading the things that changed.
Which makes the failure mode worth naming. The client most likely to hit all three at once is a brand-new one, connecting cold, exactly once — a reviewer, an evaluator, a first-time user. The population you never test against is the population that decides whether you are worth adopting.
A second implementation of the same open specification is the cheapest way to become that client on purpose. It cost a connector URL.
What this means for the catalog
Every payload already published works in ChatGPT today, unchanged. So do the surface declarations: each atom carries a works_on list naming the surfaces it supports and, where relevant, how it degrades — a static path instead of an animation in PDF, an ordered list instead of a stepper in email, and an honest refusal where a surface genuinely cannot carry an atom at all.
That model is the actual product. The renderers are downstream of it, and so is any individual host.