Skip to content

Commit 8fa2961

Browse files
authored
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
1 parent bd892b6 commit 8fa2961

File tree

1 file changed

+35
-4
lines changed

1 file changed

+35
-4
lines changed

examples/basic-host/src/index.tsx

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,30 @@
1+
import { getToolUiResourceUri, McpUiToolMetaSchema } from "@modelcontextprotocol/ext-apps/app-bridge";
12
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
23
import { Component, type ErrorInfo, type ReactNode, StrictMode, Suspense, use, useEffect, useMemo, useRef, useState } from "react";
34
import { createRoot } from "react-dom/client";
45
import { callTool, connectToServer, hasAppHtml, initializeApp, loadSandboxProxy, log, newAppBridge, type ServerInfo, type ToolCallInfo, type ModelContext, type AppMessage } from "./implementation";
56
import styles from "./index.module.css";
67

8+
/**
9+
* Check if a tool is visible to the model (not app-only).
10+
* Tools with `visibility: ["app"]` should not be shown in tool lists.
11+
*/
12+
function isToolVisibleToModel(tool: { _meta?: Record<string, unknown> }): boolean {
13+
const result = McpUiToolMetaSchema.safeParse(tool._meta?.ui);
14+
if (!result.success) return true; // default: visible to model
15+
const visibility = result.data.visibility;
16+
if (!visibility) return true; // default: visible to model
17+
return visibility.includes("model");
18+
}
19+
20+
/** Compare tools: UI-enabled first, then alphabetically by name. */
21+
function compareTools(a: Tool, b: Tool): number {
22+
const aHasUi = !!getToolUiResourceUri(a);
23+
const bHasUi = !!getToolUiResourceUri(b);
24+
if (aHasUi && !bHasUi) return -1;
25+
if (!aHasUi && bHasUi) return 1;
26+
return a.name.localeCompare(b.name);
27+
}
728

829
/**
930
* Extract default values from a tool's JSON Schema inputSchema.
@@ -80,7 +101,13 @@ function CallToolPanel({ serversPromise, addToolCall }: CallToolPanelProps) {
80101
const [selectedTool, setSelectedTool] = useState("");
81102
const [inputJson, setInputJson] = useState("{}");
82103

83-
const toolNames = selectedServer ? Array.from(selectedServer.tools.keys()) : [];
104+
// Filter out app-only tools, prioritize tools with UIs
105+
const toolNames = selectedServer
106+
? Array.from(selectedServer.tools.values())
107+
.filter((tool) => isToolVisibleToModel(tool))
108+
.sort(compareTools)
109+
.map((tool) => tool.name)
110+
: [];
84111

85112
const isValidJson = useMemo(() => {
86113
try {
@@ -93,10 +120,14 @@ function CallToolPanel({ serversPromise, addToolCall }: CallToolPanelProps) {
93120

94121
const handleServerSelect = (server: ServerInfo) => {
95122
setSelectedServer(server);
96-
const [firstTool] = server.tools.keys();
97-
setSelectedTool(firstTool ?? "");
123+
// Filter out app-only tools, prioritize tools with UIs
124+
const visibleTools = Array.from(server.tools.values())
125+
.filter((tool) => isToolVisibleToModel(tool))
126+
.sort(compareTools);
127+
const firstTool = visibleTools[0]?.name ?? "";
128+
setSelectedTool(firstTool);
98129
// Set input JSON to tool defaults (if any)
99-
setInputJson(getToolDefaults(server.tools.get(firstTool ?? "")));
130+
setInputJson(getToolDefaults(server.tools.get(firstTool)));
100131
};
101132

102133
const handleToolSelect = (toolName: string) => {

0 commit comments

Comments
 (0)