initialize and ui/initialize — clientInfo, hostInfo, capability advertise, hostContext (theme, viewport, device, locale), and sandbox policy (CSP directives, permissions, allow-features). With a preset selected, a widget rendered in the Playground behaves the way it would in production under that host.
If your host isn’t in the list yet — or if Microsoft, Anthropic, OpenAI, etc. ship a new capability and the inspector hasn’t caught up — adding a preset is a small, self-contained contribution. This page walks through it.
Host presets are values, not code. There’s no runtime to write. A new preset
is a TypeScript object that conforms to two interfaces, plus a logo file.
What a preset includes
A host preset has two pieces that live in separate files:- Host style — branding, label, chatbox chrome, font CSS, and the capability surface the host supports (which MCP Apps and OpenAI Apps SDK features it implements). Defined in
client/src/lib/client-styles/built-ins.ts. - Host template — the seed values stamped into a new
HostConfigwhen a user picks the preset in the UI:clientCapabilities,hostCapabilitiesOverride,hostContext, andmcpProfile(withclientInfo,hostInfo, sandbox policy, compat-runtime flags). Defined inclient/src/lib/client-templates.ts.
The probe-driven philosophy
The single most important rule when adding a host: capture values from a live probe, don’t invent them. Every existing preset has inline comments citing a real source — a capturedui/initialize response, a DevTools Network capture of the host’s response Content-Security-Policy header, or an official vendor doc. Examples from the codebase:
CLAUDE_HOST_STYLE_VARIABLES— verbatim from Claude’s published design tokens.CHATGPTtemplatecspDirectives— “captured 2026-05-18 via DevTools → Network → oaiusercontent.com response”.COPILOTtemplate — links to Microsoft Learn’s “Supported MCP Apps capabilities in Copilot” table.
When a field can’t be probed (no live capture, no public doc), say so in a
code comment. Several existing presets use phrases like “Undocumented;
chosen to match the ChatGPT convention” — that’s honest. Don’t hide
guesses behind clean values.
Steps
Probe the real host
Capture as much as you can from the live host before writing any code:
- MCP
initializeresponse —serverInfo,clientInfo, advertisedcapabilities(including theexperimentalblock and the MCP UI extension). ui/initializeresponse —hostInfo,hostCapabilities,hostContext(theme, displayMode, containerDimensions, locale, timeZone, userAgent, platform, deviceCapabilities, safeAreaInsets, styles).- Outer iframe —
sandbox=attribute,allow=attribute, and the responseContent-Security-Policyheader. - Inner iframe (if the host nests) — same three things.
Add the host style
Open Then register it in
client/src/lib/client-styles/built-ins.ts and add a HostStyleDefinition for your host alongside CLAUDE_HOST_STYLE, CHATGPT_HOST_STYLE, etc.BUILT_IN_HOST_STYLES (same file, bottom):Drop in the logo
Add the host’s logo to
client/public/ as yourhost_logo.png (or .svg). Existing presets use 256-512px square assets.Then import it at the top of client/src/lib/client-templates.ts:Add the host template
In Then add an entry to
client/src/lib/client-templates.ts, extend the HostTemplateId union:HOST_TEMPLATES. The skeleton below covers the fields every preset should consider — fill in what you probed, delete what doesn’t apply (see Field guide for what each one does):Test it
Run the inspector locally, create a new host using your preset, and try a widget under it:
- Does the brand pill render with your logo?
- Does
app.getHostContext()(orwindow.openai.theme, etc.) report what you’d expect from the real host? - Does a widget that probes
hostInfo.name === "YourHost"take that branch? - Are CSP violations reported in DevTools the same ones the real host would emit?
Field guide
A non-exhaustive reference for the fields most often gotten wrong:| Field | What to capture | Common mistake |
|---|---|---|
clientInfo.name | Verbatim from the host’s MCP initialize request. | Inventing a “friendly” name. Apps that branch on this string break under your preset. |
hostInfo.name | Verbatim from ui/initialize. Different protocol layer from clientInfo. | Reusing clientInfo.name. ChatGPT uses openai-mcp and chatgpt respectively. |
hostCapabilitiesOverride | Only what ui/initialize.hostCapabilities lists. | Advertising capabilities the renderer can’t honor. If listChanged notifications aren’t forwarded, omit the sub-field. |
cspDirectives | Verbatim from the host’s response CSP header. | Hand-curating a “reasonable” set. Real hosts ship surprising values (e.g. real ChatGPT emits only frame-src). |
sandboxAttrs | From the iframe sandbox= attribute, minus allow-scripts + allow-same-origin (baseline). | Including the baseline. The schema layers your entries on top of the renderer’s required-by-spec set. |
permissions.allow | The intersection of the outer iframe allow= and what ui/initialize.hostCapabilities advertises. | Granting permissions the host advertises but doesn’t actually attach. |
containerDimensions | The host’s intent (e.g. 720px chat column), not your inspector’s iframe size. | Treating it as a literal viewport. Views interpret it as policy intent. |
Gotchas
Open a PR
When the preset is working locally:- Add a screenshot of a widget rendered under your preset to the PR description.
- Cite the sources for each non-default value (capture date for probes, doc URL for vendor specs).
- If any field is a guess, mark it as such in a comment.
Stuck on a probe? Open a thread in the
MCPJam Discord — the team can usually help
with capture techniques or sanity-check what a vendor’s docs actually mean.

