vercel/ai · error · Error

resources/read is limited to ui:// resources: ${params.uri}

Error message

resources/read is limited to ui:// resources: ${params.uri}

What it means

This is a deliberate security restriction: the bridge only allows MCP App iframes to read `ui://` scheme resources via `resources/read`. Any other scheme (e.g. `https://`, `file://`) is rejected. This keeps untrusted app frames from exfiltrating or reading arbitrary server resources.

Source

Thrown at packages/react/src/mcp-apps/bridge.ts:74

  if (!isJSONObject(params) || typeof params.name !== 'string') {
    throw new Error('Invalid tools/call params');
  }

  return {
    name: params.name,
    arguments: isJSONObject(params.arguments) ? params.arguments : undefined,
  };
}

/**
 * Validates `resources/read` params and limits reads to `ui://` app resources.
 */
function assertResourceReadParams(params: unknown): { uri: string } {
  if (!isJSONObject(params) || typeof params.uri !== 'string') {
    throw new Error('Invalid resources/read params');
  }
  if (!params.uri.startsWith('ui://')) {
    throw new Error(
      `resources/read is limited to ui:// resources: ${params.uri}`,
    );
  }
  return { uri: params.uri };
}

/**
 * Validates `ui/open-link` params and allows only `https:`/`http:`/`mailto:`
 * URLs.
 */
function assertOpenLinkParams(params: unknown): { url: string } {
  if (!isJSONObject(params) || typeof params.url !== 'string') {
    throw new Error('Invalid ui/open-link params');
  }

  let scheme: string;
  try {
    scheme = new URL(params.url).protocol;

View on GitHub (pinned to 69428b1f8b)

Solutions

  1. Serve app-visible resources with a `ui://` URI so the iframe can read them.
  2. If the host needs the app to access other data, expose it through an allow-listed tool via `tools/call` instead of resources/read.
  3. Fetch non-ui resources on the host side and pass them to the iframe via tool results or `ui/update-model-context`.
  4. Confirm the resource URI was not truncated or mis-prefixed by the app.

Example fix

// before
readResource({ uri: 'https://cdn.example.com/widget.json' })
// after
readResource({ uri: 'ui://widget/config' })
Defensive patterns

Strategy: validation

Validate before calling

// app-side, before requesting:
if (!uri.startsWith('ui://')) {
  throw new Error(`App may only read ui:// resources, got: ${uri}`);
}

Type guard

function isUiResourceUri(uri: string): boolean {
  return uri.startsWith('ui://');
}

Try / catch

try {
  await readResource({ uri });
} catch (error) {
  if (error instanceof Error && error.message.startsWith('resources/read is limited to ui://')) {
    // fall back to fetching the data through an allow-listed tool instead
    await callTool({ name: 'getData', arguments: { uri } });
  }
}

Prevention

When it happens

Trigger: The iframe calls `resources/read` with a valid string `uri` that does not start with `ui://`, e.g. `https://example.com/data` or `server://logs`.

Common situations: App tries to read regular MCP server resources that were never exposed for app use; developer expects all server resources to be readable from the iframe; migrating an existing MCP client integration that used `https://` or custom scheme URIs.

Related errors


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