basic-host: filter app-only tools, prioritize UI tools (#262)

* basic-host: filter non-ui tools

* basic-host: filter app-only tools, prioritize UI tools

- Filter out tools with visibility: ['app'] from host tool list
- Sort tools to show UI-enabled tools first, then alphabetically
- Add local isToolVisibleToModel helper in basic-host
This commit is contained in:
Olivier Chafik 2026-01-13 15:05:12 +00:00 • committed by GitHub
commit 8fa2961ffc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,9 +1,30 @@
import { getToolUiResourceUri, McpUiToolMetaSchema } from "@modelcontextprotocol/ext-apps/app-bridge";
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
import { Component, type ErrorInfo, type ReactNode, StrictMode, Suspense, use, useEffect, useMemo, useRef, useState } from "react";
import { createRoot } from "react-dom/client";
import { callTool, connectToServer, hasAppHtml, initializeApp, loadSandboxProxy, log, newAppBridge, type ServerInfo, type ToolCallInfo, type ModelContext, type AppMessage } from "./implementation";
import styles from "./index.module.css";
/**
* Check if a tool is visible to the model (not app-only).
* Tools with `visibility: ["app"]` should not be shown in tool lists.
*/
function isToolVisibleToModel(tool: { _meta?: Record<string, unknown> }): boolean {
const result = McpUiToolMetaSchema.safeParse(tool._meta?.ui);
if (!result.success) return true; // default: visible to model
const visibility = result.data.visibility;
if (!visibility) return true; // default: visible to model
return visibility.includes("model");
}
/** Compare tools: UI-enabled first, then alphabetically by name. */
function compareTools(a: Tool, b: Tool): number {
const aHasUi = !!getToolUiResourceUri(a);
const bHasUi = !!getToolUiResourceUri(b);
if (aHasUi && !bHasUi) return -1;
if (!aHasUi && bHasUi) return 1;
return a.name.localeCompare(b.name);
}
/**
* Extract default values from a tool's JSON Schema inputSchema.
@ -80,7 +101,13 @@ function CallToolPanel({ serversPromise, addToolCall }: CallToolPanelProps) {
const [selectedTool, setSelectedTool] = useState("");
const [inputJson, setInputJson] = useState("{}");
const toolNames = selectedServer ? Array.from(selectedServer.tools.keys()) : [];
// Filter out app-only tools, prioritize tools with UIs
const toolNames = selectedServer
? Array.from(selectedServer.tools.values())
.filter((tool) => isToolVisibleToModel(tool))
.sort(compareTools)
.map((tool) => tool.name)
: [];
const isValidJson = useMemo(() => {
try {
@ -93,10 +120,14 @@ function CallToolPanel({ serversPromise, addToolCall }: CallToolPanelProps) {
const handleServerSelect = (server: ServerInfo) => {
setSelectedServer(server);
const [firstTool] = server.tools.keys();
setSelectedTool(firstTool ?? "");
// Filter out app-only tools, prioritize tools with UIs
const visibleTools = Array.from(server.tools.values())
.filter((tool) => isToolVisibleToModel(tool))
.sort(compareTools);
const firstTool = visibleTools[0]?.name ?? "";
setSelectedTool(firstTool);
// Set input JSON to tool defaults (if any)
setInputJson(getToolDefaults(server.tools.get(firstTool ?? "")));
setInputJson(getToolDefaults(server.tools.get(firstTool)));
};
const handleToolSelect = (toolName: string) => {