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
- Serve app-visible resources with a `ui://` URI so the iframe can read them.
- If the host needs the app to access other data, expose it through an allow-listed tool via `tools/call` instead of resources/read.
- Fetch non-ui resources on the host side and pass them to the iframe via tool results or `ui/update-model-context`.
- 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
- Publish app-facing resources only under `ui://` URIs.
- Never expect `https://` or server-scheme resources to be readable from the iframe.
- Route non-ui data needs through allow-listed tools.
- Document the ui:// restriction in your app's resource naming conventions.
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
- Disallowed ui/open-link scheme: ${scheme}
- Tool is not app-visible: ${params.name}
- Invalid tools/call params
- Invalid resources/read params
- Invalid ui/open-link params
AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30).
Data as JSON: /api/errors/a8448714a1a66ce9.
Report an issue: GitHub.