vercel/ai · error · Error

Unsupported MCP App resource content format: ${uri}

Error message

Unsupported MCP App resource content format: ${uri}

What it means

Thrown by getMCPAppResourceFromReadResult when the content entry has the correct MCP App MIME type but contains neither a 'text' string nor a 'blob' base64 string, so no HTML can be extracted. The library requires renderable HTML payload from the resource.

Source

Thrown at packages/mcp/src/tool/mcp-apps.ts:257

  if (content == null) {
    throw new Error(`MCP App resource not found in read result: ${uri}`);
  }

  if (content.mimeType !== MCP_APP_MIME_TYPE) {
    throw new Error(
      `Unsupported MCP App resource MIME type: ${content.mimeType}`,
    );
  }

  const html =
    'text' in content && typeof content.text === 'string'
      ? content.text
      : 'blob' in content && typeof content.blob === 'string'
        ? new TextDecoder().decode(convertBase64ToUint8Array(content.blob))
        : undefined;

  if (html == null) {
    throw new Error(`Unsupported MCP App resource content format: ${uri}`);
  }

  const meta = getResourceUiMeta(content._meta);

  return { uri, mimeType: MCP_APP_MIME_TYPE, html, meta };
}

/**
 * Reads a `ui://` resource from an MCP server and normalizes it for rendering.
 */
export async function readMCPAppResource({
  client,
  uri,
  options,
}: {
  client: Pick<MCPClient, 'readResource'>;
  uri: string;
  options?: RequestOptions;

View on GitHub (pinned to 69428b1f8b)

Solutions

  1. Fix the MCP server to include the HTML in content.text (or base64 in content.blob) for the ui:// resource.
  2. Log the full read result to inspect what the server actually returned.
  3. Update the MCP server implementation/SDK version so the resource body is serialized correctly.
  4. If the payload is large, ensure the transport (e.g. SSE/HTTP) is not truncating or dropping the content.

Example fix

// before (server response missing body)
{ uri: 'ui://widget/gauge', mimeType: 'text/html;profile=mcp-app' }
// after
{ uri: 'ui://widget/gauge', mimeType: 'text/html;profile=mcp-app', text: '<html>...</html>' }
Defensive patterns

Strategy: validation

Validate before calling

function hasRenderableBody(c: { text?: unknown; blob?: unknown }) {
  return typeof c.text === 'string' || typeof c.blob === 'string';
}

Type guard

function hasHtmlPayload(c: unknown): c is { text: string } | { blob: string } {
  return !!c && typeof c === 'object' && (('text' in c && typeof (c as any).text === 'string') || ('blob' in c && typeof (c as any).blob === 'string'));
}

Try / catch

try {
  const app = await readMCPAppResource({ client, uri });
} catch (e) {
  if (e instanceof Error && e.message.startsWith('Unsupported MCP App resource content format')) {
    // treat as empty/missing app, use placeholder UI
  } else throw e;
}

Prevention

When it happens

Trigger: A read result content matches the requested uri and has mimeType MCP_APP_MIME_TYPE, but is missing both the text and blob fields (or they are of the wrong type), leaving html === undefined.

Common situations: A buggy or partially-implemented MCP server returns an empty resource entry; a proxy strips the text field; the server sends binary content with malformed base64 encoding fields omitted; serialization drops the body.

Related errors


AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30). Data as JSON: /api/errors/24d614f76137433e. Report an issue: GitHub.