ChatGPT Plugin Extensions: Sidebar Apps, Panels and File Editors

TL;DR
ChatGPT plugins can now add sidebar apps, side panels, file editors and native forms. How openai/ui entrypoints work, with type-checked code.
Last updated: October 17, 2026
Since September 29, 2026, ChatGPT plugins can put their own UI in four places beyond the chat bubble: a full-screen app in the sidebar, a panel beside a conversation, a viewer or editor for a file type, and native forms. Under the hood this is OpenAI MCP Extensions - a few lines of _meta["openai/ui"] on top of a standard MCP App, plus the @openai/mcp-extensions SDK (0.2.0 at the time of writing). Our take: if you already ship an MCP App, adding a sidebar or panel entrypoint is cheap and works on desktop, the Work browser, iOS and Android. File editors and composer mentions are desktop-only for now, so treat them as an enhancement layer rather than the core of your product.
This guide covers what users see, how the pieces fit, and a working path through a small "CSV Studio" plugin: a thread panel, a .csv editor that saves back with ETags, a selection that becomes model context, and a native form. Every TypeScript snippet was type-checked against @openai/mcp-extensions@0.2.0. None of it was run against ChatGPT itself, since no keys were available for this piece.
What shipped: the user's view#
The ChatGPT release notes list it under September 29, 2026 as "Work with interactive plugin extensions":
Plugins can now include interactive panels, sidebar views, and viewers or editors for supported file types. Work with an app alongside your conversation, select the information you want ChatGPT to use, or complete a form provided by the plugin.
Plugins still need their account connections and workspace permissions. The help center article on plugins describes what that looks like in practice:
- Sidebar apps. Some plugins add an icon to the ChatGPT sidebar that opens the app full screen.
- Views inside a conversation. A plugin can open as a tab next to the thread you are working in.
- Mentions. You can mention specific items from a plugin in a prompt.
- Files. Plugins can open and edit supported file types in their own viewer.
- Settings and forms. Plugins can provide their own settings page and their own forms.
OpenAI's extensions docs name three launch examples: Canva uses sidebar tabs, Figma uses composer mentions in the desktop app, and Adobe uses file extension handlers to edit files with Acrobat and Photoshop inside ChatGPT. OpenAI's own kitchen-sink demo is Bits & Bolts, a CAD parts library; the "Bits & Bolts Remote" build installs from the plugin directory and its source lives in the mcp-extensions repo.
The same release notes entry also added "Start automations from connected-app events", where supported plugins use MCP events to kick off an automation. That is a separate feature and out of scope here.
If you missed the DevDay context, our DevDay 2026 recap lists plugin extensions alongside everything else that shipped that week.
How it works: skills plus an MCP server plus optional UI#
A ChatGPT plugin bundles skills, connected apps, app templates and, now, extensions. The UI part is still an MCP App: an HTML resource with the text/html;profile=mcp-app MIME type, linked from a tool through _meta.ui.resourceUri and rendered in a sandboxed iframe. If MCP Apps are new to you, start with our MCP Apps vs tool calling comparison; this post assumes that baseline.
What OpenAI MCP Extensions add is a set of ChatGPT-specific entrypoints and host APIs on top of that standard. The extensions docs describe it as letting plugins "hook their plugins into key surfaces of the ChatGPT user experience, including the sidebar, composer, and file viewers."
%% title: Where a plugin's UI shows up in ChatGPT
flowchart LR
P[Plugin] --> S[Skills]
P --> M[MCP server]
M --> T[Tools]
M --> R["UI resource ui://..."]
T -- "global" --> G[Sidebar app]
T -- "thread" --> TH[Side panel]
T -- "file" --> F[File editor]
R --> I[Sandboxed iframe]
G --> I
TH --> I
F --> IThe spec lets a single MCP App define up to three entrypoint types:
- Global: shows up in the primary sidebar. The server must accept
{}as tool arguments. On desktop it opens with a composer and thread layout and keeps the app as a permanent tab, soui/update-model-contextandui/messagetarget that thread. - Thread: shows up as a content tab inside a conversation. The server receives
{}, and every thread gets its own instance of the app. - File: a file-extension handler that renders matching files referenced anywhere in any thread.
When an app opens from an entrypoint, ChatGPT ignores _meta.ui.visibility, so a tool hidden from the model can still be a user-facing entrypoint.
The extension reference lists 11 extensions in total. Platform support at DevDay launch, from the spec's own table ("Web" means the Work browser; classic ChatGPT is excluded):
| Extension | What it does | Desktop | Work web | iOS | Android |
|---|---|---|---|---|---|
| Sidebar apps (global entrypoint) | Full-screen app in the sidebar | Yes | Yes | Yes | Yes |
| Conversation panels (thread entrypoint) | App as a tab beside the thread | Yes | Yes | Yes | Yes |
| Plugin settings | Native controls on the plugin details page | Yes | Yes | Yes | Yes |
| Display modes | Inline or fullscreen rendering | Yes | Yes | Yes | Yes |
| Plugin onboarding | A packaged skill that runs after install | Yes | Yes | Yes | Yes |
| Deep links | Jump to a page inside a global app | Yes | Yes | Yes | No |
| Extended message sending | Send or draft messages from the app | Yes | Yes | Default only | Default only |
| Extended model context | Attach app state or selections to the prompt | Yes | Partial (no images) | Partial (no thumbnails) | Yes |
| File viewers and editors (file entrypoint, file opening, file resources) | Open and save files in your app | Yes | No | No | No |
| Composer mentions | @-mention items from your plugin | Yes | No | No | No |
| Extended forms | Native forms with richer inputs | Yes | Partial | No | No |
The pattern is clear: anything that touches the local filesystem or the desktop composer is desktop-only, while the "open my app somewhere persistent" surfaces work everywhere.
Step 1: install the SDK (pin versions)#
The SDKs and spec live in github.com/openai/mcp-extensions under Apache-2.0. The TypeScript package covers both servers and apps; the Python package covers servers only.
For the MCP server:
npm install @openai/mcp-extensions@0.2.0 @modelcontextprotocol/server@2.3.1 zod@4.4.3For the MCP App (the code that runs in the iframe):
npm install @openai/mcp-extensions@0.2.0 @modelcontextprotocol/ext-apps@1.7.5 @modelcontextprotocol/sdk@1.32.1The Node package requires Node 22 or newer. For a Python server:
uv add openai-mcp-extensions
At the time of writing that resolves to 0.2.0, which needs Python 3.10+ and mcp>=2.0.0,<3.
Two version traps worth knowing up front:
- ext-apps 2.x is on npm, but the SDK's peer range is
^1.7.5. If you let npm pick the latest ext-apps you fall outside that range. Pin 1.x. - 0.2.0 moved the server helpers to MCP SDK 2.
McpServernow comes from@modelcontextprotocol/server, not@modelcontextprotocol/sdk. Older examples that import from the v1 SDK on the server side are the pre-0.2.0 shape.
The README's own install lines use pnpm add with caret ranges (@modelcontextprotocol/server@^2, @modelcontextprotocol/ext-apps@^1.7.5, @modelcontextprotocol/sdk@^1). The exact pins above are the versions this post was checked against.
Step 2: declare a conversation panel and a file editor#
On the server, each entrypoint is a tool with an openai/ui block in its _meta, pointing at a UI resource. This registers two UI resources, a thread panel, and a file handler for .csv and .tsv:
import { McpServer } from "@modelcontextprotocol/server";
import {
OpenAIExtensions,
OpenAIFileEntrypointInputSchema,
type OpenAIUiResourceMetadata,
type OpenAIUiToolMetadata,
} from "@openai/mcp-extensions/server";
import { z } from "zod";
declare const PANEL_HTML: string; // your bundled app HTML
declare const VIEWER_HTML: string;
const server = new McpServer({ name: "csv-studio", version: "1.0.0" });
const openaiExtensions = new OpenAIExtensions(server);
const PANEL_URI = "ui://csv-studio/panel";
const VIEWER_URI = "ui://csv-studio/viewer";
const MCP_APP_MIME = "text/html;profile=mcp-app";
const fullscreen = {
preferredDisplayMode: "fullscreen",
availableDisplayModes: ["fullscreen"],
} satisfies OpenAIUiResourceMetadata;
for (const [name, uri, html] of [
["csv-panel", PANEL_URI, PANEL_HTML],
["csv-viewer", VIEWER_URI, VIEWER_HTML],
] as const) {
server.registerResource(name, uri, { mimeType: MCP_APP_MIME }, async () => ({
contents: [
{ uri, mimeType: MCP_APP_MIME, text: html, _meta: { "openai/ui": fullscreen } },
],
}));
}
// Conversation panel: one instance per thread. Must accept {}.
server.registerTool(
"csv.panel",
{
title: "CSV Studio panel",
description: "Open CSV Studio beside the conversation.",
inputSchema: z.object({}),
_meta: {
ui: { resourceUri: PANEL_URI },
"openai/ui": { entrypoints: [{ type: "thread" }] } satisfies OpenAIUiToolMetadata,
},
},
async () => ({ content: [] }),
);
// File viewer/editor: ChatGPT passes { file: { name, resourceUri } }.
server.registerTool(
"csv.open",
{
title: "Open in CSV Studio",
description: "View and edit a CSV or TSV file.",
inputSchema: OpenAIFileEntrypointInputSchema,
_meta: {
ui: { resourceUri: VIEWER_URI },
"openai/ui": {
entrypoints: [{ type: "file", extensions: [".csv", ".tsv"] }],
} satisfies OpenAIUiToolMetadata,
},
},
async ({ file }) => ({
content: [{ type: "text", text: `Opened ${file.name}` }],
}),
);
export { server, openaiExtensions };
Type-checked with tsc --noEmit (strict, moduleResolution: bundler) against @openai/mcp-extensions@0.2.0 and @modelcontextprotocol/server@2.3.1. A deliberate typo such as type: "sidebar" fails the check, because OpenAIUiToolMetadata only accepts "file" | "global" | "thread".
What to notice:
- A sidebar app is a one-word change. Swap
{ type: "thread" }for{ type: "global" }. Global entries can also carry an optionalquickActionthat calls another tool. - Global and thread tools must accept
{}.z.object({})is the honest schema. Do not make the panel tool require arguments the host will never send. - File extensions use the
.extform of the HTMLacceptattribute, so".csv", not"text/csv". - One entrypoint per tool, each with a distinct title. OpenAI's patterns guide asks for this, and says thread and file titles should describe the view they open rather than repeat the plugin name. The rendered title comes from the tool's
title, thenannotations.title, thenname. - Display mode metadata goes on the resource content item, not the tool, so ChatGPT can pick a mode before the app initializes. ChatGPT supports
inlineandfullscreen(nopip) and uses fullscreen for all entrypoints. If you list both modes without a preference, it defaults to fullscreen.
Step 3: read and save the file in the app#
The file entrypoint is the most interesting part of the spec, because ChatGPT deliberately keeps the filesystem away from your iframe. MCP Apps can run untrusted JavaScript, so the app never sees a raw path. It gets an opaque resourceUri, and ChatGPT itself handles resources/read and resources/subscribe on that URI instead of forwarding them to your server.
%% title: File editor read and save
sequenceDiagram
participant U as User
participant H as ChatGPT host
participant A as MCP App
participant S as MCP server
U->>H: Open hex.csv
H->>A: tool-input {file.name, resourceUri}
A->>H: resources/read (opaque URI)
H-->>A: text, etag, writable
A->>H: openai/resources/write ifMatch etag
H-->>A: saved, conflict or too-large
opt Server needs the real file
A->>H: tools/call
H->>S: tools/call + openai/resource.path
endHere is the app side. It listens for the tool input, reads the file as text, and saves with an ETag guard:
import { App } from "@modelcontextprotocol/ext-apps";
import {
OpenAIExtensions,
OpenAIFileEntrypointInputSchema,
} from "@openai/mcp-extensions/app";
declare function render(text: string): void;
declare function showError(message: string): void;
const app = new App({ name: "csv-studio-viewer", version: "1.0.0" });
const openai = new OpenAIExtensions(app);
let fileUri: string | null = null;
let etag: string | undefined;
let writable = false;
async function load(uri: string): Promise<void> {
const result = await openai.resources?.read({ uri, representation: "text" });
const content = result?.contents[0];
if (content == null || !("text" in content)) return;
etag = content.openaiMetadata?.etag;
writable = content.openaiMetadata?.writable ?? false;
render(content.text);
}
// Register before connect() so the initial tool input is not missed.
app.addEventListener("toolinput", async ({ arguments: args }) => {
const input = OpenAIFileEntrypointInputSchema.safeParse(args);
if (!input.success || openai.resources == null) return;
fileUri = input.data.file.resourceUri; // opaque, never a filesystem path
await load(fileUri);
});
await app.connect();
export async function save(text: string): Promise<void> {
if (fileUri == null || !writable || openai.resources == null) return;
const result = await openai.resources.write(fileUri, {
text,
...(etag == null ? {} : { ifMatch: etag }),
});
switch (result.outcome) {
case "saved":
await load(fileUri); // pick up the new etag
break;
case "conflict":
showError("This file changed elsewhere. Reloading the latest version.");
await load(fileUri);
break;
case "too-large":
showError(`File exceeds the ${result.maxBytes}-byte write limit.`);
break;
}
}
Type-checked against @openai/mcp-extensions@0.2.0, @modelcontextprotocol/ext-apps@1.7.5 and @modelcontextprotocol/sdk@1.32.1.
The rules that matter, all from the spec:
- Writes go through
openai/resources/write, a new method. It takes an optionalifMatchETag and returns"saved","conflict"or"too-large"(withmaxBytes). Branch on all three; a save that silently does nothing on conflict is worse than one that shows an error. - Only write when the read said
writable: true, and only to the URI you were given. - Register the
toolinputlistener beforeapp.connect(). The SDK README calls this out: otherwise you can miss the initial input. openai.resourcescan be undefined. The SDK only populatesresources,modelContext,messageandfilesafter initialization, and leaves them undefined on hosts that lack the extension.
When your server genuinely needs the file (say, to parse a large file natively or read a sibling file), the app calls one of your own server tools. ChatGPT amends those calls with _meta["openai/resource"].path, the absolute path, and the server SDK exposes getResourcePath(context.mcpReq._meta) to read it. The README's example also resolves real paths and refuses anything outside the opened file's directory; copy that guard rather than trusting a relative path from the iframe.
Step 4: hand the user's selection to ChatGPT#
The "select the information you want ChatGPT to use" part of the release note is ui/update-model-context. The app pushes content to the host, the host shows it as a removable attachment in the composer, and the model reads it with the next prompt.
%% title: Selecting data for ChatGPT
sequenceDiagram
participant U as User
participant A as MCP App
participant H as ChatGPT host
participant M as Model
U->>A: Select rows in panel
A->>H: ui/update-model-context
H-->>U: Removable attachment in composer
U->>H: Send prompt
H->>M: Prompt + app context
M-->>H: Answer or server tool callIn CSV Studio, that is a short function in the same app module:
export async function shareSelection(rows: string[][], header: string[]): Promise<void> {
const modelContext = openai.modelContext;
if (modelContext == null) return; // host lacks the extension
await modelContext.update({
content: [
{
type: "text",
text: `Selected ${rows.length} rows (${header.join(", ")}):\n` +
rows.map((r) => r.join(", ")).join("\n"),
_meta: { "openai/title": `${rows.length} selected rows` },
},
],
structuredContent: { header, rows },
});
}
Rules from the spec and patterns guide:
- Each call replaces the previous one.
ui/update-model-contextis idempotent per app instance, so send the full current selection every time, not a delta. contentvsstructuredContent. OpenAI's decision table says to usecontentfor things the user explicitly selects (like an item they add to chat) andstructuredContentfor background state the model needs to understand what changed, such as a page navigation. Use server tools when the model needs more detail or needs to make a change.- Hidden blocks are not private. A block with
annotations.audience: ["assistant"]is hidden from the user but still sent to the model. - Labels and limits.
_meta["openai/title"]labels the attachment. Images in model context are not yet supported on web,openai/thumbnailis not supported on iOS, and audio blocks are not supported at all.
The app can also write into the composer. openai.message?.send() defaults to { target: "active", send: true }, which submits immediately. On desktop and the Work browser you can pass _meta: { "openai/message": { target: "new", send: false } } to open an editable draft instead (0.2.0 added this). iOS and Android support only the default.
Step 5: native forms (optional)#
Extended forms let your server ask the user for structured input through ChatGPT's own form UI rather than your iframe. In 0.2.0 that goes through requestFormInput:
import { McpServer } from "@modelcontextprotocol/server";
import { requestFormInput, type OpenAIForm } from "@openai/mcp-extensions/server";
const server = new McpServer({ name: "csv-studio-forms", version: "1.0.0" });
const chartSchema = {
type: "object",
properties: {
chart: {
type: "string",
title: "Chart type",
oneOf: [
{ const: "line", title: "Line" },
{ const: "bar", title: "Bar" },
{ const: "scatter", title: "Scatter" },
],
},
title: { type: "string", title: "Chart title", minLength: 1 },
},
required: ["chart"],
} satisfies OpenAIForm;
server.registerTool(
"csv.chart",
{ description: "Build a chart from the selected rows." },
(context) => {
const result = requestFormInput(context, {
key: "chart-options",
mode: "form",
message: "How should the chart look?",
requestedSchema: chartSchema,
});
// First round trip: hand ChatGPT the form and stop here.
if ("resultType" in result) return result;
// Second round trip: the user's answers.
return { content: [], structuredContent: result };
},
);
export { server };
Type-checked against @openai/mcp-extensions@0.2.0 and @modelcontextprotocol/server@2.3.1.
The handler looks synchronous because it is. This is the multi-round-trip request (MRTR) pattern from MCP 2026-07-28: on the first call requestFormInput returns an input-required result, you return it, and ChatGPT calls the tool again with the user's answers. We covered that protocol change in MCP 2026-07-28 breaking changes.
Protocol rules from the spec:
- OpenAI-registered MCP servers need MCP 2026-07-28 or later with MRTR for form elicitation. Direct MCP connections still support legacy forms.
- On older protocol versions, servers must use
openai/elicitation/create. The SDK exposes that aselicitInputLegacy; the oldelicitInputname is deprecated in 0.2.0. - Unsupported input types reject the whole form. ChatGPT reports the form as unsupported rather than showing part of it, so test every field type you use.
- Not on iOS or Android, and directory selection is unsupported on web.
Related: plugin settings are a separate capability, openai/settings, with a read tool and an update tool. ChatGPT renders native controls on the plugin details page, and your server is responsible for persisting the values. The SDK wraps it as extensions.settings?.register({ fields, layout, read, update }).
Step 6: connect and test in ChatGPT#
The connect guide gives the flow:
- Serve over public HTTPS. Use streamable HTTP, usually at
/mcp, or Secure MCP Tunnel for a local server. - Inspect it first. Run
npx @modelcontextprotocol/inspector@latestand confirm your tools list shows theopenai/uimetadata and that the UI resources resolve. - Add it in ChatGPT. In Plugins, choose +, then Add custom MCP server, then Create as a plugin.
- Use it. Start a new conversation and type
@to select the plugin. - Refresh after changes. When you change UI resources or metadata, select Refresh on the connection. ChatGPT also caches a global entrypoint's sidebar tab and widget HTML by default, so check for a missed refresh before debugging stale UI.
Then check the layout and client version:
- Desktop version. The patterns guide says to treat desktop
26.928.20710as the minimum for DevDay 2026 extensions, check it when the app opens from any entrypoint, and show an update message on older builds. - Viewport sizes. Test entrypoint views at 375 x 812, 768 x 1024 and 1440 x 900. Respect the host's safe-area insets.
- Caching. MCP App
localStoragepersists across desktop restarts and browser refreshes, so a global entrypoint can render cached data immediately and refresh in the background. - Workspace permissions. In Enterprise workspaces, "Use plugins" is on by default but "Upload plugins" and "Create plugins with MCPs" are off by default. If you cannot see Add custom MCP server, ask an admin.
If you would rather not hand-write the manifest, the help center notes that Plugin Creator (@plugin-creator) can scaffold a plugin from chat. The ChatGPT desktop and Work surfaces where the desktop-only extensions live are covered in our ChatGPT Work and Codex desktop app post.
Gotchas and limits#
- File editors are desktop-only. File entrypoints, file opening, file resources and composer mentions do not work on the Work browser, iOS or Android at launch.
- Deep links do not work on Android. Extended forms do not work on either mobile platform.
- No picture-in-picture. ChatGPT supports
inlineandfullscreenonly, and every entrypoint opens fullscreen. Do not add your own display-mode buttons; the patterns guide says ChatGPT provides them. - Free and Go on web are not there yet. The extensions docs say plugin extensions on the web are "coming soon" for ChatGPT Free and Go users.
- Desktop-only plugins stay desktop-only. The help center FAQ: "Extension availability does not remove an existing desktop restriction."
_meta["openai/visibility"]is deprecated (since July 21, 2026) in favor of the standard_meta.ui.visibility.- 0.2.0 had breaking renames.
createAppTransportis gone (useAppfrom ext-apps withOpenAIExtensions),type: "settings"entrypoints are gone (use theopenai/settingscapability), andelicitInputis deprecated in favor ofelicitInputLegacy. The SDK is 0.x and moved twice in two weeks (0.1.0 on September 29, 0.2.0 on October 10), so pin it. - Mention search must be read-only. If you advertise
openai/mentions: { searchTool }, that tool must setreadOnlyHint: true. - Check capabilities at runtime.
openai.resources,openai.modelContextandopenai.messagestay undefined on hosts that lack them. Every snippet above guards for that, and yours should too.
Plugin extensions vs plain MCP Apps#
The closest alternative is not another vendor's plugin system; it is the standard MCP Apps extension (io.modelcontextprotocol/ui) that these extensions sit on. The MCP project describes it as "Interactive HTML interfaces rendered inline in the conversation," and the client matrix lists support in Claude (web), Claude Desktop, VS Code GitHub Copilot, Microsoft 365 Copilot, Goose, Postman, MCPJam, ChatGPT, Cursor, Archestra.AI and PostHog Code. ChatGPT has been fully compatible with the MCP Apps spec since February 22, 2026, per OpenAI's plugin UI changelog.
%% title: Portable first, ChatGPT extras second
flowchart TD
A[New plugin UI] --> C[Standard MCP App]
C --> D{Host has openai/* capability?}
D -- Yes --> E[Add sidebar, panel, file, forms]
D -- No --> F[Inline UI + tool result]
E --> H[ChatGPT]
F --> G[Claude, VS Code, Cursor, Goose]| Standard MCP Apps | OpenAI MCP Extensions | |
|---|---|---|
| Where it runs | 11 hosts on the MCP client matrix, including ChatGPT, Claude, VS Code Copilot and Cursor | ChatGPT only (desktop, Work web, iOS, Android, varying by feature) |
| Governance | Official MCP extension | OpenAI spec and SDK, Apache-2.0 |
| Where UI appears | Inline in the conversation, invoked via the model | Adds sidebar app, thread tab, file handler, settings page, composer mentions |
| Files | No host-managed file editing | Opaque file URIs, write-back with ETags and conflict detection |
| Forms | Core MCP form elicitation | Richer inputs (resource pickers, suggestions), MRTR on registered servers |
| Sandbox | Iframe sandbox | Same iframe sandbox, same resource format |
| Distribution | Whatever each host offers | OpenAI plugin directory |
The tradeoff is reach against surface area. Standard MCP Apps reach the most hosts but only render inline when a model calls a tool. The extensions give your app a persistent home inside ChatGPT, a real file-editing story, and native forms, but none of that carries to other hosts, and parts of it are desktop-only today.
Because extensions are additive metadata and optional APIs, you do not have to choose. Build the MCP App first, then add openai/ui entrypoints and guard every openai.* call behind a capability check. The same server keeps working in Claude and the other MCP Apps hosts, and ChatGPT users get the extra surfaces. If you are coming from the earlier Apps SDK walkthrough, our Apps SDK build guide shows the older inline-widget path that this layers on.
FAQ#
Do I need to rewrite my Apps SDK app?#
No. Extensions sit on top of MCP Apps, and ChatGPT has been fully compatible with the MCP Apps spec since February 22, 2026. You add _meta["openai/ui"] entrypoints to existing tools and resources, then opt into the app-side APIs through OpenAIExtensions. Check for the deprecated _meta["openai/visibility"] while you are in there and move to _meta.ui.visibility.
Which ChatGPT surfaces support file editors?#
Only the ChatGPT desktop app at launch, according to the spec's platform table. The Work browser, iOS and Android do not support file entrypoints, file opening or file resources. OpenAI's patterns guide recommends treating desktop version 26.928.20710 as the minimum and showing an update prompt on older builds.
Can the app read the user's files directly?#
No. The app receives an opaque resourceUri that never maps to a filesystem path, and ChatGPT handles reads and writes on it. If your server needs the real file, the app calls one of your server tools, and ChatGPT adds the absolute path as _meta["openai/resource"].path, readable with getResourcePath.
Will this work in Claude?#
The MCP App part, yes: the client matrix lists Claude web and Claude Desktop as MCP Apps hosts, so the same UI resource and tools render there inline. The openai/* entrypoints and APIs are ChatGPT-specific. Other hosts ignore the metadata, and the app-side extension fields stay undefined, so gate them on capability checks and your plugin degrades cleanly.
Sources#
- ChatGPT release notes (September 29, 2026)
- ChatGPT help center: plugins
- OpenAI docs: plugin extensions
- openai/mcp-extensions repository
- OpenAI MCP Extensions specification
- OpenAI MCP Extensions patterns guide
- mcp-extensions changelog
- TypeScript SDK README
- OpenAI docs: connect a plugin to ChatGPT
- OpenAI plugin UI changelog
- MCP extensions client matrix
- @openai/mcp-extensions on npm
Continue Reading#
- OpenAI Apps SDK: Building MCP UIs Inside ChatGPT - the inline widget path that plugin extensions build on.
- MCP Apps vs Tool Calling vs Standalone UIs - when a portable MCP App beats plain tool calls.
- OpenAI DevDay 2026 Recap: What Shipped and What to Test - the rest of the launch that plugin extensions shipped with.
Get the next deep dive like this in your inbox
One email a week on OpenAI and the rest of the AI dev stack. Free.
Read next on AI coding tools
OpenAI Apps SDK: Building MCP UIs Inside ChatGPT
Apps SDK extends MCP with UI. Here is how to ship a real Apps SDK app from scratch: logic, interface, deploy, distribution, and the gotchas that cost me a weekend.
12 min readMCP Apps vs Tool Calling vs Standalone UIs: Interactive Interfaces for Agent Tools Compared
MCP Apps vs tool calling vs standalone web UIs: how sandboxed interactive UIs for MCP servers compare to the other two, and when to use each approach.
9 min readThe MCP 2026-07-28 Rewrite: What Breaks and How to Migrate
MCP 2026-07-28 breaking changes: a stateless core, deprecated Roots, Sampling and Logging, and tougher OAuth, plus a migration checklist for server authors.
11 min readNew here? Start with
Technical content at the intersection of AI and development. Building with AI agents, Claude Code, and modern dev tools - then showing you exactly how it works.








