Skip to main content
Glama
sallu-786

Unitree Go2 MCP Server

by sallu-786

πŸ€– MCP Web Portal β€” Unitree Go2 Robot Control Interface

A browser-based control and monitoring portal for the Unitree Go2 robot dog, built with Gradio and ROS2 keeping it 100% pythonic. The portal streams live camera feeds, displays sensor telemetry, provides remote navigation controls, supports autonomous waypoint missions, and integrates LLM-powered scene description. Portal Screenshot

Table of Contents


Related MCP server: WEATHGARDS

Feature Overview

Feature

Description

πŸŽ₯ Live Camera

Front-facing video stream + Intel RealSense RGB/Depth feed, with YOLO object detection overlaid on either camera

πŸ—ΊοΈ Map & Navigation

2D occupancy-grid map rendering with Nav2 goal-setting from the browser

πŸ•ΉοΈ Remote Control

Virtual joystick / directional controller for driving the robot over WirelessController messages

πŸ“‘ Telemetry

Live battery %, pose (x/y/yaw), sport-mode state, and IMU roll/pitch/yaw

🧠 AI Scene Description

LLM-based image analysis of the camera feed (Azure OpenAI or local Ollama models via LiteLLM), with a configurable system prompt

πŸ”Š Audio / Sounds

Upload audio files and play them through the robot's onboard speaker

πŸ’‘ LED Controller

Adjust headlight color and brightness

πŸ“Š ROS Graph View

Auto-generated visual graph of active ROS 2 topics, publishers, and subscribers

πŸ› οΈ Development Tab

Diagnostic tools and dev utilities for debugging the ROS bridge/connections

🌐 MCP Server

Exposes all portal-managed topics/services as MCP tools so an LLM agent can inspect and control the robot

πŸ“ Waypoint Missions

Save and replay autonomous navigation waypoints (data/waypoints/waypoints.json)


Architecture

Architecture Diagram

The robot connects to the host machine via rosbridge (default 127.0.0.1:9090), using either the DDS or WebRTC transport mode (MODE in config.py). All ROS 2 nodes are registered onto a shared executor in main.py, and their live data is surfaced to both the Gradio UI and the MCP server from the same DataStream object in web_backend/data_stream.py.


Project Structure

.
β”œβ”€β”€ main.py                    # App entry point β€” ROS2 init, node registration, Gradio launch
β”œβ”€β”€ config.py                  # Central settings: topic names, LLM config, rosbridge connection, UI flags
β”œβ”€β”€ server.py                  # MCP server exposing ROS2 topics/services as agent tools
β”œβ”€β”€ test.py                    # Test/scratch script
β”œβ”€β”€ pyproject.toml / uv.lock   # uv-managed dependency lockfile
β”œβ”€β”€ requirements.txt           # Full pinned dependency list (ROS2, Gradio, ML, MCP stack)
β”‚
β”œβ”€β”€ web_backend/
β”‚   β”œβ”€β”€ action_sub.py          # SportMode action interface (stand, sit, hello, dance, etc.)
β”‚   β”œβ”€β”€ audio_sub.py           # Access to the Go2's onboard speaker
β”‚   β”œβ”€β”€ bm_status.py           # Battery / motor / IMU (roll, pitch, yaw) status
β”‚   β”œβ”€β”€ camera.py              # Main front camera access
β”‚   β”œβ”€β”€ camera_rs.py           # RealSense RGB/Depth camera access (requires RealSense ROS2 pkg on the Go2)
β”‚   └── data_stream.py         # Core hub: ROS2 subscribers/publishers, YOLO inference, LLM calls, map builder
β”‚
β”œβ”€β”€ web_frontend/
β”‚   β”œβ”€β”€ index.py                # Main tab UI β€” camera, map, telemetry
β”‚   β”œβ”€β”€ action.py                # Actions tab UI β€” waypoints, missions, sport commands
β”‚   β”œβ”€β”€ dev.py                   # Development tab UI β€” diagnostics
β”‚   └── style.css                # Custom CSS
β”‚
β”œβ”€β”€ backends/                  # Additional backend service modules
β”œβ”€β”€ utils/                     # Shared helper utilities
β”‚
β”œβ”€β”€ data/
β”‚   β”œβ”€β”€ yolo/best2.pt           # YOLO model weights used for object detection
β”‚   β”œβ”€β”€ waypoints/waypoints.json# Saved navigation waypoints
β”‚   └── sounds/                 # Uploaded audio files for robot playback
β”‚
└── github_media/               # Screenshots / media used in repo documentation

Tech Stack

Based on the project's pinned requirements.txt, the portal is built on:

  • Robotics / middleware: ROS 2 (rclpy, ros2cli tooling), rosbridge-suite for the WebSocket bridge to the robot, unitree_go/unitree_api/unitree_hg message packages, and the unitree_sdk2_python SDK (does not need seperate install, requirements.txt alread has it as an editable Git dependency)

  • Navigation: nav2-msgs, nav2-simple-commander, slam-toolbox, cartographer-ros-msgs for occupancy-grid mapping and goal navigation. You may choose any code you like. I used following repo go2_slam_nav2

  • Web UI: gradio (v6.x) and gradio_client for the browser interface; fastapi / starlette / uvicorn underneath

  • Computer vision: opencv-python, ultralytics (YOLO) for object detection, torch / torchvision

  • LLM / agent layer: litellm (unified model API), openai, ollama (Python client), and mcp (the official Model Context Protocol SDK) for the agent-facing tool server

  • Audio: gTTS, pydub for text-to-speech / audio handling

  • Real-time transport: aiortc/aioice/av for optional WebRTC-based video/data channels

  • Misc: pandas, matplotlib/networkx (for the ROS topic/service graph visualization), redis, python-dotenv


Prerequisites

  • Ubuntu 22.04 (recommended)

  • ROS 2 Humble or later

  • Python 3.10+

  • Unitree ROS 2 SDK β€” unitreerobotics/unitree_ros2, installed and sourced

  • rosbridge_suite (```bash ros2 launch rosbridge_server rosbridge_websocket_launch.xml

```) (if you run it on laptop you can find it on 127.0.0.1:9090)

  • Nav2 (optional β€” required only for autonomous waypoint navigation)

  • Intel RealSense ROS 2 package installed on the Go2 (optional β€” required only for the RealSense RGB/Depth tab)

  • Ollama (optional β€” for local LLM inference instead of Azure OpenAI)

  • An NVIDIA GPU is not required, but the pinned requirements include CUDA-enabled torch/nvidia-* wheels for faster YOLO inference if one is available


Installation

1. Clone the repository

git clone https://github.com/sallu-786/Unitree_Go2_Web_Portal.git
cd Unitree_Go2_Web_Portal

2. Install Python dependencies

install from the pinned requirements.txt (note: this file includes ROS 2 Python packages, so it assumes a ROS 2 environment is already sourced/available):

pip install -r requirements.txt

3. Source ROS 2 and the Unitree setup script

source /opt/ros/humble/setup.bash
source /home/<your-user>/unitree_ros2/setup.sh

Update UNITREE_ROS2_SETUP_SH_PATH in config.py to match the actual path on your machine.

4. Configure config.py

At minimum, review and set:

  • ROSBRIDGE_IP / ROSBRIDGE_PORT β€” where rosbridge is running

  • MODE β€” "DDS" or "WEBRTC"

  • INTERFACE β€” your network interface for ROS 2 (ip a to find it)

  • ROBOT β€” a friendly name for your robot

  • Topic names (camera, cmd_vel, LIDAR, pose, odom, map, etc.) if they differ from your setup

  • UNITREE_ROS2_SETUP_SH_PATH and ROS_JS_LIB_PATH

5. (Optional) Set up .env for API keys

Rather than hard-coding credentials in config.py, create a .env file:

# .env
AZURE_API_KEY=your_key_here

config.py hard-coded keys are unsafe β€” load them via python-dotenv instead:

from dotenv import load_dotenv
import os
load_dotenv()
AZURE_API_KEY = os.getenv("AZURE_API_KEY")

6. Run the portal

python main.py

The Gradio app launches at http://0.0.0.0:7860 by default.


Configuration Reference

All settings live in config.py. Key groups:

Connection

Setting

Purpose

ROSBRIDGE_IP / ROSBRIDGE_PORT

Address of the rosbridge WebSocket server (default 127.0.0.1:9090)

MODE

Transport mode β€” "DDS" or "WEBRTC"

INTERFACE

Network interface used for ROS 2 DDS traffic

ROBOT

Display name for the connected robot

LLM / Scene Description

Setting

Purpose

LLM_MODE

"azure" or "ollama"

MODELS

Dict mapping mode β†’ friendly name β†’ LiteLLM model string

DEFAULT_MODEL

Default model per mode

AZURE_API_BASE / AZURE_OPENAI_DEPLOYMENT / AZURE_API_KEY / AZURE_API_VERSION

Azure OpenAI credentials (use .env, not literals)

OLLAMA_API_BASE / OLLAMA_API_KEY

Local Ollama endpoint (default http://localhost:11434)

SYSTEM_PROMPT / LLM_PROMPT

Prompts used for periodic scene description; the shipped example is tuned for factory-floor PPE/hazard detection

MCP_AGENT_PROMPT

System prompt for the MCP-connected agent, instructing it to use tools for robot state/control and never claim success without a confirmed tool result

UI Feature Flags

Setting

Purpose

SHOW_CAMERA / SHOW_TOPICS / SHOW_SERVICES / SHOW_CONTROLLER / SHOW_DESCRIPTION / SHOW_LIDAR

Toggle individual UI panels on/off

TTS_LANGUAGE

Language code for text-to-speech ("ja" by default in the sample config)

UPDATE_INTERVAL

Seconds between periodic scene-description calls

IMAGE_HEIGHT / IMAGE_WIDTH

Camera stream display dimensions

YOLO_MODE

"main" for the front camera or "rs" for RealSense as the YOLO detection source

Paths

Setting

Purpose

YOLO_MODEL

Path to YOLO weights (data/yolo/best2.pt)

SOUNDS_DIR

Directory for uploaded playback audio

WAYPOINT_FILE

JSON file storing saved navigation waypoints

UNITREE_ROS2_SETUP_SH_PATH

Path to the Unitree ROS 2 setup.sh

ROS_JS_LIB_PATH

Path to the JS library used for browser-side map/nav rendering

Topic Names β€” all remappable to match your robot's actual topic names: CAMERA_TOPIC_NAME, REALSENSE_CAMERA_COLOR, REALSENSE_CAMERA_DEPTH, CMD_VEL_PUB_TOPIC_NAME (+ _TYPE), LIDAR (+ LIDAR_MAX_POINTS), POSE (+ POSE_HEADER_FRAME_ID), ODOM, MAP, SPORTS, LFLOWCMD.

ROS Graph Styling β€” TOPIC_COLOR, PUBLISHER_COLOR, SUBSCRIBER_COLOR, NODE_SIZE, TOPIC_SIZE, PLOT_WIDTH, PLOT_HEIGHT control the appearance of the topic/service graph shown in the UI.


Running the MCP Portal

python main.py

This will:

  1. Initialize rclpy, instantiate all ROS 2 subscriber/publisher nodes defined in web_backend/, and register them on a shared executor.

  2. Launch the Gradio app with tabs for the main dashboard, actions/waypoints, and development diagnostics.

  3. Start the background loop that periodically grabs a camera frame, runs it through the configured LLM, and updates the on-screen scene description.


MCP Server & LLM Agent Integration

server.py starts an MCP server that mirrors the robot's ROS 2 surface as callable tools β€” battery/pose/telemetry reads, topic/service introspection, and movement/action commands. Any MCP-compatible client (a custom agent script, an IDE assistant, or a chat UI wired up with an MCP connector) can attach to it and:

  • List and inspect active ROS 2 topics and services

  • Read live telemetry (battery, pose, sport-mode state, sensors)

  • Issue movement or action commands through the exposed tools

  • Get grounded, tool-verified answers rather than the model guessing at robot state

The MCP_AGENT_PROMPT in config.py explicitly instructs the connected agent to rely on tool calls for anything robot-related and to never report success unless a tool call actually confirms it β€” useful guardrails when letting an LLM drive a physical robot.

To customize which model powers the natural-language side of the agent, add entries to MODELS in config.py using LiteLLM's model string format, e.g.:

MODELS = {
    "ollama": {
        "Gemma3": "ollama/gemma3:latest",
        "Llama3": "ollama/llama3:latest",   # ← new entry
    }
}

Accessing the Portal

Access Type

URL

Local (same machine)

http://localhost:7860

LAN (other devices)

http://<robot-host-ip>:7860


Extending the Project

Add a new ROS 2 subscriber

  1. Create a new subscriber class in web_backend/, following the pattern of an existing one (e.g. the camera subscribers).

  2. Instantiate it inside DataStream.__init__() in web_backend/data_stream.py.

  3. Register the node with the executor in main.py:

    executor.add_node(launcher.your_new_subscriber)
  4. Expose the data via a property or method on DataStream so the frontend can read it.

Add a new UI tab

  1. Create web_frontend/my_tab.py and define a get_my_tab_page(demo, launcher) function using Gradio components.

  2. Wire it into main.py inside the gr.Tabs() block:

    with gr.Tab("My Tab"):
        get_my_tab_page(demo, launcher)

Change the LLM scene-description prompt

Edit SYSTEM_PROMPT and LLM_PROMPT in config.py:

SYSTEM_PROMPT = "You are a robot assistant."
LLM_PROMPT = "Describe the scene and highlight any hazards."

Add a new LLM model β€” add an entry to the MODELS dict as shown above in the MCP section.


License & Acknowledgements

This project is intended for internal/research use. Please respect the licenses of its third-party dependencies, including Gradio, ROS 2, the Unitree SDK, and the MCP SDK. See the repository's LICENSE file for details.

Acknowledgements:

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables controlling a Unitree GO2 robot's motion (forward, backward, left, right, stop, stand up, and move with custom velocities) via MCP tools, using ROS2 commands.
    -
  • A
    license
    Not graded
    quality
    D
    maintenance
    MCP server for controlling Unitree robots via DDS protocol, supporting G1, Go2, H1, H2, B2, A2, R1 and more. Enables LLM agents to command robots with high-level actions and safety guards.
    4
    MIT