@@ -78,9 +78,18 @@ type mcpServerParams struct {
7878 TLSDisableSystemCAs * bool
7979}
8080
81+ // MCPAppToolNames is the set of MCP tool names whose results render as
82+ // interactive MCP App (UI) widgets in the chat (the tool declares a
83+ // ui.resourceUri and is visible to the agent). It is used as a set, so the bool
84+ // value is always true and only key presence is meaningful. The agent attaches
85+ // the model-result compaction callback (see agent.MakeMCPAppModelResultCallback)
86+ // only to these tools.
87+ type MCPAppToolNames map [string ]bool
88+
8189// CreateToolsets creates toolsets from all configured HTTP and SSE MCP servers,
82- // returning the accumulated toolsets. Errors on individual servers are logged
83- // and skipped.
90+ // returning the accumulated toolsets and the set of agent-visible tools that
91+ // render as MCP App widgets (see MCPAppToolNames). Errors on individual servers
92+ // are logged and skipped.
8493//
8594// When propagateToken is true, Authorization is forwarded to every MCP server
8695// independently of AllowedHeaders, mirroring the Python ADKTokenPropagationPlugin
@@ -94,10 +103,10 @@ func CreateToolsets(
94103 sseTools []adk.SseMcpServerConfig ,
95104 propagateToken bool ,
96105 headerProvider DynamicHeaderProvider ,
97- ) ([]tool.Toolset , map [ string ] bool ) {
106+ ) ([]tool.Toolset , MCPAppToolNames ) {
98107 log := logr .FromContextOrDiscard (ctx )
99108 var toolsets []tool.Toolset
100- appToolNames := make (map [ string ] bool )
109+ appToolNames := make (MCPAppToolNames )
101110
102111 log .Info ("Processing HTTP MCP tools" , "httpToolsCount" , len (httpTools ))
103112 for i , httpTool := range httpTools {
@@ -153,7 +162,7 @@ func CreateToolsets(
153162}
154163
155164// addToolset logs, initializes, and returns a single MCP toolset.
156- func addToolset (ctx context.Context , log logr.Logger , params mcpServerParams , tools []string , label string , index int ) (tool.Toolset , map [ string ] bool , error ) {
165+ func addToolset (ctx context.Context , log logr.Logger , params mcpServerParams , tools []string , label string , index int ) (tool.Toolset , MCPAppToolNames , error ) {
157166 if params .Headers == nil {
158167 params .Headers = make (map [string ]string )
159168 }
@@ -263,10 +272,53 @@ func createTransport(ctx context.Context, params mcpServerParams) (mcpsdk.Transp
263272 return mcpTransport , nil
264273}
265274
266- // isMCPAppOnlyTool reports whether a tool is callable only from the rendered
267- // MCP app (hidden from the agent). A tool is app-only when its visibility
268- // declares "app" but not "model". Absent visibility defaults to agent-visible.
269- func isMCPAppOnlyTool (meta map [string ]any ) bool {
275+ // mcpToolKind classifies how an MCP tool is surfaced to the agent and the UI,
276+ // derived from the tool's MCP-UI metadata (the "ui" block). Modeling this as a
277+ // single kind rather than a set of overlapping booleans keeps the call sites
278+ // readable and leaves room for additional kinds (e.g. new tool surfaces) without
279+ // reworking signatures.
280+ type mcpToolKind int
281+
282+ const (
283+ // mcpToolKindAgent is a regular tool exposed to the agent/model with no
284+ // interactive UI rendering.
285+ mcpToolKindAgent mcpToolKind = iota
286+ // mcpToolKindApp is an agent-visible tool whose result renders as an
287+ // interactive MCP App (UI) widget in the chat (declares a ui.resourceUri).
288+ mcpToolKindApp
289+ // mcpToolKindAppOnly is hidden from the agent and only callable from within
290+ // the rendered MCP App (visibility declares "app" but not "model").
291+ mcpToolKindAppOnly
292+ )
293+
294+ func (k mcpToolKind ) String () string {
295+ switch k {
296+ case mcpToolKindApp :
297+ return "app"
298+ case mcpToolKindAppOnly :
299+ return "app_only"
300+ default :
301+ return "agent"
302+ }
303+ }
304+
305+ // mcpToolKindOf classifies a tool from its MCP metadata. App-only takes
306+ // precedence: a tool hidden from the model is never surfaced to the agent even
307+ // if it also declares a UI resource.
308+ func mcpToolKindOf (meta map [string ]any ) mcpToolKind {
309+ if isAppOnlyVisibility (meta ) {
310+ return mcpToolKindAppOnly
311+ }
312+ if hasUIResource (meta ) {
313+ return mcpToolKindApp
314+ }
315+ return mcpToolKindAgent
316+ }
317+
318+ // isAppOnlyVisibility reports whether a tool's visibility hides it from the
319+ // agent: it declares "app" but not "model". Absent visibility defaults to
320+ // agent-visible.
321+ func isAppOnlyVisibility (meta map [string ]any ) bool {
270322 ui , _ := meta ["ui" ].(map [string ]any )
271323 if ui == nil {
272324 return false
@@ -283,9 +335,9 @@ func isMCPAppOnlyTool(meta map[string]any) bool {
283335 return hasApp
284336}
285337
286- // isMCPAppTool reports whether a tool declares an MCP-UI resourceUri,
287- // meaning its result should be rendered as an interactive app in the chat UI.
288- func isMCPAppTool (meta map [string ]any ) bool {
338+ // hasUIResource reports whether a tool declares an MCP-UI resourceUri, meaning
339+ // its result should be rendered as an interactive app in the chat UI.
340+ func hasUIResource (meta map [string ]any ) bool {
289341 if ui , ok := meta ["ui" ].(map [string ]any ); ok {
290342 if uri , _ := ui ["resourceUri" ].(string ); uri != "" {
291343 return true
@@ -318,7 +370,7 @@ func normalizeVisibility(value any) []string {
318370// agentVisibleToolFilter lists tools from the MCP server, filters out app-only
319371// tools and any not in the configured allow-list, and returns a predicate the
320372// toolset can apply plus the set of remaining tools that render as MCP apps.
321- func agentVisibleToolFilter (ctx context.Context , params mcpServerParams , configuredFilter map [string ]bool ) (tool.Predicate , map [ string ] bool , error ) {
373+ func agentVisibleToolFilter (ctx context.Context , params mcpServerParams , configuredFilter map [string ]bool ) (tool.Predicate , MCPAppToolNames , error ) {
322374 mcpTransport , err := createTransport (ctx , params )
323375 if err != nil {
324376 return nil , nil , fmt .Errorf ("failed to create transport for %s: %w" , params .URL , err )
@@ -337,20 +389,23 @@ func agentVisibleToolFilter(ctx context.Context, params mcpServerParams, configu
337389 }
338390
339391 allowedTools := make ([]string , 0 , len (result .Tools ))
340- appToolNames := make (map [ string ] bool )
392+ appToolNames := make (MCPAppToolNames )
341393 for _ , t := range result .Tools {
342394 if t == nil || t .Name == "" {
343395 continue
344396 }
345397 if len (configuredFilter ) > 0 && ! configuredFilter [t .Name ] {
346398 continue
347399 }
348- if isMCPAppOnlyTool (t .Meta ) {
400+ switch mcpToolKindOf (t .Meta ) {
401+ case mcpToolKindAppOnly :
402+ // Hidden from the agent; only the rendered MCP App calls it.
349403 continue
350- }
351- allowedTools = append (allowedTools , t .Name )
352- if isMCPAppTool (t .Meta ) {
404+ case mcpToolKindApp :
405+ allowedTools = append (allowedTools , t .Name )
353406 appToolNames [t .Name ] = true
407+ default : // mcpToolKindAgent
408+ allowedTools = append (allowedTools , t .Name )
354409 }
355410 }
356411
@@ -411,7 +466,7 @@ func (rt *headerRoundTripper) RoundTrip(req *http.Request) (*http.Response, erro
411466
412467// initializeToolSet fetches tools from an MCP server using Google ADK's mcptoolset.
413468// Returns the created toolset on success.
414- func initializeToolSet (ctx context.Context , params mcpServerParams , toolFilter map [string ]bool ) (tool.Toolset , map [ string ] bool , error ) {
469+ func initializeToolSet (ctx context.Context , params mcpServerParams , toolFilter map [string ]bool ) (tool.Toolset , MCPAppToolNames , error ) {
415470 mcpTransport , err := createTransport (ctx , params )
416471 if err != nil {
417472 return nil , nil , fmt .Errorf ("failed to create transport for %s: %w" , params .URL , err )
0 commit comments