Skip to main content
MCP Apps SDK Tool annotations are standard MCP metadata on a tool definition. They tell hosts what kind of action the tool performs, which helps hosts choose review UI, risk labels, and tool-calling behavior. Annotations are separate from _meta.ui:
FieldAnswersExample
annotationsWhat does this tool do?Read data, delete data, call an external service.
_meta.uiWho can call it, and does it render a View?Model-visible, app-only, linked to ui://orders/view.html.
Use both on MCP App tools. A UI-launching tool can be read-only. An app-only helper can still write data. Visibility does not describe safety.

Annotation Fields

AnnotationUse it whenMCP App guidance
titleYou want a short human-readable tool name.Keep it action-oriented, such as Search Orders or Save Draft.
readOnlyHintThe tool does not modify state.Set true for search, preview, list, load, and filter tools.
destructiveHintThe tool can delete, overwrite, cancel, charge, send, or otherwise cause user-visible harm.Set true for destructive model-visible tools and for destructive app-only helpers called from the View.
idempotentHintRepeating the same call has the same effect as calling it once.Set true only when retries are safe, such as saveDraft with a stable draft id.
openWorldHintThe tool interacts with systems outside the MCP server’s control.Set false for local reads or your own database. Set true for email, payment, web, SaaS, or third-party API actions.
Annotations are hints, not authorization. Your server still needs authentication, permission checks, input validation, and confirmation flows for sensitive actions.

Read-Only UI Tool

A search or dashboard tool usually renders a View and reads server data. Mark it read-only so hosts can treat it as lower risk.
registerAppTool(
  server,
  'search-orders',
  {
    title: 'Search Orders',
    description: 'Search orders and display the results.',
    inputSchema: { query: z.string() },
    annotations: {
      readOnlyHint: true,
      openWorldHint: false,
    },
    _meta: {
      ui: {
        resourceUri: 'ui://orders/view.html',
        visibility: ['model', 'app'],
      },
    },
  },
  async ({ query }) => {
    const result = await searchOrders(query);

    return {
      content: [{ type: 'text', text: `Found ${result.orders.length} orders.` }],
      structuredContent: result,
    };
  }
);

App-Only Write Helper

App-only tools are hidden from the model, but they are still real server tools. If a button in the View writes data, annotate that tool honestly.
registerAppTool(
  server,
  'save-order-note',
  {
    title: 'Save Order Note',
    description: 'Save an order note from the app UI.',
    inputSchema: { orderId: z.string(), note: z.string() },
    annotations: {
      readOnlyHint: false,
      destructiveHint: false,
      idempotentHint: true,
      openWorldHint: false,
    },
    _meta: {
      ui: {
        visibility: ['app'],
      },
    },
  },
  async ({ orderId, note }) => {
    const saved = await saveOrderNote(orderId, note);

    return {
      content: [{ type: 'text', text: 'Order note saved.' }],
      structuredContent: saved,
    };
  }
);

Destructive Tool

If a model-visible tool can cancel, delete, send, purchase, or overwrite something, set destructiveHint: true. Hosts may use this to add confirmation or show stronger review UI.
registerAppTool(
  server,
  'cancel-order',
  {
    title: 'Cancel Order',
    description: 'Cancel an order by id.',
    inputSchema: { orderId: z.string() },
    annotations: {
      readOnlyHint: false,
      destructiveHint: true,
      idempotentHint: true,
      openWorldHint: false,
    },
    _meta: {
      ui: {
        resourceUri: 'ui://orders/cancel.html',
        visibility: ['model', 'app'],
      },
    },
  },
  async ({ orderId }) => {
    const result = await cancelOrder(orderId);

    return {
      content: [{ type: 'text', text: `Order ${orderId} was cancelled.` }],
      structuredContent: result,
    };
  }
);

Checklist

  • Add annotations to every model-visible tool, especially UI-launching tools.
  • Add annotations to app-only tools when they write data or call external systems.
  • Do not use readOnlyHint: true on a tool that writes state, even if the write is small.
  • Set destructiveHint: true for delete, cancel, overwrite, send, purchase, or irreversible actions.
  • Set idempotentHint: true only when server-side retries are safe.
  • Set openWorldHint: true when the tool reaches outside your controlled backend.

Tool and Resource Contract

Check the full server-side contract for MCP App tools and resources.

Tool _meta

Link tools to Views and control model vs app visibility.