Skip to main content
MCPJam ships built-in host presets for Claude, ChatGPT, Cursor, Copilot, Codex, and MCPJam itself. Each preset captures what a real host publishes during MCP initialize and ui/initializeclientInfo, 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:
  1. 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.
  2. Host template — the seed values stamped into a new HostConfig when a user picks the preset in the UI: clientCapabilities, hostCapabilitiesOverride, hostContext, and mcpProfile (with clientInfo, hostInfo, sandbox policy, compat-runtime flags). Defined in client/src/lib/client-templates.ts.
The split matters: a host style is the immutable identity of the host (you don’t toggle Claude into “ChatGPT mode”), while a host template is the editable starting point — users can adjust theme, viewport, or capabilities after seeding.

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 captured ui/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.
  • CHATGPT template cspDirectives — “captured 2026-05-18 via DevTools → Network → oaiusercontent.com response”.
  • COPILOT template — links to Microsoft Learn’s “Supported MCP Apps capabilities in Copilot” table.
Inventing values silently misrepresents the host. A widget that works in MCPJam-as-YourHost but fails in production YourHost is the failure mode we’re avoiding.
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

1

Probe the real host

Capture as much as you can from the live host before writing any code:
  • MCP initialize responseserverInfo, clientInfo, advertised capabilities (including the experimental block and the MCP UI extension).
  • ui/initialize responsehostInfo, hostCapabilities, hostContext (theme, displayMode, containerDimensions, locale, timeZone, userAgent, platform, deviceCapabilities, safeAreaInsets, styles).
  • Outer iframesandbox= attribute, allow= attribute, and the response Content-Security-Policy header.
  • Inner iframe (if the host nests) — same three things.
DevTools → Network → Response Headers + the inspector’s own JSON-RPC logger (Playground) are the easiest capture surfaces. Save these somewhere referenceable — you’ll cite them in code comments.
2

Add the host style

Open client/src/lib/client-styles/built-ins.ts and add a HostStyleDefinition for your host alongside CLAUDE_HOST_STYLE, CHATGPT_HOST_STYLE, etc.
export const YOURHOST_HOST_STYLE: HostStyleDefinition = {
  id: "yourhost",
  mcp: {
    // Which Apps SDK protocol the host implements. UIType.MCP_APPS for
    // MCP Apps spec, UIType.OPENAI_SDK for the OpenAI Apps SDK surface.
    protocolOverride: UIType.MCP_APPS,
    // window.navigator-like fields exposed to widget HTML.
    platform: YOURHOST_PLATFORM,
    // Optional @font-face block injected into the widget iframe.
    fontCss: YOURHOST_FONT_CSS,
    // Which capabilities your host implements. Use MCP_APPS_FULL_SURFACE,
    // MCP_APPS_NO_CLAIMS_SURFACE, or a custom subset.
    mcpAppsCapabilities: MCP_APPS_FULL_SURFACE,
    resolveStyleVariables: getYourHostStyleVariables,
  },
  chatUi: {
    label: "YourHost",
    shortLabel: "YourHost-style host",
    pickerDescription: "YourHost chatbox chrome",
    logoSrc: yourHostLogo,
    family: "yourhost",
    resolveChatBackground: (theme) => YOURHOST_CHAT_BACKGROUND[theme],
    loadingIndicator: YourHostMarkIndicator,
  },
};
Then register it in BUILT_IN_HOST_STYLES (same file, bottom):
export const BUILT_IN_HOST_STYLES: readonly HostStyleDefinition[] = [
  MCPJAM_HOST_STYLE,
  CLAUDE_HOST_STYLE,
  CHATGPT_HOST_STYLE,
  CURSOR_HOST_STYLE,
  COPILOT_HOST_STYLE,
  CODEX_HOST_STYLE,
  YOURHOST_HOST_STYLE,
];
3

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:
import yourHostLogo from "/yourhost_logo.png";
4

Add the host template

In client/src/lib/client-templates.ts, extend the HostTemplateId union:
export type HostTemplateId =
  | "mcpjam"
  | "claude"
  | "chatgpt"
  | "cursor"
  | "codex"
  | "copilot"
  | "yourhost";
Then add an entry to 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):
{
  id: "yourhost",
  label: "YourHost",
  description: "One-line description shown in the picker.",
  logoSrc: yourHostLogo,
  seed: (opts) => {
    const base = emptyHostConfigInputV2({
      hostStyle: "yourhost",
      modelId: "anthropic/claude-haiku-4.5", // or openai/<slug>, etc.
      temperature: 0.7,
      requireToolApproval: false,
    });
    const theme = opts?.theme ?? "dark";

    // From your MCP initialize capture. Spread the SDK default to keep
    // the MCP UI extension entry; only add what your host advertises
    // on top.
    base.clientCapabilities = {
      ...base.clientCapabilities,
      // experimental: { "yourhost/feature": { enabled: true } },
    };

    // From your ui/initialize capture. List only what the host
    // actually publishes — omitting a capability is more honest than
    // advertising one the host doesn't implement.
    base.hostCapabilitiesOverride = {
      openLinks: {},
      serverTools: {},
      // ...
    };

    // From your ui/initialize hostContext capture.
    base.hostContext = {
      theme,
      displayMode: "inline",
      availableDisplayModes: ["inline", "fullscreen"],
      containerDimensions: { width: 720, maxHeight: 5000 },
      locale: "en-US",
      timeZone: "America/Los_Angeles",
      userAgent: "yourhost",
      platform: "desktop",
      deviceCapabilities: { touch: false, hover: true },
      safeAreaInsets: { top: 0, right: 0, bottom: 0, left: 0 },
    };

    base.mcpProfile = {
      profileVersion: 1,
      initialize: {
        // From your MCP initialize clientInfo capture.
        clientInfo: { name: "yourhost-mcp", version: "1.0.0" },
      },
      apps: {
        uiInitialize: {
          // From your ui/initialize hostInfo capture.
          hostInfo: { name: "YourHost", version: "1.0.0" },
        },
        // Only if the host exposes window.openai (the OpenAI Apps SDK
        // surface) to widget HTML.
        // compatRuntime: { openaiApps: true },
        sandbox: {
          csp: {
            // ALMOST ALWAYS "declared". See the warning below.
            mode: "declared",
            // From your response Content-Security-Policy header.
            cspDirectives: {
              // ...
            },
          },
          permissions: {
            mode: "custom",
            // From your outer iframe allow= attribute, intersected
            // with what the host advertises in ui/initialize.
            allow: { clipboardWrite: true },
          },
          // From your iframe sandbox= attribute, minus the spec-mandated
          // allow-scripts + allow-same-origin (those are baseline).
          sandboxAttrs: ["allow-forms"],
          // Non-spec Permissions-Policy entries (e.g. fullscreen).
          allowFeatures: { fullscreen: "*" },
        },
      },
    };

    return base;
  },
},
5

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() (or window.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?
The Playground’s “Compare hosts” mode lets you render the same tool call under YourHost alongside Claude or ChatGPT side-by-side — useful for catching capability gaps.

Field guide

A non-exhaustive reference for the fields most often gotten wrong:
FieldWhat to captureCommon mistake
clientInfo.nameVerbatim from the host’s MCP initialize request.Inventing a “friendly” name. Apps that branch on this string break under your preset.
hostInfo.nameVerbatim from ui/initialize. Different protocol layer from clientInfo.Reusing clientInfo.name. ChatGPT uses openai-mcp and chatgpt respectively.
hostCapabilitiesOverrideOnly what ui/initialize.hostCapabilities lists.Advertising capabilities the renderer can’t honor. If listChanged notifications aren’t forwarded, omit the sub-field.
cspDirectivesVerbatim 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).
sandboxAttrsFrom 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.allowThe intersection of the outer iframe allow= and what ui/initialize.hostCapabilities advertises.Granting permissions the host advertises but doesn’t actually attach.
containerDimensionsThe 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

Almost never set restrictTo on sandbox.csp. SEP-1865 makes restrictTo an intersection with the view’s declared _meta.ui.csp, not a union. Adding the host’s production allowlist here can only narrow widgets — never help them. A view declaring connect-src https://api.example.com silently goes to zero if your restrictTo.connectDomains doesn’t list api.example.com. Use mode: "declared" and trust the view’s CSP. The only legitimate use of restrictTo is to express a deny intent via empty arrays (see the Copilot preset’s frameDomains: []).
hostCapabilitiesOverride is what the model sees. Listing capabilities the renderer doesn’t actually implement misleads widget authors — they’ll gate on the advertised flag, hit a dead path at runtime, and blame their widget. If a capability’s listChanged notifications aren’t forwarded by the renderer yet, omit the sub-field. Several existing presets call this out explicitly in comments — match that discipline.
When mirroring a host that doesn’t render widgets at all (e.g. Codex CLI), replace clientCapabilities instead of spreading — a spread leaks the SDK-default MCP UI extension back in and misrepresents the host as UI-capable. See the Codex preset for the pattern.

Open a PR

When the preset is working locally:
  1. Add a screenshot of a widget rendered under your preset to the PR description.
  2. Cite the sources for each non-default value (capture date for probes, doc URL for vendor specs).
  3. If any field is a guess, mark it as such in a comment.
The maintainers will eyeball the values against the cited captures, run a couple of widgets under the preset, and merge. If your host evolves later — a new capability, a CSP change — open a follow-up PR with a fresh probe.
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.