vercel/ai · warning · Error

Invalid ui/open-link url: ${params.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.

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)

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.