Android ChatGPT blocks Apps SDK widget app destructive tool before MCP, while web/iOS show confirmation modal and work

Hi OpenAI team,

I’m seeing an Android-specific issue with a custom ChatGPT App built with the Apps SDK / MCP and a widget UI.

The issue is easiest to reproduce with a destructive tool: delete_zone.

On ChatGPT web and iOS, the native confirmation modal appears every time for this tool, and after approval the MCP server receives the call and the zone is deleted correctly.

On ChatGPT Android, the same tool is blocked before reaching the MCP server.

Environment

  • App type: ChatGPT App / Apps SDK / MCP

  • UI: Apps SDK widget registered with registerAppResource

  • Some tools use registerAppTool

  • App status: dev mode

  • Backend: Node MCP server

  • Platforms where it works:

    • ChatGPT web: confirmation modal appears, then tool executes

    • ChatGPT iOS: confirmation modal appears, then tool executes

  • Platform with issue:

    • ChatGPT Android
  • Android devices tested:

    • Pixel 6a, Android 16

    • another Android device with the same behavior

Tool involved

The tool is delete_zone, registered in the same MCP server as the widget app.

It is a destructive tool and is annotated accordingly:

annotations: {
readOnlyHint: false,
openWorldHint: false,
destructiveHint: true,
}

The tool is expected to delete an existing zone from an App map after user confirmation.

Expected behavior

On Android, ChatGPT should behave like web and iOS:

  1. The assistant prepares the delete_zone call.

  2. ChatGPT displays the native confirmation modal.

  3. The user approves.

  4. The MCP server receives the delete_zone tool call.

  5. The zone is deleted.

Actual behavior on Android

On Android, the confirmation flow fails before the tool reaches the MCP server.

The call is blocked by ChatGPT/OpenAI safety controls, or the confirmation UI does not complete correctly.

When this happens:

  • no delete_zone request reaches the MCP server;

  • there is no MCP server log for the tool call;

  • the zone is not deleted;

  • the assistant reports that it could not execute the tool.

Behavior on web and iOS

On web and iOS, the same delete_zone flow works consistently:

  • the native confirmation modal appears every time;

  • after approval, the MCP server receives the call;

  • the tool executes successfully.

This makes the issue appear Android-specific, not backend-specific.

Important comparison

Other tools in the same app can execute on Android when no native confirmation modal is triggered.

For example, a simple create_map flow can succeed on Android.

So MCP authentication and basic tool execution work on Android. The issue appears specifically related to Android’s native confirmation / safety flow for destructive tools in a widget-based Apps SDK app.

Widget context

This is not a plain MCP-only app. The app includes a widget UI:

  • widget resource registered with registerAppResource

  • map visualization rendered through the widget

  • tools such as visualize_map use widget metadata

  • some tools are registered with registerAppTool

The delete_zone tool belongs to the same widget-based Apps SDK app.

Reproduction steps

  1. Open ChatGPT Android.

  2. Use the custom Apps SDK app in dev mode.

  3. Ask the app to delete an existing zone, for example:

Delete the zone named [zone name]

  1. ChatGPT prepares the destructive delete_zone tool call.

  2. On web/iOS, the confirmation modal appears and the tool works.

  3. On Android, the flow fails before the MCP server receives the tool call.

MCP server evidence

When the tool works, MCP logs show a delete_zone call.

When it fails on Android, no delete_zone call appears in the MCP logs, which means the block happens before the request reaches the MCP server.

Impact

This breaks destructive actions on Android for the app.

For safety reasons, delete_zone should remain annotated as destructive, so changing the annotation to avoid confirmation is not a good workaround.

Question

Is this a known issue with the ChatGPT Android app and Apps SDK widget apps in dev mode?

Is there a recommended way to make native confirmation modals for destructive tools reliable on Android, or to obtain more diagnostics when a destructive tool call is blocked before reaching the MCP server?

Thanks for the detailed repro @2803MEDIA, this is very helpful.

Based on your logs and cross-platform behavior, this does not look like a general MCP/backend issue. The strongest signal is that on Android, no tools/call for delete_zone ever reaches the MCP server, while the same destructive flow works on web and iOS and non-destructive tools work on Android.

That suggests the failure is likely happening in the Android client before dispatch, possibly in the native confirmation/safety flow for destructive tools.

Kindly provide the following details to help isolate the issue:

  • ChatGPT Android app version
  • Exact Android versions/devices tested
  • Whether the native confirmation modal appears at all on Android
  • If it appears, what happens after approval (dismisses, hangs, error, etc.)
  • Full delete_zone tool descriptor + annotations
  • Whether the tool is assistant-invoked, widget-invoked via window.openai.callTool, or both
  • One successful web/iOS MCP log vs one failed Android attempt
  • Whether a minimal harmless destructive test tool (same annotations, only logs) also fails on Android

The key diagnostic is whether Android ever dispatches tools/call. If it never does, while non-destructive tools continue to work, that strongly points to an Android-specific destructive confirmation/safety-flow issue rather than an MCP server problem.

Please keep destructiveHint: true for the real delete operation.

-Mark G.

Hi Mark, here are the details.

ChatGPT Android app version

- Google Pixel 7, 1.2026.125

- Google Pixel 6a, 1.2026.125

Android versions/devices tested

- Google Pixel 7, Android 17 build CP31.260423.012.A1

- Google Pixel 6a, Android 16 build BP41.250822.010

Whether the native confirmation modal appears at all on Android

For my Isocarto app, no: the native confirmation modal does not appear on Android for the affected tools.

As a comparison, I tested the Dropbox ChatGPT app on the same Android device. Dropbox does show the native confirmation modal correctly. One difference is that Dropbox appears to be a shipped app without my widget flow, while Isocarto is an Apps SDK app with a widget.

Behavior after approval / failure

Since the confirmation modal does not appear for Isocarto on Android, there is no approval step.

Instead, the assistant returns an error before the MCP server receives the tool call. For example, I sometimes get:

“I couldn’t execute debug_write_confirmation_probe: the call was blocked by OpenAI safety controls.”

For delete_zone specifically, the Android failure produces no MCP log at all, which suggests the call is blocked client-side / OpenAI-side before reaching my MCP endpoint.

Full delete_zone tool descriptor + annotations

Here is the tool code:

import { registerAppTool } from "@modelcontextprotocol/ext-apps/server";

import { z } from "zod";

import { fetchClientApi } from "../../helpers/apiClient.js";

import { requireAuth } from "../../middleware/auth.js";




export function registerDeleteZoneTool(server) {

  registerAppTool(

    server,

    "delete_zone",

    {

      title: "Supprimer une zone",

      description: "Supprime une zone de chalandise d'une carte Isocarto.",

      inputSchema: {

        mapId: z.string(),

        zoneId: z.string(),

      },

      annotations: { readOnlyHint: false, openWorldHint: false, destructiveHint: true },

      _meta: {},

    },

    requireAuth(async (args) => {

      try {

        const { mapId, zoneId } = args;

        if (!mapId || !zoneId) throw new Error("mapId et zoneId sont requis.");

        await fetchClientApi(`/zones/delete-zone/${zoneId}`, null, {

          bearerToken: args._mcpAccessToken,

          method: "DELETE",

          body: { mapId },

        });

        const remainingZonesResult = await fetchClientApi("/zones/get-zones", null, {

          bearerToken: args._mcpAccessToken,

          params: { mapId },

        }).catch(() => ({ zones: [] }));

        const remainingZones = remainingZonesResult?.zones || remainingZonesResult || [];

        return {

          content: [{ type: "text", text: "🗑️ Zone supprimée avec succès." }],

          structuredContent: {

            type: "zone_deleted",

            mapId,

            zoneId,

            remainingZones,

            remainingCount: remainingZones.length,

          },

        };

      } catch (err) {

          return {

            isError: true,

            content: [{ type: "text", text: `❌ Erreur : ${err.message}` }],

          structuredContent: { error: err.message },

        };

      }

    }),

  );

}

Invocation mode

The delete_zone tool is assistant-invoked from the conversation. It is not called directly from the widget via window.openai.callTool in this repro.

The app does have a widget for map visualization, but the failing delete_zone call is triggered by the assistant/tool orchestration, not by a direct widget button calling window.openai.callTool.

Successful web/desktop MCP log

On web/desktop, the native confirmation modal appears and the tool reaches the MCP server successfully:

1|isocarto-mcp        | 2026-05-15 16:57:18: 🧭 MCP tool called {

1|isocarto-mcp        | 2026-05-15 16:57:18:   toolName: 'delete_zone',

1|isocarto-mcp        | 2026-05-15 16:57:18:   userId: '252e8aad-64a1-486d-89a4-6ec06f6b8ab0',

1|isocarto-mcp        | 2026-05-15 16:57:18:   organizationId: '80075eaa-1314-4097-bf55-04e31ebbfb47',

1|isocarto-mcp        | 2026-05-15 16:57:18:   clientId: 'nrPRsqWupqcUMVEwpuwcapaRuYPllDym',

1|isocarto-mcp        | 2026-05-15 16:57:18:   sessionId: null,

1|isocarto-mcp        | 2026-05-15 16:57:18:   tokenMode: 'jwt',

1|isocarto-mcp        | 2026-05-15 16:57:18:   args: {

1|isocarto-mcp        | 2026-05-15 16:57:18:     mapId: '7d4b0b82-3941-4621-96f6-03dafeba59f0',

1|isocarto-mcp        | 2026-05-15 16:57:18:     zoneId: 'bfd86904-cfaf-4552-9476-3f1a8e3e34f7',

1|isocarto-mcp        | 2026-05-15 16:57:18:     hasGeometry: false

1|isocarto-mcp        | 2026-05-15 16:57:18:   }

1|isocarto-mcp        | 2026-05-15 16:57:18: }

186|isocarto          | 2026-05-15 16:57:18: [USER] Suppression d'une zone

1|isocarto-mcp        | 2026-05-15 16:57:18: ✅ MCP tool result {

1|isocarto-mcp        | 2026-05-15 16:57:18:   toolName: 'delete_zone',

1|isocarto-mcp        | 2026-05-15 16:57:18:   durationMs: 163,

1|isocarto-mcp        | 2026-05-15 16:57:18:   success: true,

1|isocarto-mcp        | 2026-05-15 16:57:18:   isError: false,

1|isocarto-mcp        | 2026-05-15 16:57:18:   type: 'zone_deleted',

1|isocarto-mcp        | 2026-05-15 16:57:18:   error: null,

1|isocarto-mcp        | 2026-05-15 16:57:18:   mapId: '7d4b0b82-3941-4621-96f6-03dafeba59f0',

1|isocarto-mcp        | 2026-05-15 16:57:18:   zoneId: 'bfd86904-cfaf-4552-9476-3f1a8e3e34f7'

1|isocarto-mcp        | 2026-05-15 16:57:18: }

Failed Android attempt

On Android, there is no MCP log for the failed delete_zone attempt. The tool is not called at all server-side.

Here is a screen (of another tool but the error is the same)

Minimal destructive test tool

I also tested a minimal harmless destructive test tool with similar annotations. It only logs/persists a debug label and does not perform a real deletion. That test also fails on Android in the same way: sometimes blocked by OpenAI safety controls before reaching the MCP server.

Summary

The issue seems specific to ChatGPT Android handling of Apps SDK confirmation/safety flow for my widget app. Web and iOS work. Android can still run some non-confirmation tools, but tools requiring the native confirmation modal, especially destructive tools like delete_zone, fail before reaching MCP.

@2803MEDIA, I can see why this has been frustrating. At this point, the best next step is getting more eyes on it.

We’re escalating this case to our specialized support team for deeper investigation. They’ll review the behavior in more detail and help determine what’s causing it.

If anyone else in the thread is seeing the same thing, sharing repro steps or screenshots can help surface patterns faster.

-Mark G.

thanks @Mark for the follow up!