MCP Apps

Apus can render tools that implement the stable MCP Apps extension. Apps are opt-in because their HTML comes from the configured MCP server. A view with current App-interaction authority can request app-visible tools and resources from that same server.

Enable the host bridge:

apus config set mcp.apps.enabled true --strict-json

Restart the Gateway after changing this setting. When enabled, Apus starts a sandbox-only HTTP(S) listener on the Gateway port plus one (for the default Gateway, 18790). The Control UI loads Apps from that separate origin; the listener never serves Control UI, authenticated Gateway routes, or user data.

Direct Gateway connections need access to both ports. If a reverse proxy or TLS terminator exposes the Control UI, give Apps a dedicated public origin and proxy only that origin to the sandbox listener:

{
  mcp: {
    apps: {
      enabled: true,
      sandboxOrigin: "https://mcp-apps.example.com",
      sandboxPort: 18790,
    },
  },
}

The sandbox origin must differ from the Control UI origin. Do not host other authenticated or sensitive content on it.

For example, the official basic React demo can be configured as:

{
  mcp: {
    apps: { enabled: true },
    servers: {
      "basic-react": {
        command: "npx",
        args: ["-y", "@modelcontextprotocol/server-basic-react", "--stdio"],
      },
    },
  },
}

Plugin extensions

The Control UI also recognizes OpenAI MCP Plugin Extensions. These extend the same MCP Apps host; they do not install a second plugin runtime or grant access to ChatGPT accounts. Connect the plugin's MCP server and enable Apps using the setting above. Existing server enablement, session tool access, and account permissions still apply.

Extensions let a server contribute:

A server advertises entrypoints on its normal tool descriptor:

{
  "name": "parts.library",
  "title": "Parts library",
  "inputSchema": { "type": "object", "properties": {} },
  "_meta": {
    "ui": { "resourceUri": "ui://parts/library" },
    "openai/ui": { "entrypoints": [{ "type": "global" }, { "type": "thread" }] }
  }
}

Global and thread entrypoint tools accept {}. File entrypoints declare extensions with a leading dot, such as .stl, and receive the selected file's name and host-issued resource URI. The host supplies the initial tool result to the app; the app should render that result instead of repeating the opening tool call.

App resource metadata can declare supported and preferred display modes. Apps must inspect the actual host capabilities before using an extension: a standalone channel window does not have every capability of a connected Control UI conversation. Do not infer file, messaging, or model-context authority from a successful MCP connection alone.

When an App asks to send a message to the assistant, review its preview and choose Send or Cancel in the App pane's confirmation strip.

File saves follow the extension protocol’s optional ifMatch precondition. Sending the ETag from the last read prevents a stale save from replacing a newer edit; omitting ifMatch performs an unconditional save (last writer wins). App authors should send the ETag when protecting concurrent edits. Both forms still require a writable read, the host-issued file URI, and current session and requester authority.

Native Codex Apps borrow the conversation’s existing MCP connection and retain its approval policy. Interactive forms require a native policy that allows prompting; see Rich MCP forms. Only one native App tool call per server and conversation can be active at a time; wait for it to finish before starting another. Each view also enforces limits of four simultaneous requests, 120 requests per minute, and 30 tool calls per minute. Background preview generation consumes the same limits as user-triggered calls.

The extensions use the existing sandbox and permission boundaries below. Server-owned settings and plugin data remain with their existing owners. Raw app state is not a new durable Gateway store, and a reconstructed transcript preview is not a fresh grant to run tools.

Tool approvals

When an App tool call needs approval, choose Allow once, Allow while this App is open, or Deny. Allow once approves only that call. Allow while this App is open lets the same requester call that exact server/tool pair again from the same current view without another prompt.

The grant lives only in the view's ten-minute in-memory lease. It ends when the lease is released, expires, or is replaced; relaunching or reconstructing a view does not carry it forward. A different tool, view, session, or requester needs its own approval. The grant never updates configuration or a persistent tool allowlist, and it does not approve model-driven calls.

Current server and session policies still apply and are checked again before execution. Calls that the server's approval mode already permits do not prompt. Calls made before a view exists, such as opening an entrypoint, retain Allow once and Deny.

Behavior and security boundaries