vercel/ai · error · Error

Tool is not app-visible: ${params.name}

Error message

Tool is not app-visible: ${params.name}

What it means

The MCP Apps bridge is deny-by-default: an app iframe may only invoke tools explicitly listed in the host's `allowedTools` array. This error is thrown when the app requests a tool that either is not in `allowedTools` or when `allowedTools` was omitted entirely (which exposes no tools). It prevents untrusted frames from invoking arbitrary server tools.

Source

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

            ...(this.handlers.onLog != null ? { logging: {} } : {}),
          },
          hostInfo: this.hostInfo,
          hostContext: this.hostContext,
        };

      case 'tools/call': {
        if (this.handlers.callTool == null) {
          throw new Error('No tools/call handler configured');
        }
        const params = assertToolCallParams(request.params);
        // Deny-by-default: the (untrusted) MCP App may only invoke tools the
        // host has explicitly allow-listed. Omitting `allowedTools` exposes no
        // tools, rather than forwarding every requested tool to `callTool`.
        if (
          this.handlers.allowedTools == null ||
          !this.handlers.allowedTools.includes(params.name)
        ) {
          throw new Error(`Tool is not app-visible: ${params.name}`);
        }
        return this.handlers.callTool(params);
      }

      case 'resources/read':
        if (this.handlers.readResource == null) {
          throw new Error('No resources/read handler configured');
        }
        return this.handlers.readResource(
          assertResourceReadParams(request.params),
        );

      case 'resources/list':
        if (this.handlers.listResources == null) {
          throw new Error('No resources/list handler configured');
        }
        return this.handlers.listResources(request.params);

View on GitHub (pinned to 69428b1f8b)

Solutions

  1. Add the requested tool's exact name to `handlers.allowedTools` in the MCPAppBridge configuration.
  2. Remember that omitting `allowedTools` exposes zero tools — you must opt in explicitly.
  3. Verify the tool name string matches exactly (case, spacing) between app and allow-list.
  4. If the tool should not be app-visible, fix the app to use an allow-listed tool instead.

Example fix

// before
handlers: { allowedTools: ['search'], callTool: p => client.callTool(p) }
// after
handlers: { allowedTools: ['search', 'refreshDashboardData'], callTool: p => client.callTool(p) }
Defensive patterns

Strategy: validation

Validate before calling

// host-side, before constructing the bridge, keep allow-list in sync with the app:
const APP_VISIBLE_TOOLS = ['search', 'refreshDashboardData'] as const;
const handlers = {
  allowedTools: [...APP_VISIBLE_TOOLS],
  callTool: (params: { name: string }) => {
    if (!(APP_VISIBLE_TOOLS as readonly string[]).includes(params.name)) {
      throw new Error(`Tool ${params.name} not exposed to apps`);
    }
    return client.callTool(params);
  },
};

Type guard

function isAllowedTool(name: string, allowedTools: string[] | undefined): boolean {
  return allowedTools != null && allowedTools.includes(name);
}

Try / catch

try {
  await callTool({ name: toolName, arguments });
} catch (error) {
  if (error instanceof Error && error.message.startsWith('Tool is not app-visible:')) {
    const tool = error.message.split(': ')[1];
    console.warn(`Tool "${tool}" is not in handlers.allowedTools; add it to expose it to the app`);
  }
}

Prevention

When it happens

Trigger: The iframe calls `tools/call` with `name: 'deleteUser'` but the host configured `allowedTools: ['search']`; or the host never set `allowedTools`, so every tool call is rejected.

Common situations: New tool added to the app but not added to the host allow-list; tool renamed in the app while the allow-list kept the old name; developer assumed tools were exposed by default and omitted `allowedTools`; case-sensitivity mismatch between tool names.

Related errors


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