Skip to content

Commit 737e013

Browse files
authored
Bugfix: adding check on missing rosapi services to avoid crashes in older versions (#182)
* Check rosapi required services for action-related tools and throw error/warning if not available (applies to ROS 1 or ROS 2 Humble) * Improved return messages for action-related tools when required services are not compatible
1 parent 35943ed commit 737e013

File tree

1 file changed

+185
-1
lines changed

1 file changed

+185
-1
lines changed

server.py

Lines changed: 185 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2073,6 +2073,45 @@ def get_actions() -> dict:
20732073
dict: Contains list of all active actions,
20742074
or a message string if no actions are found.
20752075
"""
2076+
# Check if required service is available
2077+
required_services = ["/rosapi/action_servers"]
2078+
2079+
with ws_manager:
2080+
# Get available services to check compatibility
2081+
services_message = {
2082+
"op": "call_service",
2083+
"service": "/rosapi/services",
2084+
"type": "rosapi/Services",
2085+
"args": {},
2086+
"id": "check_services_for_get_actions",
2087+
}
2088+
2089+
services_response = ws_manager.request(services_message)
2090+
if not services_response or not isinstance(services_response, dict):
2091+
return {
2092+
"warning": "Cannot check service availability",
2093+
"compatibility": {
2094+
"issue": "Cannot determine available services",
2095+
"required_services": required_services,
2096+
"suggestion": "Ensure rosbridge is running and rosapi is available",
2097+
},
2098+
}
2099+
2100+
available_services = services_response.get("values", {}).get("services", [])
2101+
missing_services = [svc for svc in required_services if svc not in available_services]
2102+
2103+
if missing_services:
2104+
return {
2105+
"warning": "Action listing not supported by this rosbridge/rosapi version",
2106+
"compatibility": {
2107+
"issue": "Required action services are not available",
2108+
"missing_services": missing_services,
2109+
"required_services": required_services,
2110+
"available_services": [s for s in available_services if "action" in s],
2111+
"suggestion": "This rosbridge version doesn't support action listing services",
2112+
},
2113+
}
2114+
20762115
# rosbridge service call to get action list
20772116
message = {
20782117
"op": "call_service",
@@ -2127,6 +2166,47 @@ def get_action_type(action: str) -> dict:
21272166
if not action or not action.strip():
21282167
return {"error": "Action name cannot be empty"}
21292168

2169+
# Check if required service is available
2170+
required_services = ["/rosapi/interfaces"]
2171+
2172+
with ws_manager:
2173+
# Get available services to check compatibility
2174+
services_message = {
2175+
"op": "call_service",
2176+
"service": "/rosapi/services",
2177+
"type": "rosapi/Services",
2178+
"args": {},
2179+
"id": "check_services_for_get_action_type",
2180+
}
2181+
2182+
services_response = ws_manager.request(services_message)
2183+
if not services_response or not isinstance(services_response, dict):
2184+
return {
2185+
"warning": "Cannot check service availability",
2186+
"action": action,
2187+
"compatibility": {
2188+
"issue": "Cannot determine available services",
2189+
"required_services": required_services,
2190+
"suggestion": "Ensure rosbridge is running and rosapi is available",
2191+
},
2192+
}
2193+
2194+
available_services = services_response.get("values", {}).get("services", [])
2195+
missing_services = [svc for svc in required_services if svc not in available_services]
2196+
2197+
if missing_services:
2198+
return {
2199+
"warning": "Action type resolution not supported by this rosbridge/rosapi version",
2200+
"action": action,
2201+
"compatibility": {
2202+
"issue": "Required services are not available",
2203+
"missing_services": missing_services,
2204+
"required_services": required_services,
2205+
"available_services": [s for s in available_services if "interface" in s],
2206+
"suggestion": "This rosbridge version doesn't support interface listing services",
2207+
},
2208+
}
2209+
21302210
# Since there's no direct action_type service, we'll derive it from known patterns
21312211
# or use a mapping approach for common actions
21322212

@@ -2173,7 +2253,16 @@ def get_action_type(action: str) -> dict:
21732253
"suggestion": "This action might not be available or use a different naming pattern",
21742254
}
21752255

2176-
return {"error": f"Failed to get type for action {action}"}
2256+
return {
2257+
"error": f"Failed to get type for action {action}",
2258+
"action": action,
2259+
"compatibility": {
2260+
"issue": "Failed to retrieve interfaces from rosapi",
2261+
"required_services": ["/rosapi/interfaces"],
2262+
"suggestion": "Ensure rosbridge is running and rosapi is available",
2263+
"note": "Action type resolution requires /rosapi/interfaces service",
2264+
},
2265+
}
21772266

21782267

21792268
@mcp.tool(
@@ -2197,6 +2286,57 @@ def get_action_details(action_type: str) -> dict:
21972286
if not action_type or not action_type.strip():
21982287
return {"error": "Action type cannot be empty"}
21992288

2289+
# Check if required action detail services are available
2290+
required_services = [
2291+
"/rosapi/action_goal_details",
2292+
"/rosapi/action_result_details",
2293+
"/rosapi/action_feedback_details",
2294+
]
2295+
2296+
with ws_manager:
2297+
# Get available services to check compatibility
2298+
services_message = {
2299+
"op": "call_service",
2300+
"service": "/rosapi/services",
2301+
"type": "rosapi/Services",
2302+
"args": {},
2303+
"id": "check_services_for_action_details",
2304+
}
2305+
2306+
services_response = ws_manager.request(services_message)
2307+
if not services_response or not isinstance(services_response, dict):
2308+
return {
2309+
"error": "Failed to check service availability",
2310+
"action_type": action_type,
2311+
"compatibility": {
2312+
"issue": "Cannot determine available services",
2313+
"required_services": required_services,
2314+
"suggestion": "Ensure rosbridge is running and rosapi is available",
2315+
},
2316+
}
2317+
2318+
available_services = services_response.get("values", {}).get("services", [])
2319+
missing_services = [svc for svc in required_services if svc not in available_services]
2320+
2321+
if missing_services:
2322+
return {
2323+
"error": f"Action details for {action_type} not found",
2324+
"action_type": action_type,
2325+
"compatibility": {
2326+
"issue": "Required action detail services are not available",
2327+
"missing_services": missing_services,
2328+
"required_services": required_services,
2329+
"available_services": [s for s in available_services if "action" in s],
2330+
"suggestions": [
2331+
"Use get_actions() to list available actions",
2332+
"Use get_action_type() to get action type from action name",
2333+
"Action details may not be exposed by this rosbridge/rosapi version",
2334+
"Consider subscribing to action topics directly for live message inspection",
2335+
],
2336+
"note": "Action detail services (/rosapi/action_*_details) are not part of standard rosapi",
2337+
},
2338+
}
2339+
22002340
result = {"action_type": action_type, "goal": {}, "result": {}, "feedback": {}}
22012341

22022342
# Get goal, result, and feedback details in a single WebSocket context
@@ -2475,6 +2615,50 @@ def inspect_all_actions() -> dict:
24752615
dict: Contains detailed information about all actions,
24762616
including action names, types, and server information.
24772617
"""
2618+
# Check if required action services are available
2619+
required_services = ["/rosapi/action_servers"]
2620+
2621+
with ws_manager:
2622+
# Get available services to check compatibility
2623+
services_message = {
2624+
"op": "call_service",
2625+
"service": "/rosapi/services",
2626+
"type": "rosapi/Services",
2627+
"args": {},
2628+
"id": "check_services_for_inspect_actions",
2629+
}
2630+
2631+
services_response = ws_manager.request(services_message)
2632+
if not services_response or not isinstance(services_response, dict):
2633+
return {
2634+
"error": "Failed to check service availability",
2635+
"compatibility": {
2636+
"issue": "Cannot determine available services",
2637+
"required_services": required_services,
2638+
"suggestion": "Ensure rosbridge is running and rosapi is available",
2639+
},
2640+
}
2641+
2642+
available_services = services_response.get("values", {}).get("services", [])
2643+
missing_services = [svc for svc in required_services if svc not in available_services]
2644+
2645+
if missing_services:
2646+
return {
2647+
"error": "Action inspection not supported by this rosbridge/rosapi version",
2648+
"compatibility": {
2649+
"issue": "Required action services are not available",
2650+
"missing_services": missing_services,
2651+
"required_services": required_services,
2652+
"available_services": [s for s in available_services if "action" in s],
2653+
"suggestions": [
2654+
"This rosbridge version doesn't support action inspection services",
2655+
"Use get_actions() to list available actions",
2656+
"Consider upgrading rosbridge or using a different implementation",
2657+
],
2658+
"note": "Action inspection requires /rosapi/action_servers service",
2659+
},
2660+
}
2661+
24782662
# First get all actions
24792663
actions_message = {
24802664
"op": "call_service",

0 commit comments

Comments
 (0)