Skip to main content
Glama
selfpatch

ros2_medkit_mcp

by selfpatch
README.md
# ros2_medkit_mcp

[![CI](https://github.com/selfpatch/ros2_medkit_mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/selfpatch/ros2_medkit_mcp/actions/workflows/ci.yml)
[![GHCR](https://img.shields.io/badge/GHCR-ghcr.io%2Fselfpatch%2Fros2__medkit__mcp-blue?logo=github)](https://github.com/selfpatch/ros2_medkit_mcp/pkgs/container/ros2_medkit_mcp)
[![Docs](https://img.shields.io/badge/docs-GitHub%20Pages-blue)](https://selfpatch.github.io/ros2_medkit/)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
[![Discord](https://img.shields.io/badge/Discord-Join%20Us-7289DA?logo=discord&logoColor=white)](https://discord.gg/6CXPMApAyq)

A thin MCP (Model Context Protocol) adapter that connects an LLM to an existing SOVD HTTP API exposed by [ros2_medkit](https://github.com/selfpatch/ros2_medkit).

## Overview

This server does **not** implement SOVD itself. It provides MCP tools that call the existing HTTP endpoints of a running ros2_medkit gateway.

## Features

- **Full ros2_medkit gateway coverage**: Discovery, component data, operations (services/actions), configurations (ROS 2 parameters), and entity lifecycle status (apps/components)
- **Dual transport support**: stdio and streamable-http
- **Async HTTP client** using httpx
- **Pydantic validation** for configuration and models
- **Bearer token authentication** support

## Quick Start

### Prerequisites

- Python 3.11+
- [Poetry](https://python-poetry.org/) or [uv](https://docs.astral.sh/uv/)
- A running ros2_medkit gateway (default: `http://localhost:8080`)

### Installation

```bash
# Clone the repository
git clone https://github.com/selfpatch/ros2_medkit_mcp.git
cd ros2_medkit_mcp

# Install dependencies with Poetry
poetry install
```

Or with [uv](https://docs.astral.sh/uv/):

```bash
uv venv
uv pip install -e .            # add '.[dev]' for the test and lint tools
```

> The project uses the Poetry build backend, so install it with `uv pip install`
> (not `uv sync`, which only reads PEP 621 `[project]` dependencies). The entry
> points then live in `.venv/bin/`.

Don't want a checkout at all? Run it straight from the repository with `uvx`:

```bash
uvx --from git+https://github.com/selfpatch/ros2_medkit_mcp ros2-medkit-mcp-stdio
```

### Configuration

The server is configured via environment variables:

| Variable | Default | Description |
|----------|---------|-------------|
| `ROS2_MEDKIT_BASE_URL` | `http://localhost:8080/api/v1` | Base URL of the ros2_medkit SOVD API |
| `ROS2_MEDKIT_BEARER_TOKEN` | *(none)* | Optional Bearer token for authentication |
| `ROS2_MEDKIT_TIMEOUT_S` | `30` | HTTP request timeout in seconds |

### Running the Server

#### stdio Transport (for Claude Desktop, etc.)

```bash
poetry run ros2-medkit-mcp-stdio
# uv:  uv run --no-sync ros2-medkit-mcp-stdio   (or: .venv/bin/ros2-medkit-mcp-stdio)
# uvx: uvx --from git+https://github.com/selfpatch/ros2_medkit_mcp ros2-medkit-mcp-stdio
```

For Claude Desktop, add to your `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "ros2_medkit": {
      "command": "poetry",
      "args": ["run", "ros2-medkit-mcp-stdio"],
      "cwd": "/path/to/ros2_medkit_mcp",
      "env": {
        "ROS2_MEDKIT_BASE_URL": "http://localhost:8080/api/v1"
      }
    }
  }
}
```

Or with `uvx`, which needs no local checkout (drop the `cwd`):

```json
{
  "mcpServers": {
    "ros2_medkit": {
      "command": "uvx",
      "args": [
        "--from",
        "git+https://github.com/selfpatch/ros2_medkit_mcp",
        "ros2-medkit-mcp-stdio"
      ],
      "env": {
        "ROS2_MEDKIT_BASE_URL": "http://localhost:8080/api/v1"
      }
    }
  }
}
```

#### Streamable HTTP Transport

```bash
poetry run ros2-medkit-mcp-http --host 0.0.0.0 --port 8765
# uv:  uv run --no-sync ros2-medkit-mcp-http --host 0.0.0.0 --port 8765
# uvx: uvx --from git+https://github.com/selfpatch/ros2_medkit_mcp ros2-medkit-mcp-http --host 0.0.0.0 --port 8765
```

The server will be available at `http://0.0.0.0:8765/mcp`.

#### VS Code MCP Configuration

See the [examples/](examples/) directory for ready-to-use MCP configuration files:
- `mcp-stdio.json` - Local stdio transport
- `mcp-http.json` - HTTP transport for remote server

### Docker

```bash
# Build image
docker build -t ros2-medkit-mcp .

# Run HTTP server (default)
docker run -p 8765:8765 ros2-medkit-mcp

# Run with stdio transport
docker run -i ros2-medkit-mcp stdio

# With custom gateway URL
docker run -p 8765:8765 -e ROS2_MEDKIT_BASE_URL=http://host.docker.internal:8080/api/v1 ros2-medkit-mcp
```

Using docker-compose:

```bash
docker-compose up
```

#### Using Docker Image as MCP Server in VS Code

To use the Docker image as an MCP server in VS Code with the GitHub Copilot extension:

1. **Start the MCP server container:**

   ```bash
   docker run -d --name ros2-medkit-mcp -p 8765:8765 \
     -e ROS2_MEDKIT_BASE_URL=http://host.docker.internal:8080/api/v1 \
     ghcr.io/selfpatch/ros2_medkit_mcp:latest
   ```

2. **Configure VS Code MCP settings** (`.vscode/mcp.json` or user settings):

   ```json
   {
     "servers": {
       "ros2_medkit": {
         "type": "sse",
         "url": "http://localhost:8765/mcp",
         "headers": {}
       }
     }
   }
   ```

3. **Verify the connection** by checking the health endpoint:

   ```bash
   curl http://localhost:8765/health
   ```

   Expected response:
   ```json
   {"status": "healthy", "service": "ros2_medkit_mcp", "sovd_url": "http://host.docker.internal:8080/api/v1"}
   ```

4. **Use with Copilot Chat** - the MCP tools will be available for querying ROS 2 system state via SOVD API.

> **Note:** Use `host.docker.internal` to connect from the container to services running on your host machine (like ros2_medkit gateway).

## MCP Tools

### Discovery Tools

#### `sovd_version`
Get the SOVD API version information.

**Arguments:** None
**Returns:** JSON version object from `GET /version-info`

#### `sovd_entities_list`
List all SOVD entities (areas, components, apps, and functions) with optional filtering.

**Arguments:**
- `filter` (optional, string): Substring filter applied to entity `id` and `name` fields

**Returns:** Combined array of areas from `GET /areas`, components from `GET /components`, apps from `GET /apps`, and functions from `GET /functions` (when those endpoints are available)

#### `sovd_entities_get`
Get a specific entity by ID with live data if available.

**Arguments:**
- `entity_id` (required, string): The entity identifier

**Returns:** Entity object with optional `data` field for components

#### `sovd_faults_list`
List faults for a specific component.

**Arguments:**
- `component_id` (required, string): The component identifier

**Returns:** Array of fault objects from `GET /components/{component_id}/faults`

#### `sovd_faults_get`
Get a specific fault by ID.

**Arguments:**
- `component_id` (required, string): The component identifier
- `fault_id` (required, string): The fault identifier

**Returns:** Fault object from `GET /components/{component_id}/faults/{fault_id}`

#### `sovd_faults_clear`
Clear (acknowledge/dismiss) a fault.

**Arguments:**
- `component_id` (required, string): The component identifier
- `fault_id` (required, string): The fault identifier to clear

**Returns:** Response from `DELETE /components/{component_id}/faults/{fault_id}`

#### `sovd_area_components`
List all components within a specific area.

**Arguments:**
- `area_id` (required, string): The area identifier (e.g., 'powertrain', 'chassis', 'body')

**Returns:** Array of component objects from `GET /areas/{area_id}/components`

### Entity Data Tools

#### `sovd_entity_data`
Read all topic data from an entity (component or app).

**Arguments:**
- `entity_id` (required, string): The entity identifier
- `entity_type` (optional, string): Entity type - 'components' or 'apps' (default: 'components')

**Returns:** Array of topic data from `GET /{entity_type}/{entity_id}/data`

#### `sovd_entity_topic_data`
Read data from a specific topic within an entity.

**Arguments:**
- `entity_id` (required, string): The entity identifier
- `topic_name` (required, string): The topic name (e.g., 'temperature', 'rpm')
- `entity_type` (optional, string): Entity type - 'components' or 'apps' (default: 'components')

**Returns:** Topic data from `GET /{entity_type}/{entity_id}/data/{topic_name}`

#### `sovd_publish_topic`
Publish data to a component's topic.

**Arguments:**
- `component_id` (required, string): The component identifier
- `topic_name` (required, string): The topic name to publish to
- `data` (required, object): The message data to publish as JSON object

**Returns:** Response from `PUT /components/{component_id}/data/{topic_name}`

### Operations Tools (Services & Actions)

#### `sovd_list_operations`
List all operations (services and actions) available for a component.

**Arguments:**
- `component_id` (required, string): The component identifier

**Returns:** Array of operations from `GET /components/{component_id}/operations`

#### `sovd_create_execution`
Call a ROS 2 service or send an action goal.

**Arguments:**
- `entity_id` (required, string): The entity identifier
- `operation_name` (required, string): The operation name (service or action)
- `request_data` (optional, object): Request data (parameters for actions/services)
- `entity_type` (optional, string): Entity type - 'components', 'apps', 'areas', or 'functions' (default: 'components')

**Returns:** Response from `POST /{entity_type}/{entity_id}/operations/{operation_name}/executions`

#### `sovd_get_execution`
Get the current status of a running action execution.

**Arguments:**
- `entity_id` (required, string): The entity identifier
- `operation_name` (required, string): The action name
- `execution_id` (required, string): The execution ID (goal_id)
- `entity_type` (optional, string): Entity type (default: 'components')

**Returns:** Status from `GET /{entity_type}/{entity_id}/operations/{operation_name}/executions/{execution_id}`

#### `sovd_list_executions`
List all executions for an operation.

**Arguments:**
- `entity_id` (required, string): The entity identifier
- `operation_name` (required, string): The action name
- `entity_type` (optional, string): Entity type (default: 'components')

**Returns:** List from `GET /{entity_type}/{entity_id}/operations/{operation_name}/executions`

#### `sovd_cancel_execution`
Cancel a running action execution.

**Arguments:**
- `entity_id` (required, string): The entity identifier
- `operation_name` (required, string): The action name
- `execution_id` (required, string): The execution ID (goal_id)
- `entity_type` (optional, string): Entity type (default: 'components')

**Returns:** Response from `DELETE /{entity_type}/{entity_id}/operations/{operation_name}/executions/{execution_id}`

### Configuration Tools (ROS 2 Parameters)

#### `sovd_list_configurations`
List all configurations (ROS 2 parameters) for a component.

**Arguments:**
- `component_id` (required, string): The component identifier

**Returns:** Array of parameters from `GET /components/{component_id}/configurations`

#### `sovd_get_configuration`
Get a specific configuration (parameter) value.

**Arguments:**
- `component_id` (required, string): The component identifier
- `param_name` (required, string): The parameter name

**Returns:** Parameter value from `GET /components/{component_id}/configurations/{param_name}`

#### `sovd_set_configuration`
Set a configuration (parameter) value.

**Arguments:**
- `component_id` (required, string): The component identifier
- `param_name` (required, string): The parameter name
- `value` (required, any): The new parameter value (string, number, boolean, or array)

**Returns:** Response from `PUT /components/{component_id}/configurations/{param_name}`

#### `sovd_delete_configuration`
Reset a configuration (parameter) to its default value.

**Arguments:**
- `component_id` (required, string): The component identifier
- `param_name` (required, string): The parameter name

**Returns:** Response from `DELETE /components/{component_id}/configurations/{param_name}`

#### `sovd_delete_all_configurations`
Reset all configurations (parameters) to their default values.

**Arguments:**
- `component_id` (required, string): The component identifier

**Returns:** Response from `DELETE /components/{component_id}/configurations`

### Lifecycle Tools

Lifecycle status is only available for apps and components (not areas or functions).

Reading the status needs no plugin - the gateway derives it from the ROS 2 graph and,
for managed lifecycle nodes, from their reported state. Triggering a transition does need
one: the gateway routes it to a `LifecycleProvider` plugin registered for that entity, and
no provider ships with the gateway. A transition on a local app or component of a gateway
with no provider therefore gets HTTP `501` from the gateway, which this server surfaces as
`[not-implemented] Lifecycle control not available for this entity`. An aggregating
gateway forwards requests for remote entities to the peer that owns them, so a peer that
does have a provider answers normally.

#### `ros2_medkit_status_get`
Get the lifecycle status of an app or component (e.g. `ready` / `notReady`).

**Arguments:**
- `entity_type` (required, string): `apps` or `components`
- `entity_id` (required, string): The entity identifier

**Returns:** Response from `GET /{entity_type}/{entity_id}/status`

#### `ros2_medkit_status_set`
Trigger a lifecycle transition on an app or component via `PUT /{entity_type}/{entity_id}/status/{action}`. Requires a gateway-side `LifecycleProvider` plugin for the entity. **Warning:** `shutdown`, `force-shutdown`, `restart`, and `force-restart` affect the running node or host process.

**Arguments:**
- `entity_type` (required, string): `apps` or `components`
- `entity_id` (required, string): The entity identifier
- `action` (required, string): one of `start`, `restart`, `force-restart`, `shutdown`, `force-shutdown`

**Returns:** `{}` - the gateway accepts the transition with a body-less `202`, which the tool renders as an empty JSON object. With no provider registered for the entity the call returns the gateway error instead: `[not-implemented] Lifecycle control not available for this entity`.

## MCP Resources

### `sovd://openapi`

Returns information about the OpenAPI specification location.

## Development

### Setup

```bash
# Install dependencies including dev tools
poetry install

# Install pre-commit hooks
poetry run pre-commit install
```

### Running Tests

```bash
# Use the test runner script (recommended, avoids ROS 2 plugin conflicts)
poetry run python run_tests.py -v

# Or directly if not in a ROS 2 environment
poetry run pytest -v
```

### Code Quality

```bash
# Run all pre-commit hooks
poetry run pre-commit run --all-files

# Or run individually:
poetry run ruff check src/ tests/     # Linting
poetry run ruff format src/ tests/    # Formatting
poetry run mypy src/                   # Type checking
```

## License