1+ import { getToolUiResourceUri , McpUiToolMetaSchema } from "@modelcontextprotocol/ext-apps/app-bridge" ;
12import type { Tool } from "@modelcontextprotocol/sdk/types.js" ;
23import { Component , type ErrorInfo , type ReactNode , StrictMode , Suspense , use , useEffect , useMemo , useRef , useState } from "react" ;
34import { createRoot } from "react-dom/client" ;
45import { callTool , connectToServer , hasAppHtml , initializeApp , loadSandboxProxy , log , newAppBridge , type ServerInfo , type ToolCallInfo , type ModelContext , type AppMessage } from "./implementation" ;
56import 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