1818def register_blueprint_tools (mcp : Any , client : Any , ** kwargs : Any ) -> None :
1919 """Register Home Assistant blueprint management tools."""
2020
21- @mcp .tool (annotations = {"idempotentHint" : True , "readOnlyHint" : True , "tags" : ["blueprint" ], "title" : "List Blueprints" })
22- @log_tool_usage
23- async def ha_list_blueprints (
24- domain : Annotated [
25- str ,
26- Field (
27- description = "Blueprint domain: 'automation' or 'script'" ,
28- default = "automation" ,
29- ),
30- ] = "automation" ,
31- ) -> dict [str , Any ]:
32- """
33- List installed blueprints for a specific domain.
21+ def _format_blueprint_list (blueprints_data : dict [str , Any ], domain : str ) -> dict [str , Any ]:
22+ """Format blueprint data into list response structure.
3423
35- Returns all blueprints available in Home Assistant for the specified domain,
36- including their paths and metadata.
37-
38- EXAMPLES:
39- - List automation blueprints: ha_list_blueprints("automation")
40- - List script blueprints: ha_list_blueprints("script")
24+ Args:
25+ blueprints_data: Raw blueprint data from WebSocket API
26+ domain: Blueprint domain (automation or script)
4127
42- RETURNS:
43- - List of blueprints with path, name, and domain information
44- - Each blueprint includes its relative path for use with ha_get_blueprint
28+ Returns:
29+ Formatted response with blueprints list, count, and domain
4530 """
46- try :
47- # Validate domain
48- valid_domains = ["automation" , "script" ]
49- if domain not in valid_domains :
50- return {
51- "success" : False ,
52- "error" : f"Invalid domain '{ domain } '. Must be one of: { ', ' .join (valid_domains )} " ,
53- "valid_domains" : valid_domains ,
54- }
55-
56- # Send WebSocket command to list blueprints
57- response = await client .send_websocket_message (
58- {"type" : "blueprint/list" , "domain" : domain }
59- )
60-
61- if not response .get ("success" ):
62- return {
63- "success" : False ,
64- "error" : response .get ("error" , "Failed to list blueprints" ),
65- "domain" : domain ,
66- }
67-
68- # Process the blueprint list
69- blueprints_data = response .get ("result" , {})
70-
71- # Convert to a more usable format
72- blueprints = []
73- for path , metadata in blueprints_data .items ():
74- blueprint_info = {
75- "path" : path ,
76- "domain" : domain ,
77- "name" : metadata .get ("name" , path .split ("/" )[- 1 ].replace (".yaml" , "" )),
78- }
79-
80- # Add optional metadata if available
81- if "metadata" in metadata :
82- meta = metadata ["metadata" ]
83- blueprint_info .update ({
84- "description" : meta .get ("description" ),
85- "source_url" : meta .get ("source_url" ),
86- "author" : meta .get ("author" ),
87- })
88-
89- blueprints .append (blueprint_info )
90-
91- return {
92- "success" : True ,
31+ blueprints = []
32+ for bp_path , metadata in blueprints_data .items ():
33+ blueprint_info = {
34+ "path" : bp_path ,
9335 "domain" : domain ,
94- "count" : len (blueprints ),
95- "blueprints" : blueprints ,
36+ "name" : metadata .get ("name" , bp_path .split ("/" )[- 1 ].replace (".yaml" , "" )),
9637 }
9738
98- except Exception as e :
99- logger .error (f"Error listing blueprints: { e } " )
100- return {
101- "success" : False ,
102- "domain" : domain ,
103- "error" : str (e ),
104- "suggestions" : [
105- "Verify Home Assistant connection" ,
106- "Check if blueprint integration is enabled" ,
107- f"Use domain 'automation' or 'script' (got '{ domain } ')" ,
108- ],
109- }
39+ # Add optional metadata if available
40+ if "metadata" in metadata :
41+ meta = metadata ["metadata" ]
42+ blueprint_info .update ({
43+ "description" : meta .get ("description" ),
44+ "source_url" : meta .get ("source_url" ),
45+ "author" : meta .get ("author" ),
46+ })
47+
48+ blueprints .append (blueprint_info )
49+
50+ return {
51+ "success" : True ,
52+ "domain" : domain ,
53+ "count" : len (blueprints ),
54+ "blueprints" : blueprints ,
55+ }
11056
111- @mcp .tool (annotations = {"idempotentHint" : True , "readOnlyHint" : True , "tags" : ["blueprint" ], "title" : "Get Blueprint Details " })
57+ @mcp .tool (annotations = {"idempotentHint" : True , "readOnlyHint" : True , "tags" : ["blueprint" ], "title" : "Get Blueprint" })
11258 @log_tool_usage
11359 async def ha_get_blueprint (
11460 path : Annotated [
115- str ,
61+ str | None ,
11662 Field (
117- description = "Blueprint path (e.g., 'homeassistant/motion_light.yaml' or 'custom/my_blueprint.yaml')"
63+ description = "Blueprint path to get details for (e.g., 'homeassistant/motion_light.yaml'). "
64+ "If omitted, lists all blueprints in the domain." ,
65+ default = None ,
11866 ),
119- ],
67+ ] = None ,
12068 domain : Annotated [
12169 str ,
12270 Field (
@@ -126,17 +74,22 @@ async def ha_get_blueprint(
12674 ] = "automation" ,
12775 ) -> dict [str , Any ]:
12876 """
129- Get detailed information about a specific blueprint .
77+ Get blueprint information - list all blueprints or get details for a specific one .
13078
131- Retrieves the full blueprint configuration including inputs, triggers,
132- conditions, and actions. Use this to understand what a blueprint does
133- and what inputs it requires .
79+ Without a path: Lists all installed blueprints for the specified domain.
80+ With a path: Retrieves full blueprint configuration including inputs, triggers,
81+ conditions, and actions .
13482
13583 EXAMPLES:
136- - Get automation blueprint: ha_get_blueprint("homeassistant/motion_light.yaml", "automation")
137- - Get script blueprint: ha_get_blueprint("custom/backup_script.yaml", "script")
84+ - List all automation blueprints: ha_get_blueprint(domain="automation")
85+ - List script blueprints: ha_get_blueprint(domain="script")
86+ - Get specific blueprint: ha_get_blueprint(path="homeassistant/motion_light.yaml", domain="automation")
13887
139- RETURNS:
88+ RETURNS (when listing):
89+ - List of blueprints with path, name, and domain information
90+ - Count of blueprints found
91+
92+ RETURNS (when getting specific blueprint):
14093 - Blueprint metadata (name, description, author, source_url)
14194 - Input definitions with selectors and defaults
14295 - Blueprint configuration (triggers, conditions, actions for automations; sequence for scripts)
@@ -151,22 +104,25 @@ async def ha_get_blueprint(
151104 "valid_domains" : valid_domains ,
152105 }
153106
154- # First, list blueprints to check if path exists
107+ # Get list of blueprints
155108 list_response = await client .send_websocket_message (
156109 {"type" : "blueprint/list" , "domain" : domain }
157110 )
158111
159112 if not list_response .get ("success" ):
160113 return {
161114 "success" : False ,
162- "error" : "Failed to query blueprints" ,
163- "path" : path ,
115+ "error" : list_response .get ("error" , "Failed to query blueprints" ),
164116 "domain" : domain ,
165117 }
166118
167119 blueprints_data = list_response .get ("result" , {})
168120
169- # Check if blueprint exists
121+ # If no path provided, return list of all blueprints
122+ if path is None :
123+ return _format_blueprint_list (blueprints_data , domain )
124+
125+ # Path provided - get specific blueprint details
170126 if path not in blueprints_data :
171127 available_paths = list (blueprints_data .keys ())[:10 ]
172128 return {
@@ -176,7 +132,7 @@ async def ha_get_blueprint(
176132 "domain" : domain ,
177133 "available_blueprints" : available_paths ,
178134 "suggestions" : [
179- "Use ha_list_blueprints () to see available blueprints" ,
135+ "Use ha_get_blueprint () without path to see all available blueprints" ,
180136 "Check the path format (e.g., 'homeassistant/motion_light.yaml')" ,
181137 ],
182138 }
@@ -223,7 +179,7 @@ async def ha_get_blueprint(
223179 "error" : str (e ),
224180 "suggestions" : [
225181 "Verify the blueprint path is correct" ,
226- "Use ha_list_blueprints () to find available blueprints" ,
182+ "Use ha_get_blueprint () without path to see available blueprints" ,
227183 "Check Home Assistant connection" ,
228184 ],
229185 }
@@ -284,7 +240,7 @@ async def ha_import_blueprint(
284240 ]
285241
286242 if "already exists" in str (error_msg ).lower ():
287- suggestions .insert (0 , "Blueprint already exists - use ha_list_blueprints () to see installed blueprints" )
243+ suggestions .insert (0 , "Blueprint already exists - use ha_get_blueprint () to see installed blueprints" )
288244
289245 return {
290246 "success" : False ,
@@ -305,7 +261,7 @@ async def ha_import_blueprint(
305261 "name" : result_data .get ("blueprint" , {}).get ("name" ),
306262 "description" : result_data .get ("blueprint" , {}).get ("description" ),
307263 },
308- "message" : "Blueprint imported successfully. Use ha_list_blueprints () to see all installed blueprints." ,
264+ "message" : "Blueprint imported successfully. Use ha_get_blueprint () to see all installed blueprints." ,
309265 }
310266
311267 except Exception as e :
0 commit comments