Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 38 additions & 17 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ name: Publish (PyPI + MCP Registry)

on:
workflow_dispatch:
push:
tags:
- 'v*'

concurrency:
group: publish-${{ github.ref }}
Expand All @@ -11,54 +14,73 @@ jobs:
pypi:
name: Publish to PyPI
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
id-token: write # REQUIRED for PyPI Trusted Publishing (OIDC)
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Validate version matches tag
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
run: |
set -euo pipefail

TAG_VERSION="${GITHUB_REF#refs/tags/v}"
PYPROJECT_VERSION=$(grep -oP '^version = "\K[^"]+' pyproject.toml)
SERVER_VERSION=$(grep -oP '"version":\s*"\K[^"]+' server.json | head -1)
SERVER_PKG_VERSION=$(grep -oP '"version":\s*"\K[^"]+' server.json | tail -1)

ERRORS=0
[ "$PYPROJECT_VERSION" != "$TAG_VERSION" ] && echo "pyproject.toml: $PYPROJECT_VERSION != $TAG_VERSION" && ERRORS=1
[ "$SERVER_VERSION" != "$TAG_VERSION" ] && echo "server.json: $SERVER_VERSION != $TAG_VERSION" && ERRORS=1
[ "$SERVER_PKG_VERSION" != "$TAG_VERSION" ] && echo "server.json package: $SERVER_PKG_VERSION != $TAG_VERSION" && ERRORS=1

if [ $ERRORS -eq 1 ]; then
echo "Please update all version fields to $TAG_VERSION before creating the tag."
exit 1
fi

echo "All versions match: $TAG_VERSION"

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
python-version: "3.10"

- name: Install uv
run: pipx install uv

# --- Optional checks (keep for now; can be moved to PR CI later) ---
- name: Sync deps
- name: Sync dependencies
run: uv sync

- name: Ruff lint
- name: Run ruff lint
run: uvx ruff check .

- name: Ruff format check
- name: Run ruff format check
run: uvx ruff format --check .

- name: Run tests (if present)
- name: Run tests
run: |
if [ -d tests ]; then
uv run pytest -q
else
echo "No tests/ directory; skipping."
fi

# --- Build artifacts for PyPI ---
- name: Build sdist & wheel
- name: Build package
run: uv build

# --- Publish using Trusted Publishing (no tokens) ---
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: dist
# For a dress rehearsal, you can set:
# repository-url: https://test.pypi.org/legacy/

registry:
name: Publish to MCP Registry
runs-on: ubuntu-latest
timeout-minutes: 10
needs: pypi
permissions:
contents: read
Expand All @@ -67,23 +89,22 @@ jobs:
- name: Checkout
uses: actions/checkout@v4

# Small delay to allow PyPI metadata to propagate
- name: Wait briefly
- name: Wait for PyPI metadata propagation
run: sleep 20

- name: Install MCP Publisher
run: |
set -e
set -euo pipefail
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')
echo "Get the latest release version"
LATEST_VERSION=$(curl -s https://api.github.com/repos/modelcontextprotocol/registry/releases/latest | jq -r '.tag_name')
echo "Installing MCP Publisher version: $LATEST_VERSION"
curl -L "https://github.com/modelcontextprotocol/registry/releases/download/${LATEST_VERSION}/mcp-publisher_${LATEST_VERSION#v}_${OS}_${ARCH}.tar.gz" \
curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_${OS}_${ARCH}.tar.gz" \
| tar xz mcp-publisher

- name: Login to MCP Registry (OIDC)
- name: Login to MCP Registry
run: ./mcp-publisher login github-oidc

- name: Publish server.json to MCP Registry
- name: Publish to MCP Registry
run: ./mcp-publisher publish
30 changes: 10 additions & 20 deletions .github/workflows/publish_mcp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,36 @@ name: Publish MCP Registry

on:
workflow_dispatch:
workflow_run:
workflows: ["Publish PyPI"]
types: [completed]

concurrency:
group: publish-${{ github.ref }}
group: publish-mcp-${{ github.ref }}
cancel-in-progress: true

jobs:
on-success:
mcp-registry:
name: Publish to MCP Registry
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
timeout-minutes: 10
permissions:
contents: read
id-token: write
id-token: write # REQUIRED for MCP Registry GitHub OIDC login
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install MCP Publisher
run: |
set -e
set -euo pipefail
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')
echo "Get the latest release version"
LATEST_VERSION=$(curl -s https://api.github.com/repos/modelcontextprotocol/registry/releases/latest | jq -r '.tag_name')
curl -L "https://github.com/modelcontextprotocol/registry/releases/download/${LATEST_VERSION}/mcp-publisher_${LATEST_VERSION#v}_${OS}_${ARCH}.tar.gz" | tar xz mcp-publisher
echo "Installing MCP Publisher version: $LATEST_VERSION"
curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_${OS}_${ARCH}.tar.gz" \
| tar xz mcp-publisher

- name: Login to MCP Registry
run: ./mcp-publisher login github-oidc

- name: Publish to MCP Registry
run: |
echo "PyPI workflow succeeded - Publishing to MCP Registry"
./mcp-publisher publish

on-failure:
name: Handle PyPI Failure
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'failure' }}
steps:
- name: Log PyPI Failure
run: echo "PyPI workflow failed - MCP Registry publication skipped"
run: ./mcp-publisher publish
25 changes: 9 additions & 16 deletions .github/workflows/publish_pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ name: Publish PyPI

on:
workflow_dispatch:
push:
tags:
- "v*"

concurrency:
group: publish-pypi-${{ github.ref }}
Expand All @@ -14,47 +11,43 @@ jobs:
pypi:
name: Publish PyPI
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
id-token: write # REQUIRED for PyPI Trusted Publishing (OIDC)
id-token: write
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
python-version: "3.10"

- name: Install uv
run: pipx install uv

# --- Optional checks (keep for now; can be moved to PR CI later) ---
- name: Sync deps
- name: Sync dependencies
run: uv sync

- name: Ruff lint
- name: Run ruff lint
run: uvx ruff check .

- name: Ruff format check
- name: Run ruff format check
run: uvx ruff format --check .

- name: Run tests (if present)
- name: Run tests
run: |
if [ -d tests ]; then
uv run pytest -q
else
echo "No tests/ directory; skipping."
fi

# --- Build artifacts for PyPI ---
- name: Build sdist & wheel
- name: Build package
run: uv build

# --- Publish using Trusted Publishing (no tokens) ---
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: dist
# For a dress rehearsal, you can set:
# repository-url: https://test.pypi.org/legacy/
packages-dir: dist
9 changes: 3 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,9 @@ The MCP server is version-agnostic (ROS1 or ROS2) and works with any MCP-compati

### Installation

Follow the [installation guide](docs/installation.md) for step-by-step instructions:
1. Clone the repository
2. Install `uv` and `rosbridge`
3. Install Claude Desktop (or any MCP-enabled client)
4. Configure your client to connect to the ROS MCP Server
5. Start `rosbridge` on the target robot
Follow the [installation guide](docs/installation.md) for step-by-step instructions to install, run, and troubleshoot the ROS-MCP server.

For developers, we also have instructions for [installation from source](docs/installation-from-source.md)

---

Expand Down
131 changes: 131 additions & 0 deletions docs/installation-alternatives.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Alternate Installation and Configuration Options

This document covers alternative methods for installing the ROS-MCP server, configuring it with different transport options, and using it with different LLM clients.

---

## Alternative Installation Options

### Option A: Install using pip
For users who prefer traditional pip installation:

```bash
pip install ros-mcp
```
> **⚠️ Important**: This package requires pip version 23.0 or higher. Check your pip version with `pip --version` and upgrade if needed:
> **⚠️ Important**: This package requires python version 3.10 or higher. Check your python version with `python3 --version` and upgrade if needed:
```bash
python3 -m pip install --upgrade pip
```

### Option B: Install from Source
For developers or advanced users who need to modify the source code, see [Installation from Source](installation-from-source.md).

### Option C: Install from Source using pip
For developers who want to install from source but still use pip:

```bash
# Clone the repository
git clone https://github.com/robotmcp/ros-mcp-server.git
cd ros-mcp-server

# Install from source using pip
pip install .
```

> **⚠️ Important**: This package requires pip version 23.0 or higher. Check your pip version with `pip --version` and upgrade if needed:
> **⚠️ Important**: This package requires python version 3.10 or higher. Check your python version with `python3 --version` and upgrade if needed:
```bash
python3 -m pip install --upgrade pip
```

---

## Alternate Configuration - HTTP Transport

The default configurations set up the MCP server using the STDIO transport layer, which launches the server as a plugin automatically on launching Claude.

It is also possible to configure the MCP server using the HTTP transport layer, which configures Claude to connect to the MCP server when it is launched as a standalone application.

For HTTP transport, the configuration is the same across all platforms. First start the MCP server manually:

**Linux/macOS/Windows(WSL):**
```bash
cd /<ABSOLUTE_PATH>/ros-mcp-server
# Using command line arguments (recommended)
ros-mcp --transport streamable-http --host 127.0.0.1 --port 9000

# Or using environment variables (legacy)
export MCP_TRANSPORT=streamable-http
export MCP_HOST=127.0.0.1
export MCP_PORT=9000
uv run server.py
```

Then configure Claude Desktop to connect to the HTTP server (same for all platforms):

```json
{
"mcpServers": {
"ros-mcp-server-http": {
"name": "ROS-MCP Server (http)",
"transport": "http",
"url": "http://127.0.0.1:9000/mcp"
}
}
}
```

---

## Comparison between default (STDIO) and HTTP Transport

#### STDIO Transport (Default)
- **Best for**: Local development, single-user setups
- **Pros**: Simple setup, no network configuration needed
- **Cons**: MCP server and LLM/MCP client need to be running on the local machine.
- **Use case**: Running MCP server directly with your LLM client

#### HTTP/Streamable-HTTP Transport
- **Best for**: Remote access, multiple clients, production deployments
- **Pros**: Network accessible, multiple clients can connect
- **Cons**: Requires network configuration, MCP server needs to be run independently.
- **Use case**: Remote robots, team environments, web-based clients

---

## Alternate Clients

### Cursor IDE
For detailed Cursor setup instructions, see our [Cursor Tutorial](../examples/7_cursor/README.md).

### ChatGPT
For detailed ChatGPT setup instructions, see our [ChatGPT Tutorial](../examples/6_chatgpt/README.md).

### Google Gemini
For detailed Gemini setup instructions, see our [Gemini Tutorial](../examples/2_gemini/README.md).

### Custom MCP Client
You can also use the MCP server directly in your Python code.
<details>
<summary>Here is a python example of how to integrate it programmatically</summary>

```python
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

async def main():
server_params = StdioServerParameters(
command="uv",
args=["--directory", "/path/to/ros-mcp-server", "run", "server.py"]
)

async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
# Use the MCP server
result = await session.call_tool("get_topics", {})
print(result)
```

</details>

Loading