vercel/ai · warning · Error

Invalid ui/open-link url

Error message

Invalid ui/open-link url: ${params.url}

What it means

The `url` string supplied by the iframe for `ui/open-link` could not be parsed by the `URL` constructor, meaning it is not an absolute, well-formed URL. The bridge rejects it before invoking the host's openLink handler, since relative or garbage strings cannot be safely opened.

Solutions

  1. Make the iframe send an absolute URL including scheme, e.g. `https://example.com/page`.
  2. Resolve relative paths against `window.location.origin` before calling ui/open-link.
  3. Trim/encode the url string and validate it with `new URL(url)` app-side first.
  4. If links come from a backend, fix the backend to emit absolute URLs.

Example fix

// before
openLink({ url: 'example.com/docs' })
// after
openLink({ url: 'https://example.com/docs' })
Defensive patterns

Strategy: validation

Validate before calling

// app-side, before requesting:
let parsed: URL;
try {
  parsed = new URL(url); // throws on relative/malformed urls
} catch {
  parsed = new URL(url, window.location.origin); // resolve relative paths
}

Type guard

function isAbsoluteUrl(value: string): boolean {
  try { new URL(value); return true; } catch { return false; }
}

Try / catch

try {
  await openLink({ url });
} catch (error) {
  if (error instanceof Error && error.message.startsWith('Invalid ui/open-link url')) {
    // retry with an absolutized, trimmed url
    await openLink({ url: new URL(url.trim(), window.location.origin).href });
  }
}

Prevention

When it happens

Trigger: The iframe sends `ui/open-link` with a url like `"example.com/page"` (no scheme), `"/relative/path"`, an empty string, or a string containing spaces/invalid characters that fail `new URL()`.

Common situations: App constructs links from template strings with missing scheme; user-supplied text passed straight through as the url; backend returning relative paths that were never resolved to absolute URLs.

Related errors


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

Appendix: source

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

    );
  }
  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;
  } catch {
    throw new Error(`Invalid ui/open-link url: ${params.url}`);
  }

  if (scheme !== 'https:' && scheme !== 'http:' && scheme !== 'mailto:') {
    throw new Error(`Disallowed ui/open-link scheme: ${scheme}`);
  }

  return { url: params.url };
}

/**
 * Validates params for `ui/request-display-mode`.
 */
function assertDisplayModeParams(params: unknown): {
  mode: 'inline' | 'fullscreen' | 'pip';
} {
  if (
    !isJSONObject(params) ||
    (params.mode !== 'inline' &&

View on GitHub (pinned to 69428b1f8b)