Skip to content

Latest commit

 

History

History
1945 lines (1585 loc) · 52.5 KB

spec.md

File metadata and controls

1945 lines (1585 loc) · 52.5 KB

OpenAI MCP Extensions Specification

This spec outlines every extension and exactly how it builds on top of the existing MCP spec. In most cases, it is easier to use these extensions via our TypeScript and Python SDKs:

MCP.<Type> refers to the corresponding type in the MCP schema reference.

Platform Support

Web refers to the Work browser, excluding classic ChatGPT. Asterisks link to remaining platform limitations.

Feature Desktop Web iOS Android
Global entrypoint Supported Supported Supported Supported
Deep links Supported Supported Supported Not supported
Thread entrypoint Supported Supported Supported Supported
File entrypoint Supported Not supported Not supported Not supported
Resource display modes Supported Supported Supported Supported
Plugin onboarding Supported Supported Supported Supported
Structured settings Supported Supported Supported Supported
Extended model context Supported Supported* Supported* Supported
WebMCP Supported Not supported Not supported Not supported
Composer at-mentions Supported Supported Not supported Not supported
Extended message sending Supported Supported Supported* Supported
Extended forms Supported Supported* Not supported Not supported
File resources Supported Not supported Not supported Not supported
File opening Supported Not supported Not supported Not supported

Server Capabilities

Servers advertise named capabilities at these locations:

MCP Protocol version Result Capability location
2026-07-28 or newer server/discover capabilities["extensions"]
2025-11-25 or earlier initialize capabilities["extensions"] or legacy capabilities["experimental"]

MCP App Entrypoints

Normally, MCP Apps can only be invoked via the model. However, it may be convenient to allow users to open your MCP App via static entrypoints.

You can define up to three entrypoints on any MCP App:

  • Global: Available in the primary sidebar navigation.
  • Thread: Available as a content tab within a thread.
  • File: File extension handler to render matching files referenced from any part of any thread.

Behavior Details

  • Entrypoints are registered by adding one or more entries to _meta["openai/ui"]["entrypoints"], as defined below.
  • _meta["ui"]["visibility"] normally controls model versus user visibility. This field is ignored when an MCP App is invoked as an entrypoint.

Entrypoint Icons

An icon helps users identify the MCP Server in navigation.

Entrypoint icons are resolved in the following priority order:

  1. The icons property in the MCP App’s tools/list entry.
  2. The MCP Server icon for local MCP Servers, or the registered app logo for hosted MCP Apps. Server icons come from _meta["io.modelcontextprotocol/serverInfo"]["icons"] in server/discover, or serverInfo["icons"] in initialize for MCP Servers that do not implement server/discover.
  3. A generic fallback icon.

Titles

All entrypoints will be rendered with a title.

Behavior Details

  • The title comes from the first defined property in the following list on your MCP App’s tools/list entry:
    • title
    • annotations["title"]
    • name

Global Entrypoint

Global entrypoints expose apps that users can open fullscreen from global navigation.

Global CAD Parts Library

Behavior Details

  • On Desktop: requests that target the current thread, such as ui/update-model-context and ui/message, use the global entrypoint's thread.
  • The server MUST accept {} as the tool arguments when the entrypoint opens.

Schema

interface GlobalEntrypoint {
  type: "global";
}

Examples

tools/list response item:

{
  "name": "cad.library",
  "inputSchema": {
    "type": "object"
  },
  "_meta": {
    "ui": {
      "resourceUri": "ui://bits-and-bolts/library"
    },
    "openai/ui": {
      "entrypoints": [
        {
          "type": "global"
        }
      ]
    }
  }
}

Deep Links

Deep links navigate directly to a particular page within a global entrypoint.

Behavior Details
  • On Desktop: use the codex URI scheme.
  • On iOS: use the chatgpt URI scheme.
  • URI format:
    • {scheme}://plugins/{pluginId}@{marketplace}/app/{toolName}?path={encodedAppRelativePath}
      • pluginId
        • MUST be percent-encoded.
        • Can be found via the public ChatGPT plugin URL (https://chatgpt.com/plugins/{pluginId}).
      • marketplace
        • A custom marketplace.json name.
        • @{marketplace} MUST be omitted for plugins published directly to ChatGPT.
      • toolName
        • MUST be percent-encoded as a single path segment.
      • encodedAppRelativePath
        • The complete app-relative URL, including any query string, MUST be percent-encoded as the path query value.
        • The decoded app-relative URL MUST begin with / and MUST NOT contain a fragment.
        • If path is omitted, the app-relative URL defaults to /.
  • MCP Apps receive the specified app-relative URL in hostContext["openai/deepLink"] during initialization and through subsequent ui/notifications/host-context-changed notifications, following MCP Apps host-context update semantics.

Web link format:

https://chatgpt.com/plugins/<plugin-id>/app/<tool-name>?path=<encoded-app-relative-url>
Schema
interface DeepLinkHostState {
  /** App-relative URL, including any query. */
  url: string;
}
Examples

Deep link with two query parameters:

codex://plugins/bits-and-bolts/app/cad.browse?path=%2Fparts%3Ftag%3Dbolt%26sort%3Dasc

MCP App initialize result:

{
  "jsonrpc": "2.0",
  "id": 0,
  "result": {
    "protocolVersion": "2026-01-26",
    "hostInfo": { "name": "example-host", "version": "1.0.0" },
    "hostCapabilities": {},
    "hostContext": {
      "openai/deepLink": {
        "url": "/parts?tag=bolt&sort=asc"
      }
    }
  }
}

Notification when the user clicks a deep link to a different page:

codex://plugins/bits-and-bolts/app/cad.browse?path=%2Fparts%2Fhex-bolt
{
  "jsonrpc": "2.0",
  "method": "ui/notifications/host-context-changed",
  "params": {
    "openai/deepLink": {
      "url": "/parts/hex-bolt"
    }
  }
}

Thread Entrypoint

Thread entrypoints let users manually open an MCP App as a new content tab within a thread.

CAD Parts Tray in a thread side panel

Behavior Details

  • The server receives {} as the tool arguments when the entrypoint opens.
  • Every thread will have a different instance of the entrypoint.

Schema

interface ThreadEntrypoint {
  type: "thread";
}

Examples

tools/list response item:

{
  "name": "cad.tray",
  "inputSchema": {
    "type": "object",
    "properties": {}
  },
  "_meta": {
    "ui": {
      "resourceUri": "ui://bits-and-bolts/tray"
    },
    "openai/ui": {
      "entrypoints": [
        {
          "type": "thread"
        }
      ]
    }
  }
}

File Extension Entrypoint

File extension entrypoints let MCP Apps advertise support for file types (for example, .ipynb or .stl) and provide a custom viewer in place of the default file viewer.

File extension entrypoints use extended MCP Resource APIs for reading, subscribing to, and updating the files that invoked them.

STL file opened in a CAD file extension handler

Capability Advertisement

MCP App initialize result:

{
  "jsonrpc": "2.0",
  "id": 0,
  "result": {
    "protocolVersion": "2026-01-26",
    "hostInfo": { "name": "example-host", "version": "1.0.0" },
    "hostCapabilities": { "experimental": { "openai/resource": {} } },
    "hostContext": {}
  }
}

Behavior Details

  • Servers MUST use the file-extension form (a string that starts with ".") of HTML's accept attribute for each value in extensions.
  • The MCP Server tool receives FileInput as its arguments when the entrypoint opens.
    • resourceUri
      • Opaque URI that WILL NOT directly map to a filesystem path.
      • Standard MCP resource methods such as resources/read and resources/subscribe work. However, they will be intercepted and handled by ChatGPT instead of the MCP Server.
  • The MCP App receives ui/notifications/tool-input with the same FileInput arguments.

Schema

interface FileEntrypoint {
  type: "file";
  extensions: string[];
}

interface FileInput {
  file: {
    /** Non-empty name of the opened file. Includes the extension but not the path. */
    name: string;
    /** Non-blank opaque URI for access through the host. */
    resourceUri: string;
  };
}

Examples

tools/list response item:

{
  "name": "cad.open",
  "inputSchema": {
    "type": "object",
    "properties": {
      "file": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "resourceUri": {
            "type": "string"
          }
        },
        "required": ["name", "resourceUri"]
      }
    },
    "required": ["file"]
  },
  "_meta": {
    "ui": {
      "resourceUri": "ui://bits-and-bolts/viewer"
    },
    "openai/ui": {
      "entrypoints": [
        {
          "type": "file",
          "extensions": [".stl", ".step"]
        }
      ]
    }
  }
}

Notification:

{
  "jsonrpc": "2.0",
  "method": "ui/notifications/tool-input",
  "params": {
    "arguments": {
      "file": {
        "name": "hex-bolt.stl",
        "resourceUri": "host-resource://hex-bolt"
      }
    }
  }
}

Filesystem Access

Because MCP Apps can execute untrusted JavaScript, ChatGPT will never directly provide raw filesystem paths to MCP Apps. Instead, MCP resource methods should be used.

For more advanced filesystem usage, such as reading relative files or parsing very large files that can't be transferred via a single MCP resource request, use dedicated tools handled by the MCP Server.

Tool calls from an MCP App to its MCP Server within a file extension entrypoint will be intercepted and amended with _meta["openai/resource"]["path"]. Its value is the absolute filesystem path of the opened resource.

Once the MCP Server has the path, it can do anything with it, including sending the path back to the MCP App if it is trusted.

sequenceDiagram
  participant App as MCP App
  participant Host
  participant Server as MCP Server

  alt Resource operation
    App->>Host: Read, subscribe, or write using the opaque URI
    Host-->>App: Result or resource update
  else Tool call
    App->>Host: tools/call
    Host->>Server: tools/call + _meta["openai/resource"].path
    Server-->>Host: Tool result
    Host-->>App: Tool result
  end
Loading

resources/read Representation

By default, MCP Servers determine whether the right representation for a resource is text or a base64-encoded blob. However, a resources/read call for a file extension entrypoint is handled by ChatGPT, which cannot infer what the correct representation should be. To specify the ideal representation, set _meta["openai/resource"]["representation"] to either "text" or "blob".

Behavior Details
  • If the representation is unspecified, an MCP App SHOULD be able to handle either text or blob results.
  • If a representation is requested from a URI other than the one that represents the file extension entrypoint, it is up to the MCP Server to respect the representation request.
Examples

Request:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "resources/read",
  "params": {
    "uri": "host-resource://hex-bolt",
    "_meta": { "openai/resource": { "representation": "text" } }
  }
}

Resource Subscriptions

MCP Apps receive change notifications through notifications/resources/updated.

Behavior Details
  • MCP Apps MAY subscribe to changes to their resourceUri with resources/subscribe and stop receiving updates with resources/unsubscribe.

Resource Writes

The MCP protocol normally only allows you to read resources and subscribe to changes to them. File extension entrypoints support updating the resource to enable rich editor experiences within ChatGPT via an openai/resources/write method.

Behavior Details
  • MCP Apps MAY receive _meta["openai/resource"] on a content item in a resources/read result for a file opened via a file extension entrypoint.
  • MCP Apps MAY call openai/resources/write with the file["resourceUri"] given to them via a file extension entrypoint.
  • MCP Apps MAY NOT call openai/resources/write with any other URI.
  • MCP Apps MAY NOT call openai/resources/write if writable was not set to true when reading the file.
  • If ifMatch is provided, the resource will only be updated if the ETag provided matches the latest value.
Schema
/** `resources/read` `_meta["openai/resource"]` for a file extension entrypoint URI. */
interface ResourceMetadata {
  /** A hint as to whether the host supports writes. Defaults to false. */
  writable?: boolean;
  /** Opaque version token for the returned contents. */
  etag?: string;
}

/** `openai/resources/write` params. */
type ResourceWriteParams = {
  /** Non-blank host-provided URI of the opened resource. */
  uri: string;
  /** Non-empty ETag to match. Omit to write without a version check. */
  ifMatch?: string;
} & (
  | { text: string }
  | {
      /** Base64-encoded replacement contents. */
      blob: string;
    }
);

/** `openai/resources/write` result. */
type ResourceWriteResult =
  | {
      outcome: "saved";
      /** ETag of the saved version. */
      etag: string;
    }
  | {
      outcome: "conflict";
      /** Not saved. ETag of the current version. */
      etag: string;
    }
  | {
      outcome: "too-large";
      /** Not saved. Size limit exceeded by UTF-8 text or base64-decoded blob bytes. */
      maxBytes: number;
    };
Examples

resources/read response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "contents": [
      {
        "uri": "host-resource://hex-bolt",
        "text": "solid hex-bolt\nendsolid hex-bolt\n",
        "_meta": {
          "openai/resource": { "etag": "version-1", "writable": true }
        }
      }
    ]
  }
}

Request:

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "openai/resources/write",
  "params": {
    "uri": "host-resource://hex-bolt",
    "text": "solid edited-hex-bolt\nendsolid edited-hex-bolt\n",
    "ifMatch": "version-1"
  }
}

Response:

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": { "outcome": "saved", "etag": "version-2" }
}

Structured Settings

Structured settings let MCP Apps contribute settings to the plugin details page within ChatGPT. ChatGPT will render the settings with native controls that match the look and feel of ChatGPT on the current platform.

Structured settings on the Bits & Bolts plugin page

Capability Advertisement

Servers advertise openai/settings through server capabilities.

initialize result (MCP 2025-11-25):

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-11-25",
    "serverInfo": {
      "name": "bits-and-bolts",
      "version": "1.0.0"
    },
    "capabilities": {
      "tools": {},
      "experimental": {
        "openai/settings": {
          "readTool": "settings.read",
          "updateTool": "settings.update"
        }
      }
    }
  }
}

server/discover result:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "resultType": "complete",
    "supportedVersions": ["2026-07-28"],
    "_meta": {
      "io.modelcontextprotocol/serverInfo": {
        "name": "bits-and-bolts",
        "version": "1.0.0"
      }
    },
    "capabilities": {
      "tools": {},
      "extensions": {
        "openai/settings": {
          "readTool": "settings.read",
          "updateTool": "settings.update"
        }
      }
    }
  }
}

Behavior Details

  • SettingsCapability returns two tools:
    • readTool reads the type-safe schema, current values, and layout for the settings.
    • updateTool receives updates for changed settings from ChatGPT.
  • MCP Servers are responsible for persisting settings.

Schema

/** capabilities["extensions"]["openai/settings"] */
interface SettingsCapability {
  /** Non-blank name of the read settings tool. */
  readTool: string;
  /** Non-blank name of the update settings tool. */
  updateTool: string;
}

Read Settings Tool

Settings combine primitive fields and tool actions.

Behavior Details

  • Servers MUST accept {} for the read tool, and calls to it must be read-only.
  • outputSchema
    • Servers MUST declare an outputSchema describing SettingsReadResult.
    • The example response contains an outputSchema you can use.
  • values
    • Servers MUST provide a current or default value in values for every schema property.
  • layout
    • Servers MAY use layout to group settings and add tool actions into logical groupings.
    • Fields omitted from layout appear in an "Other settings" group after the listed groups.
  • Primitive settings only support boolean, string (with optional enum), number, and integer types.
  • Tool settings may correspond to MCP App tools or regular tools.
    • Tool actions use the referenced tool's display name: title, then annotations.title, then name.
    • Regular tools: when the button is clicked, ChatGPT will render a spinner followed by a tooltip containing text from the tool call response.
    • MCP App tools: when the button is clicked, ChatGPT will render the MCP App in a modal on top of settings. This can be used for bespoke settings such as a payment method entry form.

Schema

interface SettingsReadResult {
  schema: {
    type: "object";
    properties: Record<string, SettingSchema>;
    /** Required property names. All properties still need an effective value in `values`. */
    required?: string[];
  };
  /** Current or default values for every schema property. */
  values: Record<string, unknown>;
  /** Groups appear in array order. */
  layout?: SettingsGroup[];
}

type SettingSchema = {
  title: string;
  description?: string;
} & (
  | { type: "boolean" }
  | {
      type: "string";
      enum?: string[];
      /** Minimum string length. Nonnegative integer. */
      minLength?: number;
      /** Maximum string length. Nonnegative integer. */
      maxLength?: number;
      /** Regular expression that the value must match. */
      pattern?: string;
    }
  | {
      type: "number" | "integer";
      /** Inclusive lower bound. */
      minimum?: number;
      /** Inclusive upper bound. */
      maximum?: number;
      /** Positive number whose multiples are valid values. */
      multipleOf?: number;
    }
);

interface SettingsGroup {
  kind: "group";
  title: string;
  items: (
    | { kind: "property"; property: string }
    | {
        kind: "tool";
        /** Same-server tool accepting {}. */
        tool: string;
        /** @deprecated Set the title on the referenced MCP tool. Hosts ignore this field. */
        title?: string;
        description?: string;
      }
  )[];
}

Examples

Tool definition:

{
  "name": "settings.read",
  "inputSchema": { "type": "object", "additionalProperties": false },
  "annotations": { "readOnlyHint": true },
  "outputSchema": {
    "type": "object",
    "properties": {
      "schema": { "type": "object" },
      "values": { "type": "object" },
      "layout": { "type": "array", "items": { "type": "object" } }
    },
    "required": ["schema", "values"]
  }
}

Response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [],
    "structuredContent": {
      "schema": {
        "type": "object",
        "properties": {
          "units": {
            "type": "string",
            "title": "Measurement units",
            "enum": ["mm", "in"]
          },
          "showGrid": {
            "type": "boolean",
            "title": "Show grid"
          }
        }
      },
      "values": {
        "units": "in",
        "showGrid": false
      },
      "layout": [
        {
          "kind": "group",
          "title": "Display",
          "items": [
            {
              "kind": "property",
              "property": "units"
            },
            {
              "kind": "tool",
              "tool": "cad.library"
            }
          ]
        }
      ]
    }
  }
}

Update Settings

The server receives updated settings values through the tool named by updateTool in the settings capability.

Behavior Details

  • ChatGPT will only call updateTool with the settings properties that have changed.

Schema

interface SettingsUpdateArguments {
  set: Record<string, string | number | boolean>;
}

Examples

Tool definition:

{
  "name": "settings.update",
  "inputSchema": {
    "type": "object",
    "properties": {
      "set": {
        "type": "object",
        "properties": {
          "units": { "type": "string", "enum": ["mm", "in"] },
          "showGrid": { "type": "boolean" }
        },
        "minProperties": 1,
        "additionalProperties": false
      }
    },
    "required": ["set"],
    "additionalProperties": false
  },
  "outputSchema": {
    "type": "object",
    "properties": { "values": { "type": "object" } },
    "required": ["values"]
  }
}

Request:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "settings.update",
    "arguments": {
      "set": {
        "showGrid": true
      }
    }
  }
}

tools/call response retaining units:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [],
    "structuredContent": {
      "values": {
        "units": "in",
        "showGrid": true
      }
    }
  }
}

Display Modes

The MCP Apps specification defines display modes to specify where MCP Apps can be rendered. However, appCapabilities.availableDisplayModes is available only after app initialization, which can take time. ChatGPT also recognizes _meta["openai/ui"].availableDisplayModes on the UI resource content item so it can choose a supported mode immediately.

Bits & Bolts in inline and fullscreen display modes

Schema

type ChatGPTUIDisplayMode = "inline" | "fullscreen";

/** UI resource content item's `_meta["openai/ui"]`. */
interface DisplayModes {
  /** Display modes the app supports. */
  availableDisplayModes?: ChatGPTUIDisplayMode[];
  /** Preferred initial display mode. */
  preferredDisplayMode?: ChatGPTUIDisplayMode;
}

Behavior Details

  • ChatGPT supports inline and fullscreen, but not pip.
  • When the model invokes an MCP App, ChatGPT considers preferredDisplayMode when choosing the initial display mode.
  • If availableDisplayModes is omitted from _meta["openai/ui"], ChatGPT assumes the app supports all ChatGPT display modes.
  • If availableDisplayModes includes both inline and fullscreen, ChatGPT defaults to fullscreen unless preferredDisplayMode specifies otherwise.
  • If _meta["openai/ui"] is omitted entirely, ChatGPT preserves legacy behavior and defaults to inline.
  • ChatGPT uses fullscreen for all entrypoints.

Examples

resources/read content item that opens fullscreen by default:

{
  "uri": "ui://bits-and-bolts/library",
  "mimeType": "text/html;profile=mcp-app",
  "text": "<!doctype html><html>...</html>",
  "_meta": {
    "openai/ui": {
      "availableDisplayModes": ["inline", "fullscreen"]
    }
  }
}

Plugin Onboarding

Onboarding provides a setup skill for users to run after installation. Setup invokes the onboarding skill in a new conversation, or in the existing thread if the plugin was installed during a conversation.

Plugin onboarding

Behavior Details

  • Plugins MAY set extensions["com.openai"]["onboardingSkill"] in their manifest to a path relative to the plugin root, referencing a packaged skill.

Examples

Manifest:

{
  "name": "bits-and-bolts",
  "skills": "./skills/",
  "extensions": {
    "com.openai": {
      "onboardingSkill": "./skills/setup/SKILL.md"
    }
  }
}

ui/update-model-context Extensions

This extension clarifies and extends the behavior of ui/update-model-context.

Text, resource link, embedded resource, and image in the model context popover

Capability Advertisement

MCP App initialize result:

{
  "jsonrpc": "2.0",
  "id": 0,
  "result": {
    "protocolVersion": "2026-01-26",
    "hostInfo": { "name": "example-host", "version": "1.0.0" },
    "hostCapabilities": {
      "experimental": { "openai/modelContext": {} },
      "updateModelContext": {
        "text": {},
        "image": {},
        "resourceLink": {},
        "resource": {},
        "structuredContent": {}
      }
    },
    "hostContext": {}
  }
}

Behavior Details

  • On Web: image content in model context is not yet supported.
  • Content block _meta is excluded from model input.
  • ui/update-model-context is idempotent. Each call replaces the model context previously supplied by the same MCP App instance.

Groups

Bits & Bolts context groups

Group metadata labels related content blocks.

type ModelContextGroup = {
  /** Title shared by related content blocks. */
  title: string;
};

MCP Apps MAY set _meta["openai/group"] to a ModelContextGroup object on any content block in ui/update-model-context.

The group title MUST be a non-empty string. Content blocks without a usable group title use the "Context" group.

Examples

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "ui/update-model-context",
  "params": {
    "content": [
      {
        "type": "text",
        "text": "Selected: Bug keycap (1U)",
        "_meta": { "openai/group": { "title": "Keycaps" } }
      },
      {
        "type": "text",
        "text": "Selected: OpenAI keycap (1U)",
        "_meta": { "openai/group": { "title": "Keycaps" } }
      },
      {
        "type": "text",
        "text": "Selected: Codex Micro joystick cap",
        "_meta": { "openai/group": { "title": "Controls" } }
      }
    ]
  }
}

Context Changed Notifications

Context change notifications provide updates from the host when model context changes, such as when a user removes an attachment.

Behavior Details

  • MCP Apps receive their attached context in hostContext["openai/modelContext"] on initialization or remount.

Schema

type ModelContextHostState = {
  /** Non-empty ID, renewed any time content or structuredContent changes. */
  updateId: string;
  content?: MCP.ContentBlock[];
  structuredContent?: Record<string, unknown>;
} | null;

Examples

Request:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "ui/update-model-context",
  "params": {
    "content": [{ "type": "text", "text": "Selected: M6 hex bolt" }]
  }
}

Response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "_meta": {
      "openai/modelContext": {
        "updateId": "update-1"
      }
    }
  }
}

Notification (context cleared):

{
  "jsonrpc": "2.0",
  "method": "ui/notifications/host-context-changed",
  "params": {
    "openai/modelContext": null
  }
}

Titles

Titles provide human-readable labels for text attachments and alt text for images.

MCP Apps MAY set _meta["openai/title"] to a non-empty string on text and image content blocks.

Examples

Text block in ui/update-model-context with a title:

{
  "type": "text",
  "text": "Selected: agent dial",
  "_meta": {
    "openai/title": "Agent dial"
  }
}

Thumbnails

Thumbnails give text attachments a visual representation of their context.

MCP Apps MAY set _meta["openai/thumbnail"] to an MCP.Icon on text content blocks in ui/update-model-context.

Behavior Details

  • On iOS: openai/thumbnail is not yet supported.

Examples

Text block in ui/update-model-context with a thumbnail:

{
  "type": "text",
  "text": "Selected: agent dial",
  "_meta": {
    "openai/thumbnail": {
      "src": "https://example.com/agent-dial.png"
    }
  }
}

Background Context

Sometimes, it is useful to provide context to the model without showing a removable composer attachment to the user.

Behavior Details

  • Content blocks marked with annotations.audience: ["assistant"] are hidden from the user.
    • All model context is sent to the model regardless of audience.
  • MCP Apps returning serialized structuredContent in a text content block for MCP backward compatibility SHOULD set that block's annotations.audience to ["assistant"].

Examples

Request:

{
  "type": "text",
  "text": "Current part: hex-bolt",
  "annotations": {
    "audience": ["assistant"]
  }
}

Supported Content

ui/update-model-context and ui/message support the following content blocks:

Content block Support
Text Supported
Image Supported
Resource link Supported
Embedded resource Supported
Audio Not supported

WebMCP

Agents can discover and call tools registered by expanded MCP Apps through document.modelContext.

  • WebMCP is available only on Desktop.
  • ChatGPT does not support the MCP Apps SDK’s App.registerTool().

ui/message Extensions

This extension adds metadata to ui/message to control its behavior in ChatGPT.

Capability Advertisement

Support for this extension is indicated by "openai/message": {}.

MCP App initialize result:

{
  "jsonrpc": "2.0",
  "id": 0,
  "result": {
    "protocolVersion": "2026-01-26",
    "hostInfo": { "name": "example-host", "version": "1.0.0" },
    "hostCapabilities": {
      "experimental": { "openai/message": {} },
      "message": {
        "text": {}
      }
    },
    "hostContext": {}
  }
}

Behavior Details

  • On iOS: ui/message does not support resource links.
  • ui/message supports the same content types as ui/update-model-context.

Titles

Messages support the same content titles as model context.

Untitled text becomes ordinary message text, while titled text becomes a labeled inline item that can be removed as a single unit.

MCP Apps SHOULD NOT display titled text items as selected. Apps are not notified when an item is removed from the composer.

Prompt Target and Send Behavior

This extension adds metadata to ui/message to control its behavior in ChatGPT.

Behavior Details

  • Defaults when undefined: { target: "active", send: true }.
  • For supported target/send combinations, send: false appends content to the active draft or opens a fresh editable draft when target: "new", without submitting it.

Schema

interface MessageParams {
  role: "user";
  content: MCP.ContentBlock[];
  _meta?: {
    "openai/message"?: MessageOptions;
  };
}

type MessageOptions =
  | {
      /** Which conversation receives the content. */
      target: "new";
      /** Whether to send the content immediately. */
      send?: boolean;
    }
  | {
      target?: "active";
      send?: boolean;
    };

Examples

Request sending a message to a new conversation:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "ui/message",
  "params": {
    "role": "user",
    "content": [
      {
        "type": "text",
        "text": "Help me design a bracket."
      }
    ],
    "_meta": {
      "openai/message": {
        "target": "new"
      }
    }
  }
}

Request opening a new editable draft:

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "ui/message",
  "params": {
    "role": "user",
    "content": [
      {
        "type": "text",
        "text": "Help me design a bracket."
      }
    ],
    "_meta": {
      "openai/message": {
        "target": "new",
        "send": false
      }
    }
  }
}

Opening Local Files

Typically, an MCP App will not have access to raw filesystem paths. However, if its MCP Server provides an absolute filesystem path, it can request that ChatGPT open that file.

Capability Advertisement

MCP Apps receive hostCapabilities["experimental"]["openai/files"]: {} in the ui/initialize result when opening local files is available.

MCP App initialize result:

{
  "jsonrpc": "2.0",
  "id": 0,
  "result": {
    "protocolVersion": "2026-01-26",
    "hostInfo": { "name": "example-host", "version": "1.0.0" },
    "hostCapabilities": {
      "experimental": {
        "openai/files": {}
      }
    },
    "hostContext": {}
  }
}

Schema

interface FileOpenParams {
  /** Absolute filesystem path on the execution host. */
  path: string;
}

Examples

Request:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "openai/files/open",
  "params": {
    "path": "/workspace/parts/hex-bolt.stl"
  }
}

Response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {}
}

Composer At-Mentions

Normally, users can only at-mention a plugin or MCP Server. The at-mention capability allows your plugin to expose a searchable list of items (people, files, channels, etc.) that the user can mention individually in their prompt.

CAD Parts Library results in the composer at-mention picker

Behavior Details

  • The at-mention tool is responsible for handling the at-mention request, which includes a typeahead search query, and returning items for the user to choose from.
  • Returned items may be resource links.
  • Servers advertise openai/mentions: { searchTool: string } through server capabilities. searchTool is the non-blank name of a read-only tool on that server.
  • The tool named by searchTool MUST set annotations.readOnlyHint to true.
  • At-mention tools MUST include "app" in _meta["ui"]["visibility"].

Deprecated: _meta["openai/extensions"]["mentions/search"]: {} remains supported when the openai/mentions capability is absent. Advertise the capability instead.

Examples

Mention-search capability:

{
  "extensions": {
    "openai/mentions": { "searchTool": "cad.mentions" }
  }
}

Search Parameters

The at-mention tool accepts these arguments:

Schema

interface MentionSearchParams {
  /** Search text, which may be empty. */
  query: string;
}
interface MentionSearchResult {
  items: ResourceLink[];
}

Examples

Request:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": { "name": "cad.mentions", "arguments": { "query": "hex-bolt" } }
}

Response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [],
    "structuredContent": {
      "items": [
        {
          "type": "resource_link",
          "uri": "cad://parts/hex-bolt",
          "name": "hex-bolt"
        }
      ]
    }
  }
}

OpenAI Form Elicitation

Extended forms expand on MCP form elicitation with additional elicitation types and display modes.

NOTE: OpenAI-registered MCP servers require MCP 2026-07-28 or later with multi-round-trip requests (MRTR) for form elicitation. Direct MCP connections still support legacy forms.

Capability Advertisement

MCP Host initialize request (MCP 2025-11-25):

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-11-25",
    "capabilities": {
      "extensions": {
        "openai/elicitation": {
          "form": {}
        }
      }
    },
    "clientInfo": { "name": "example-host", "version": "1.0.0" }
  }
}

Behavior Details

  • On connections that negotiate 2026-07-28 or later, MCP Servers SHOULD use multi-round-trip elicitation.
  • On connections that negotiate an earlier protocol version, MCP Servers MUST use openai/elicitation/create.
  • openai/elicitation/create is a superset of elicitation/create.
  • Forms containing unsupported input types are reported as unsupported. They are not partially displayed.

Multi-Round-Trip Elicitation

This section extends multi-round-trip requests for form elicitation.

Behavior Details

  • MCP Servers MUST require both elicitation.form: {} and extensions["openai/elicitation"].form: {} in the request's client capabilities.
  • MCP Servers MUST specify an empty object requestedSchema in elicitation/create inputRequests:
    • requestedSchema: { "type": "object", "properties": {} }.
  • MCP Servers MUST put the requestedSchema in _meta["openai/elicitation"]["requestedSchema"]:
  • Missing or unsupported schemas will be reported as unsupported without displaying the empty core form.

Examples

Multi round-trip input_required response (2026-07-28 or later):

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "resultType": "input_required",
    "inputRequests": {
      "details": {
        "method": "elicitation/create",
        "params": {
          "mode": "form",
          "message": "Enter a project code",
          "requestedSchema": { "type": "object", "properties": {} },
          "_meta": {
            "openai/elicitation": {
              "requestedSchema": {
                "type": "object",
                "properties": {
                  "resource": {
                    "type": "string",
                    "format": "uri",
                    "x-openai-input": {
                      "type": "resource",
                      "options": [],
                      "userOptions": {}
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

Legacy Elicitation Request

On legacy connections (2025-11-25 or older), openai/elicitation/create is a drop-in replacement for elicitation/create.

Examples

Request:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "openai/elicitation/create",
  "params": {
    "mode": "form",
    "message": "Choose a CAD part to inspect",
    "requestedSchema": {
      "type": "object",
      "properties": {
        "resource": {
          "type": "string",
          "format": "uri",
          "x-openai-input": {
            "type": "resource",
            "options": [],
            "userOptions": {}
          }
        }
      }
    }
  }
}

Response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "action": "accept",
    "content": { "resource": "file:///parts/hex-bolt.stl" }
  }
}

Numeric Answers

Hosts MUST submit answers for integer and number fields as JSON numbers.

Servers that require exact numeric text, such as large integers or high-precision decimals, MUST request a string field. Hosts MUST preserve those string answers without numeric conversion. Servers are responsible for parsing and validating the text, and MAY use pattern to constrain its format.

Extended String Schema

Servers MAY add pattern to MCP's StringSchema.

Examples

Request:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "openai/elicitation/create",
  "params": {
    "mode": "form",
    "message": "Choose a CAD part to inspect",
    "requestedSchema": {
      "type": "object",
      "properties": {
        "part": {
          "type": "string",
          "title": "Reference file",
          "format": "uri",
          "pattern": "^(cad|file):"
        }
      }
    }
  }
}

Option Descriptions

Titled const options support an optional description.

Examples

part field with supporting text for an option (description):

{
  "type": "string",
  "oneOf": [
    {
      "const": "hex-bolt",
      "title": "M6 hex bolt",
      "description": "A fastener for the main joint."
    }
  ]
}

Thumbnails

Thumbnails allow you to specify an image as the user-visible choice instead of just text.

Select with thumbnails

Behavior Details

  • Titled const options support an optional x-openai-thumbnail: MCP.Icon.
  • Servers MUST use an HTTPS URL or base64-encoded image data URI for src.
  • Thumbnails use MCP's Icon type.
  • If ANY items for a given property have a thumbnail, ALL items for that property will be rendered with an image UI.
    • Items without a thumbnail will render a fallback image.

Examples

Request:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "openai/elicitation/create",
  "params": {
    "mode": "form",
    "message": "Choose a CAD part to inspect",
    "requestedSchema": {
      "type": "object",
      "properties": {
        "part": {
          "type": "string",
          "title": "Part",
          "oneOf": [
            {
              "const": "hex-bolt",
              "title": "M6 hex bolt",
              "x-openai-thumbnail": {
                "src": "https://example.com/hex-bolt.png"
              }
            },
            {
              "const": "washer",
              "title": "M6 washer",
              "x-openai-thumbnail": { "src": "https://example.com/washer.png" }
            }
          ]
        }
      }
    }
  }
}

Suggested Values

Suggested values let users choose from predefined options or enter free-form text. Array fields support selecting multiple options and adding custom text. The same field constraints apply to suggested and entered values.

Suggested values and a custom entry in the Bits & Bolts review form

Behavior Details

  • MCP Servers MAY add x-openai-suggestions to a string field or an array's string items schema. Each suggestion uses the same titled const option format as option descriptions.

Examples

part field with a suggested value:

{
  "type": "string",
  "minLength": 1,
  "x-openai-suggestions": [
    {
      "const": "hex-bolt",
      "title": "M6 hex bolt"
    }
  ]
}

accessories field with suggested values:

{
  "type": "array",
  "items": {
    "type": "string",
    "x-openai-suggestions": [
      {
        "const": "washer",
        "title": "M6 washer"
      }
    ]
  }
}

Elicitation result with suggested and entered values:

{
  "action": "accept",
  "content": {
    "accessories": ["washer", "custom-spacer", "custom-gasket"]
  }
}

Resource Selection

Resource selection supports supplied resources, user-added files or directories, or both.

Resource picker

Behavior Details

  • On Web:
    • Directory selection is unsupported.
    • Forms requested through MCP Apps support only explicit resource selection without user uploads.
  • Servers declare resource inputs by adding an x-openai-input to a string (single-select) or array (multi-select) form field.
  • Single-select fields submit a URI string.
  • Multi-select fields submit an array of URI strings.
  • Implicit selection always allows user uploads. When userOptions is omitted, it defaults to { kind: "file" } with no file-type restrictions. Explicit selection shows no upload input when userOptions is omitted.
  • Resource options may include a thumbnail (_meta["openai/thumbnail"]: MCP.Icon), a preview (_meta["openai/preview"]: { target: PreviewTarget }), or both.
Explicit vs Implicit Selection
Mode Behavior
"explicit" Items are chosen by selecting or deselecting them.
"implicit" Items are chosen by adding or removing them. All items that remain when the form is submitted are included in the result.
  • Selection modes apply only to multi-select (type: array) fields.
  • Servers MUST NOT specify selection mode for single-selection fields.
  • Servers MUST use only URIs from options for resource selection defaults.
  • Servers MUST NOT specify default with "implicit" selection.

Schema

/** x-openai-input */
interface ResourceInput {
  type: "resource";
  /** Multi-select only. Defaults to "explicit". */
  selection?: "explicit" | "implicit";
  /** Server-provided entries. May be empty. */
  options: MCP.Resource[];
  /** Configures user-added resources. */
  userOptions?: UserResourceOptions;
}

interface UserResourceOptions {
  /** Defaults to "file". */
  kind?: "file" | "directory";
  /**
   * Allowed filename extensions or MIME types, including MIME wildcards.
   * https://html.spec.whatwg.org/multipage/input.html#attr-input-accept
   */
  accept?: string[];
}

Examples

Request (single selection):

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "openai/elicitation/create",
  "params": {
    "mode": "form",
    "message": "Choose a CAD part to inspect",
    "requestedSchema": {
      "type": "object",
      "properties": {
        "part": {
          "type": "string",
          "title": "Reference file",
          "format": "uri",
          "x-openai-input": {
            "type": "resource",
            "options": [
              {
                "uri": "cad://parts/hex-bolt",
                "name": "hex-bolt",
                "title": "M6 hex bolt",
                "_meta": {
                  "openai/thumbnail": {
                    "src": "https://example.com/hex-bolt.png"
                  },
                  "openai/preview": {
                    "target": {
                      "type": "mcp_app_tool",
                      "name": "cad.open",
                      "arguments": {
                        "file": {
                          "name": "hex-bolt.stl",
                          "resourceUri": "cad://parts/hex-bolt"
                        }
                      }
                    }
                  }
                }
              },
              {
                "uri": "cad://parts/washer",
                "name": "washer",
                "title": "M6 washer"
              }
            ],
            "userOptions": { "kind": "file", "accept": [".stl", ".step"] }
          },
          "default": "cad://parts/hex-bolt"
        }
      }
    }
  }
}

Request (explicit selection):

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "openai/elicitation/create",
  "params": {
    "mode": "form",
    "message": "Choose CAD parts to inspect",
    "requestedSchema": {
      "type": "object",
      "properties": {
        "parts": {
          "type": "array",
          "items": { "type": "string", "format": "uri" },
          "x-openai-input": {
            "type": "resource",
            "selection": "explicit",
            "options": [
              {
                "uri": "cad://parts/hex-bolt",
                "name": "hex-bolt",
                "title": "M6 hex bolt"
              },
              {
                "uri": "cad://parts/washer",
                "name": "washer",
                "title": "M6 washer"
              }
            ]
          },
          "default": ["cad://parts/hex-bolt"]
        }
      }
    }
  }
}

Request (implicit selection):

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "openai/elicitation/create",
  "params": {
    "mode": "form",
    "message": "Choose CAD parts to inspect",
    "requestedSchema": {
      "type": "object",
      "properties": {
        "parts": {
          "type": "array",
          "items": { "type": "string", "format": "uri" },
          "x-openai-input": {
            "type": "resource",
            "selection": "implicit",
            "options": [
              {
                "uri": "cad://parts/hex-bolt",
                "name": "hex-bolt",
                "title": "M6 hex bolt"
              },
              {
                "uri": "cad://parts/washer",
                "name": "washer",
                "title": "M6 washer"
              }
            ]
          }
        }
      }
    }
  }
}

Previews

Previews open expanded details for resource options.

Behavior Details
  • MCP Servers MAY add _meta["openai/preview"]: { target: PreviewTarget } to resource options, with or without a thumbnail.
Schema
// Opens on the originating server.
type PreviewTarget =
  | {
      type: "mcp_app_tool";
      // Non-blank name of the MCP App tool to call.
      name: string;
      // Defaults to {}.
      arguments?: Record<string, unknown>;
    }
  // Reads the linked MCP resource.
  | MCP.ResourceLink;
Examples

Resource option with a preview:

{
  "uri": "cad://parts/hex-bolt",
  "name": "M6 hex bolt",
  "_meta": {
    "openai/preview": {
      "target": {
        "type": "resource_link",
        "uri": "cad://parts/hex-bolt",
        "name": "M6 hex bolt"
      }
    }
  }
}