
Brings the Robot Operating System into your AI workflow with full stdio transport support for ROS 2 Humble and Jazzy. Exposes tools to list, subscribe, and publish to topics, call services, and manage action goals with feedback and cancellation. Includes auto QoS selection and dynamic type discovery so you don't need to manually configure message schemas. Ships with ready-made prompts for topic analysis, health checks, and message relaying. The Docker setup is straightforward, and it can pull historical data from WiseVision Data Black Box if you're storing telemetry there. Useful when you need to debug robot behavior, analyze sensor streams, or script complex mission sequences through natural language instead of writing ROS launch files.

ROS2 MCP is an open-source (MPL-2.0) Model Context Protocol (MCP) server for ROS 2 Humble and Jazzy, written in Python. It lets AI tools such as Claude, Cursor and Codex list, subscribe to, publish on and call ROS 2 topics, services and actions over stdio (or SSE). It is listed in Docker's official MCP catalog as mcp/ros2.
Every tool is free and open source, including multi-topic subscribe/publish, map-to-image and point-cloud bird's-eye view.
🌐 Website: wisevision.tech · 📚 Documentation: wisevision.tech/docs
An agent connected to a real robot can move it. Start the server in read-only mode to let the agent observe but not act:
ROS2_MCP_READONLY=1 uv run mcp_ros_2_server # env var
uv run mcp_ros_2_server --read-only # or CLI flag
docker run -i --rm -e ROS2_MCP_READONLY=1 mcp/ros2
In read-only mode the tools that change robot state are not registered at all: they do not appear in list_tools, and calling one returns an Unknown tool error. The hidden tools are:
ros2_topic_publish, ros2_publish_multiple_topics, ros2_service_call, ros2_send_action_goal, ros2_cancel_action_goal.
Read-only mode fails closed: only tools that were explicitly reviewed as read-only (listed in server/tool_safety.py) are registered, and a test fails if a new tool is added without being classified.
ros2_service_call is hidden because the server cannot know whether an arbitrary service has side effects.
nav_msgs/OccupancyGrid map as a PNG imagesensor_msgs/PointCloud2 as a bird's-eye-view PNG imageSubscribe to a ROS2 topic, collect messages for a specified duration, and provide statistical analysis of the collected data.
➡️ Can auto-detect topic if only one is available. Analyzes message rates, counts, and statistics on numeric fields.
Subscribe to one ROS2 topic and republish messages to another topic with optional transformations.
➡️ Supports identity relay, rate limiting, and change-based filtering.
Check if expected ROS2 topics and services are available and functioning correctly with optional publication rate monitoring.
➡️ Provides comprehensive health report with status indicators and recommendations.
Compare two ROS2 topics and report differences in their messages with detailed field-by-field analysis.
➡️ Useful for comparing raw sensor data with filtered/processed versions or verifying topic synchronization.
Note: To call a service with a custom (non-default) type, source the package that defines it before starting the server.
Save hours of development time with native AI integration for your ROS 2 projects:
docker run or uv run commandPerfect for: Robotics developers, researchers, students, and anyone working with ROS 2 who wants to leverage AI for faster development and debugging.
🚀 Enjoying this project? Feel free to contribute or reach out for support! Write issues, submit PRs, or join our Discord community to connect with other ROS 2 and AI enthusiasts.
Want to see it on real data? The forklift rosbag demo replays a warehouse forklift recording and walks through three MCP calls in about ten minutes.
Contributions are welcome! Please check the open issues for ways to help, open a pull request, or drop by our Discord to discuss ideas before diving in. By participating, you agree to follow our Code of Conduct.


Follow the installation guide for step-by-step instructions:
If the first tool call returns an incomplete list of topics/services right after container start, DDS discovery may still be in progress. The server performs a one-time warm-up on the first tool call in container environments; tune it via:
MCP_ROS_DISCOVERY_STABLE_SEC (default: 1.0)MCP_ROS_DISCOVERY_TIMEOUT_SEC (default: 5.0)MCP_ROS_DISCOVERY_WARMUP=false to disableCheck out the Gazebo Drone Demo section
| Tool | Description | Inputs | Outputs |
|---|---|---|---|
ros2_topic_list | Returns list of available topics | – | topic_name (string): Topic name topic_type (string): Message type |
ros2_topic_subscribe | Subscribes to a ROS 2 topic and collects messages for a duration or message limit | topic_name (string) duration (float) message_limit (int) (defaults: first msg, 5s) | messages count duration |
ros2_get_messages_stored_in_influx_data_base | Retrieves past messages from a topic (data black box) | topic_name (string) message_type (string) number_of_messages (int) time_start (str) time_end (str) | timestamps messages |
ros2_get_message_fields | Gets field names and types for a message type | message_type (string) | Field names + types |
ros2_topic_publish | Publishes message to a topic (hidden in read-only mode) | topic_name (string) message_type (string) data (dict) | status |
ros2_subscribe_multiple_topics | Subscribes to several topics at once; sensor_msgs/Image and CompressedImage messages are returned as PNG images | topics[] (array of {name (string), duration (float), message_limit (int)}) (default per topic: 5 s) | per topic: count + messages (text) or images (PNG) | error |
ros2_publish_multiple_topics | Publishes to several topics simultaneously at a given frequency for a given duration (hidden in read-only mode) | topics[] (array of {topic_name (string), message_type (string), data (object), frequency (Hz, default 1.0), duration (s, default 5.0)}) | per topic: status |
ros2_get_map_as_image | Gets one nav_msgs/msg/OccupancyGrid and returns it as a PNG (unknown = gray, free = white, occupied = black) | topic_name (string, e.g. /map) | PNG image |
ros2_get_pointcloud_as_bev | Gets one sensor_msgs/msg/PointCloud2 and renders a bird's-eye view (XY projection) PNG | topic_name (string) resolution (m/px, default 0.05) zmin/zmax (m) timeout (s, default 5.0) max_pixels (int, default 2048) color_mode (intensity|height|rgb) colormap (jet|gray) | PNG image |
| Tool | Description | Inputs | Outputs |
|---|---|---|---|
ros2_service_list | Returns list of available services | – | service_name (string) service_type (string) request_fields (array) |
ros2_service_call | Calls a ROS 2 service (hidden in read-only mode) | service_name (string) service_type (string) fields (array) force_call (bool, default: false) | result (string) error (string, if any) |
| Tool | Description | Inputs | Outputs |
|---|---|---|---|
ros2_list_actions | Returns list of available ROS 2 actions with their types and request fields | – | actions[] (array) └ name (string) └ types[] (array of string) └ request_fields (array) |
ros2_send_action_goal | Sends a goal to an action. Optionally waits for the result. (hidden in read-only mode) | action_name (string) action_type (string) goal_fields (object) wait_for_result (bool, default: false) timeout_sec (number, default: 60.0) | accepted (bool) goal_id (string|null) send_goal_stamp (object|null) waited (bool) result_timeout_sec (number|null) status_code (int|null) status (string|null) result (object|null) | error (string) |
ros2_cancel_action_goal | Cancels a specific goal or all goals for an action (hidden in read-only mode) | action_name (string) goal_id_hex (string, required if cancel_all=false) cancel_all (bool, default: false) stamp_sec (int, default: 0) stamp_nanosec (int, default: 0) wait_timeout_sec (number, default: 3.0) | service (string) return_code (int) return_code_text (string) goals_canceling[] (array of {goal_id, stamp}) | error (string) |
ros2_action_request_result | Waits for the RESULT of a given goal via GetResult | action_name (string) action_type (string) goal_id_hex (string, 32-char UUID) timeout_sec (number|null, default: 60.0) wait_for_service_sec (number, default: 3.0) | service (string) goal_id (string) waited (bool) result_timeout_sec (number|null) status_code (int|null) status (string|null) result (object|null) | error (string) |
ros2_action_subscribe_feedback | Subscribes to feedback messages for an action. Can filter by goal_id. Collects messages for duration or max count. | action_name (string) action_type (string) goal_id_hex (string|null) duration_sec (number, default: 5.0) max_messages (int, default: 100) | topic (string) action_type (string) goal_id_filter (string|null) duration_sec (number) messages[] (array of {goal_id, feedback, recv_stamp}) | error (string) |
ros2_action_subscribe_status | Subscribes to an action's status topic and returns collected status frames | action_name (string) duration_sec (number, default: 5.0) max_messages (int, default: 100) | topic (string) duration_sec (number) frames[] (array of {stamp, statuses[]}) | error (string) |
Since MCP servers run over stdio, debugging can be challenging. For the best debugging experience, we strongly recommend using the MCP Inspector.
You can launch the MCP Inspector via npm with this command:
npx @modelcontextprotocol/inspector uv --directory /path/to/ros2_mcp run mcp_ros_2_server
Upon launching, the Inspector will display a URL that you can access in your browser to begin debugging.
We built this server to make AI‑assisted ROS 2 development fast and reliable. Internally, we needed a simple way for agents to discover message types, publish/subscribe to topics, and call services—without boilerplate or flaky networking. That led to a few core design goals:
After dogfooding it, we open‑sourced the project (MPL-2.0) to help the broader ROS 2 community build faster with AI. It’s now useful not only for development, but also for controlling robots, running QoS experiments, and analyzing live data and robot/swarm state. The project is actively maintained—features and improvements ship regularly based on user feedback. If this project helps you, share your use case in Discussions!
MCP_CUSTOM_PROMPTSEnable custom prompts
MCP_PROMPTS_LOCALUse local prompts
MCP_PROMPTS_PATHPath to custom prompts directory
MCP_PROMPTS_MODULEName of the prompts module