MCP server bridging AI agents (Claude, Cursor, VS Code) to ROS 2 robots via rosbridge WebSockets
rosbridge-mcp has 10 tools with mostly complete schemas and descriptions. Naming is verb-first and clear (list_*, get_*, send_*, cancel_*). Descriptions are detailed and context-aware, ranging 150 - 500 chars, well above the 10-char minimum. However, 4 tools (get_tf_tree, get_camera_image, send_action_goal, cancel_action_goal) have minimal descriptions (7 - 30 chars), violating the 20-char floor. Output schemas are documented in descriptions but not formally declared in JSON Schema. Error handling is present but inconsistent, some tools describe recovery paths (get_topic_snapshot mentions connection_lost), others do not. Parameter descriptions are thorough for core tools (get_topic_snapshot, publish_message, call_service) but sparse for action tools. No tool annotations (readOnlyHint, destructiveHint) are visible in the code.
Call any ROS service with JSON args and return the response values. Rejected when ROSBRIDGE_MCP_READONLY is set, unless the service is on the fixed allowlist of known read-only /rosapi introspection services (topics, nodes, services, *_type, *_details, get_param, get_time, ...). Args: service: Full service name, e.g. "/rosapi/topic_type" or "/reset_odometry". Discover names with list_services. args: JSON object matching the service request definition, e.g. {"topic": "/scan"} for /rosapi/topic_type. Default {}. timeout: Max seconds to wait for the response (default 10.0). Returns {"service", "success": true, "values": {...}} on success, or {"service", "success": false, "error": "..."} on failure/timeout (with any related rosbridge status errors under "rosbridge_status").
Cancel a pending ROS 2 action goal.
Get a camera image from the robot.
Get the coordinate-frame tree (TF tree) from the robot.
Read live data from a topic: subscribe, collect up to `count` messages (or until `timeout` seconds elapse), then unsubscribe. Args: topic: Topic name including leading slash, e.g. "/scan" or "/odom". count: How many messages to collect (default 1, clamped to at most 100). Use more to observe a value changing over time. timeout: Max seconds to wait (default 5.0, clamped to at most 60.0). The tool never blocks longer than this, even on a silent topic. msg_type: Optional full message type, e.g. "sensor_msgs/msg/LaserScan". Usually omit it; rosbridge resolves the type of existing topics. Returns {"topic", "requested", "received", "messages": [...], "timed_out", "timeout_s"}. If "timed_out" is true, nothing (or not enough) was published within the timeout — the topic may be silent, misspelled, or not exist. If the rosbridge connection drops mid-collection, the tool returns immediately with {"error", "connection_lost": true} instead of waiting out the timeout.
Four tools have critically short descriptions (<30 chars): get_tf_tree, get_camera_image, send_action_goal, cancel_action_goal. LLMs cannot determine when to select these tools or what they return.
No tool annotations (readOnlyHint, destructiveHint, idempotentHint) present. publish_message and call_service are marked WRITE in metadata but lack formal destructiveHint annotations in schema.
Output schemas not formally declared in JSON Schema format. Descriptions document return structure (e.g. get_topic_snapshot returns {topic, requested, received, messages, timed_out, timeout_s}) but no outputSchema field in tool definition.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | D | 59 | 2026-07-28+ | v2 |
List all ROS nodes currently running on the robot. Takes no arguments. Returns {"nodes": ["/talker", "/rosapi", ...]}. Useful to check whether an expected driver or controller is up.
List all ROS services currently available on the robot. Takes no arguments. Returns {"services": ["/reset_odometry", ...]}. Use before call_service to find the exact service name.
List all ROS topics currently known to the robot, with message types. Takes no arguments. Returns {"topics": [{"name": "/scan", "type": "sensor_msgs/msg/LaserScan"}, ...]}. Call this first to discover what the robot exposes before subscribing or publishing.
Publish a JSON message to a ROS topic (advertises the topic first). CAUTION: this can move a real robot. Rejected when ROSBRIDGE_MCP_READONLY is set. Prefer reading relevant sensor topics (e.g. /scan, /odom) before commanding motion, and publish zero velocity to stop. Args: topic: Target topic, e.g. "/cmd_vel". msg_type: Full ROS 2 message type with the "/msg/" segment, e.g. "geometry_msgs/msg/Twist" or "std_msgs/msg/String". message: JSON object whose fields match the message definition, e.g. {"linear": {"x": 0.1, "y": 0.0, "z": 0.0}, "angular": {"x": 0.0, "y": 0.0, "z": 0.2}} for a Twist. Omitted fields default to zero/empty on the ROS side. Returns {"published": true, "topic": ..., "type": ...} on success. The tool briefly waits for rosbridge 'status' errors after publishing; if rosbridge rejected the message (e.g. wrong msg_type), the result includes "rosbridge_warnings" — treat those as the publish having failed.
Send a ROS 2 action goal and wait for the result.
Error handling inconsistent. get_topic_snapshot documents connection_lost and timeout behavior; call_service documents success/failure with error field; but get_tf_tree, get_camera_image, send_action_goal, cancel_action_goal provide no error guidance.
Parameter descriptions for action tools (send_action_goal, cancel_action_goal) are minimal. 'Goal message' and 'Goal ID to cancel' lack context on format, constraints, or expected structure.