1313from dash ._layout_utils import find_component
1414from dash .mcp .types import ComponentPropertyInfo , ComponentQueryResult
1515
16+ from .base import MCPToolProvider
17+
1618
1719class _ComponentQueryInput (TypedDict ):
1820 component_id : Annotated [str , Field (description = "The component ID to query" )]
@@ -32,92 +34,94 @@ class _ComponentQueryInput(TypedDict):
3234NAME = "get_dash_component"
3335
3436
35- def get_tool_names () -> set [str ]:
36- return {NAME }
37-
38-
39- def get_tools () -> list [Tool ]:
40- return [_build_tool ()]
41-
42-
43- def _build_tool () -> Tool :
44- return Tool (
45- name = NAME ,
46- description = (
47- "Get a component's properties, values, and tool relationships. "
48- "If property is omitted, returns all defined properties. "
49- "If property is specified, returns only that property. "
50- "See the dash://components resource for available component IDs."
51- ),
52- inputSchema = _INPUT_SCHEMA ,
53- outputSchema = _OUTPUT_SCHEMA ,
54- )
55-
56-
57- def call_tool (tool_name : str , arguments : dict [str , Any ]) -> CallToolResult :
58- comp_id = arguments .get ("component_id" , "" )
59- if not comp_id :
60- raise ValueError ("component_id is required" )
37+ class GetDashComponentTool (MCPToolProvider ):
38+ """Inspects a component's properties and its tool relationships."""
39+
40+ @classmethod
41+ def get_tool_names (cls ) -> set [str ]:
42+ return {NAME }
43+
44+ @classmethod
45+ def list_tools (cls ) -> list [Tool ]:
46+ return [
47+ Tool (
48+ name = NAME ,
49+ description = (
50+ "Get a component's properties, values, and tool relationships. "
51+ "If property is omitted, returns all defined properties. "
52+ "If property is specified, returns only that property. "
53+ "See the dash://components resource for available component IDs."
54+ ),
55+ inputSchema = _INPUT_SCHEMA ,
56+ outputSchema = _OUTPUT_SCHEMA ,
57+ )
58+ ]
6159
62- prop_filter = arguments .get ("property" , "" )
63- component = find_component (comp_id )
60+ @classmethod
61+ def call_tool (cls , tool_name : str , arguments : dict [str , Any ]) -> CallToolResult :
62+ comp_id = arguments .get ("component_id" , "" )
63+ if not comp_id :
64+ raise ValueError ("component_id is required" )
65+
66+ prop_filter = arguments .get ("property" , "" )
67+ component = find_component (comp_id )
68+
69+ if component is None :
70+ callback_map = get_app ().mcp_callback_map
71+ rendering_tools = [
72+ cb .tool_name
73+ for cb in callback_map
74+ if any (out ["component_id" ] == comp_id for out in cb .outputs )
75+ ]
76+ msg = f"Component '{ comp_id } ' not found in static layout."
77+ if rendering_tools :
78+ msg += f" However, the following tools would modify it: { rendering_tools } ."
79+ msg += " Use the dash://components resource to see statically available component IDs."
80+ return CallToolResult (
81+ content = [TextContent (type = "text" , text = msg )],
82+ isError = True ,
83+ )
6484
65- if component is None :
6685 callback_map = get_app ().mcp_callback_map
67- rendering_tools = [
68- cb .tool_name
69- for cb in callback_map
70- if any (out ["component_id" ] == comp_id for out in cb .outputs )
71- ]
72- msg = f"Component '{ comp_id } ' not found in static layout."
73- if rendering_tools :
74- msg += f" However, the following tools would modify it: { rendering_tools } ."
75- msg += " Use the dash://components resource to see statically available component IDs."
76- return CallToolResult (
77- content = [TextContent (type = "text" , text = msg )],
78- isError = True ,
79- )
8086
81- callback_map = get_app ().mcp_callback_map
82-
83- properties : dict [str , ComponentPropertyInfo ] = {}
84- for prop_name in getattr (component , "_prop_names" , []):
85- if prop_filter and prop_name != prop_filter :
86- continue
87-
88- value = callback_map .get_initial_value (f"{ comp_id } .{ prop_name } " )
89- if value is None :
90- value = getattr (component , prop_name , None )
91- if value is None :
92- continue
93-
94- modified_by : list [str ] = []
95- input_to : list [str ] = []
96- id_and_prop = f"{ comp_id } .{ prop_name } "
97- for cb in callback_map :
98- for out in cb .outputs :
99- if out ["id_and_prop" ] == id_and_prop :
100- modified_by .append (cb .tool_name )
101- for inp in cb .inputs :
102- if inp ["id_and_prop" ] == id_and_prop :
103- input_to .append (cb .tool_name )
104-
105- properties [prop_name ] = ComponentPropertyInfo (
106- initial_value = value ,
107- modified_by_tool = modified_by ,
108- input_to_tool = input_to ,
87+ properties : dict [str , ComponentPropertyInfo ] = {}
88+ for prop_name in getattr (component , "_prop_names" , []):
89+ if prop_filter and prop_name != prop_filter :
90+ continue
91+
92+ value = callback_map .get_initial_value (f"{ comp_id } .{ prop_name } " )
93+ if value is None :
94+ value = getattr (component , prop_name , None )
95+ if value is None :
96+ continue
97+
98+ modified_by : list [str ] = []
99+ input_to : list [str ] = []
100+ id_and_prop = f"{ comp_id } .{ prop_name } "
101+ for cb in callback_map :
102+ for out in cb .outputs :
103+ if out ["id_and_prop" ] == id_and_prop :
104+ modified_by .append (cb .tool_name )
105+ for inp in cb .inputs :
106+ if inp ["id_and_prop" ] == id_and_prop :
107+ input_to .append (cb .tool_name )
108+
109+ properties [prop_name ] = ComponentPropertyInfo (
110+ initial_value = value ,
111+ modified_by_tool = modified_by ,
112+ input_to_tool = input_to ,
113+ )
114+
115+ labels = callback_map .component_label_map .get (comp_id , [])
116+
117+ structured : ComponentQueryResult = ComponentQueryResult (
118+ component_id = comp_id ,
119+ component_type = type (component ).__name__ ,
120+ label = labels if labels else None ,
121+ properties = properties ,
109122 )
110123
111- labels = callback_map .component_label_map .get (comp_id , [])
112-
113- structured : ComponentQueryResult = ComponentQueryResult (
114- component_id = comp_id ,
115- component_type = type (component ).__name__ ,
116- label = labels if labels else None ,
117- properties = properties ,
118- )
119-
120- return CallToolResult (
121- content = [TextContent (type = "text" , text = json .dumps (structured , default = str ))],
122- structuredContent = structured ,
123- )
124+ return CallToolResult (
125+ content = [TextContent (type = "text" , text = json .dumps (structured , default = str ))],
126+ structuredContent = structured ,
127+ )
0 commit comments