Skip to content

Initial MR #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 7, 2025
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
12 changes: 12 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.venv
.env
.git
.gitlab-ci.yml
.gitignore
__pycache__
gl-sast-report.json

Dockerfile
README.md
dev_tests
data/
78 changes: 78 additions & 0 deletions .github/workflows/docker.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#
name: Create and publish a Docker image for Litmus MCP Server

on:
push:
branches: ['main']
tags:
- "v*.*.*"
pull_request:
branches: ['main']
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
PLATFORMS: "linux/amd64"

jobs:
docker-build:
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-build') }}
name: Build Docker image (ghcr.io/litmusautomation/litmus-mcp-server)
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
attestations: write
id-token: write

steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Log in to the Container registry
if: ${{ github.event_name != 'pull_request' }}
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
env:
DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha

- name: Set up QEMU
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
with:
platforms: ${{ env.PLATFORMS }}

- name: Build and push Docker image
id: push
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0
with:
platforms: ${{ env.PLATFORMS }}
push: ${{ github.event_name != 'pull_request' }}
sbom: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
annotations: ${{ steps.meta.outputs.annotations }}
cache-from: type=gha
cache-to: type=gha,mode=max

# - name: Generate artifact attestation
# if: ${{ github.event_name != 'pull_request' }}
# uses: actions/attest-build-provenance@c074443f1aee8d4aeeae555aebba3282517141b2 # v2.2.3
# with:
# subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}
# subject-digest: ${{ steps.push.outputs.digest }}
16 changes: 16 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info

# Virtual environments
.venv

# local env
.env
.idea
dev_tests
.directory
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.12
20 changes: 20 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM python:3.12-slim
LABEL authors="Litmus Automation, Inc."


RUN apt-get update && apt-get install -y \
build-essential curl \
&& rm -rf /var/lib/apt/lists/*

COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
COPY --from=ghcr.io/astral-sh/uv:latest /uvx /bin/uvx
WORKDIR /app

COPY . .
RUN uv venv && uv sync --all-groups

ENV PATH="/app/.venv/bin:$PATH"

#CMD python src/server.py --transport=sse & python src/webclient.py src/server.py && wait
RUN chmod +x run.sh
CMD ["./run.sh"]
198 changes: 198 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
<p align="center">
<a href="https://litmus.io">
<picture>
<source media="(prefers-color-scheme: light)" srcset="static/litmus-logo-light.svg" />
<source media="(prefers-color-scheme: dark)" srcset="static/litmus-logo-dark.svg" />
<img src="static/litmus-logo-light.svg" height="60" alt="Litmus logo" />
</picture>
</a>
</p>

<p align="center">
<a href="https://docs.litmus.io">
<img src="https://img.shields.io/badge/Litmus-Docs-2acfa6?style=flat-square" alt="Documentation" />
</a>
<a href="https://www.linkedin.com/company/litmus-automation/" >
<img src="https://img.shields.io/badge/LinkedIn-Follow-0a66c2?style=flat-square" alt="Follow on LinkedIn" />
</a>
</p>

# Litmus MCP Server

The official [Litmus Automation](https://litmus.io) **Model Context Protocol (MCP) Server** enables LLMs and intelligent systems to interact with [Litmus Edge](https://litmus.io/products/litmus-edge) for device configuration, monitoring, and management. It is built on top of the MCP SDK and adheres to the [Model Context Protocol spec](https://modelcontextprotocol.io/).

<div>
<picture>
<source media="(prefers-color-scheme: light)" srcset="static/MCP-server-arch-diagram.png" />
<img src="static/MCP-server-arch-diagram.png" alt="Litmus MCP Server Architecture Diagram" />
</picture>
</div>

## Table of Contents

- [Getting Started](#getting-started)
- [Quick Launch (Docker)](#quick-launch-docker)
- [Cursor IDE Setup](#cursor-ide-setup)
- [API](#api)
- [Usage](#usage)
- [Server-Sent Events (SSE)](#server-sent-events-sse)
- [Litmus Central](#litmus-central)
- [Integrations](#integrations)
- [Cursor IDE](#cursor-ide)
- [Claude Desktop](#claude-desktop)
- [VS Code / Copilot](#vs-code--copilot)
- [Windsurf](#windsurf)

---

## Getting Started

### Quick Launch (Docker)

Run the server in Docker:

```bash
docker run -d --name litmus-mcp-server -p 8000:8000 ghcr.io/litmusautomation/litmus-mcp-server/mcp:latest
```

### Cursor IDE Setup

Example `mcp.json` configuration:

```json
{
"mcpServers": {
"litmus-mcp-server": {
"url": "http://<IP Address>:8000/sse"
}
}
}
```

See the [Cursor docs](https://docs.cursor.com/context/model-context-protocol) for more info.

---

## API

| Category | Function Name | Description |
|---------------------------|----------------------------------------|-------------|
| **Edge System Config** | `get_current_environment_config` | Get current environment configuration used for Litmus Edge connectivity. |
| | `update_environment_config` | Update environment variable config for connecting to Litmus Edge. |
| | `get_current_config` | Retrieve current Litmus Edge instance configuration. |
| | `update_config` | Update configuration of the device or container running Litmus Edge. |
| **DeviceHub** | `get_litmusedge_driver_list` | List supported Litmus Edge drivers. |
| | `get_devicehub_devices` | List devices configured in DeviceHub. |
| | `get_devicehub_device_tags` | Retrieve tags for a specific DeviceHub device. |
| | `get_current_value_of_devicehub_tag` | Get current value of a specific device tag. |
| | `create_devicehub_device` | Register a new DeviceHub device. Supports various protocols and templates for register-based data polling. |
| **Device Identity** | `get_litmusedge_friendly_name` | Retrieve the user-friendly name of the device. |
| | `set_litmusedge_friendly_name` | Assign or update the friendly name. |
| **LEM Integration** | `get_cloud_activation_status` | Check cloud activation and Litmus Edge Manager (LEM) connection status. |
| **Docker Management** | `get_all_containers_on_litmusedge` | List all containers on Litmus Edge. |
| | `run_docker_container_on_litmusedge` | Launch a Docker container via Litmus Edge Marketplace (not the MCP host). |
| **Topic Subscription** | `get_current_value_on_topic` | Subscribe to current values on a Litmus Edge topic. Use global `NATS_STATUS = False` to unsubscribe. |
| | `get_multiple_values_from_topic` | Retrieve multiple values from a topic for plotting or batch access. |

---

## Usage

### Server-Sent Events (SSE)

This server supports the [MCP SSE transport](https://modelcontextprotocol.io/docs/concepts/transports#server-sent-events-sse) for real-time communication.

- **Client endpoint:** `http://<server-ip>:8000/sse`
- **Default binding:** `0.0.0.0:8000/sse`
- **Communication:**
- Server → Client: Streamed via SSE
- Client → Server: HTTP POST

---

## Litmus Central

Download or try Litmus Edge via [Litmus Central](https://central.litmus.io).

---

## Integrations

### Cursor IDE

Add to `~/.cursor/mcp.json` or `.cursor/mcp.json`:

```json
{
"mcpServers": {
"litmus-mcp-server": {
"url": "http://<IP Address>:8000/sse"
}
}
}
```

[Cursor docs](https://docs.cursor.com/context/model-context-protocol)

---

### Claude Desktop

Add to `claude_desktop_config.json`:

```json
{
"mcpServers": {
"litmus-mcp-server": {
"url": "http://<IP Address>:8000/sse"
}
}
}
```

[Anthropic Docs](https://docs.anthropic.com/en/docs/agents-and-tools/mcp)

---

### VS Code / GitHub Copilot

#### Manual Configuration

In VS Code:
Open User Settings (JSON) → Add:

```json
{
"mcpServers": {
"litmus-mcp-server": {
"url": "http://<IP Address>:8000/sse"
}
}
}
```

Or use `.vscode/mcp.json` in your project.

[VS Code MCP Docs](https://code.visualstudio.com/docs/copilot/chat/mcp-servers)

---

### Windsurf

Add to `~/.codeium/windsurf/mcp_config.json`:

```json
{
"mcpServers": {
"litmus-mcp-server": {
"url": "http://<IP Address>:8000/sse"
}
}
}
```

[Windsurf MCP Docs](https://docs.windsurf.com/windsurf/mcp)

---

© 2025 Litmus Automation, Inc. All rights reserved.
47 changes: 47 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
[project]
name = "litmus-mcp-server"
version = "0.1.0"
description = "Litmus MCP Server and client combo"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"fastapi>=0.115.12",
"jinja2>=3.1.6",
"litmussdk",
"mcp[cli]>=1.6.0",
"nats-py>=2.10.0",
"numpy>=2.2.5",
"python-multipart>=0.0.20",
]

[dependency-groups]
lint = [
"black>=25.1.0",
"radon>=6.0.1",
"ruff>=0.11.4",
]
llm-sdks = [
"anthropic>=0.49.0",
"openai-agents>=0.0.13",
]
test = []

[tool.uv.sources]
litmussdk = { url = "https://github.com/litmusautomation/litmus-sdk-releases/releases/download/1.0.0/litmussdk-1.0.0-py3-none-any.whl" }

[tool.ruff]
exclude = [
".venv",
"venv",
"site-packages",
"build",
"dist",
".pytest_cache",
".ruff_cache",
"__init__.py",
"tester"
]
line-length = 88
indent-width = 4
[tool.ruff.lint.pydocstyle]
convention = "google"
13 changes: 13 additions & 0 deletions run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash
set -e

# Start server
mcp run src/server.py --transport=sse &
SERVER_PID=$!

# Start web client
python src/web_client.py src/server.py &
CLIENT_PID=$!

# Wait for both
wait $SERVER_PID $CLIENT_PID
Empty file added src/__init__.py
Empty file.
Loading