Skip to main content
Glama
ledeolivcisco

opampserver-mcp

README.md
# opampserver

A Python implementation of an [OpAMP](https://opentelemetry.io/docs/specs/opamp/) (Open Agent
Management Protocol) server, built on FastAPI and the official protobuf message definitions from
[open-telemetry/opamp-spec](https://github.com/open-telemetry/opamp-spec). It supports both
transports the spec defines (WebSocket and plain HTTP) on a single endpoint, and covers status
reporting, remote config delivery, and health reporting.

## Feature scope

**Implemented:**
- `ReportsStatus` — sequence-number tracking and gap detection (asks the agent to resend full
  state via `ReportFullState` when a gap is detected)
- Remote config delivery (`AcceptsRemoteConfig` / `ReportsEffectiveConfig` /
  `ReportsRemoteConfig`) — config is resolved per-agent from YAML templates or a per-instance
  override, and only re-sent when its hash differs from what the agent has already applied
- `ReportsHealth` — parses and stores `ComponentHealth`, including nested sub-component health
- Partial/compressed status messages — fields the agent omits (per the spec's status-compression
  rules) don't overwrite previously known values
- A small admin REST API for operators (list agents, inspect one — with optional decoded
  config — set/clear a per-instance config override; pushed immediately if the agent is
  connected over WebSocket)

**Out of scope for v1** (candidates for future work):
- Package/binary updates (`AcceptsPackages`, `PackagesAvailable`)
- Connection-settings management and the certificate/CSR flow
  (`AcceptsOpAMPConnectionSettings`, TOFU, agent-initiated CSR signing)
- `ServerToAgentCommand` (restart), custom messages/capabilities, `ReportsAvailableComponents`,
  heartbeat handling

## Project layout

```
opampserver/
  proto/                # vendored opamp-spec .proto files + generated protobuf stubs
  models.py              # protobuf <-> plain-dict conversion helpers
  connection.py          # Connection abstraction (WebSocket vs. one-shot HTTP) + ConnectionManager
  registry.py             # per-message bookkeeping: sequence gaps, instance_uid assignment, merging
  config_engine.py        # remote config resolution (templates + overrides) and hash-diffing
  health.py                # ComponentHealth tree summarization
  pipeline.py               # shared registry -> config-engine processing used by both transports
  storage/
    schema.sql               # SQLite schema (agents, agent_configs)
    repository.py             # async SQLite repository
  transport/
    http.py                    # plain-HTTP transport (gzip, content-type, size limits)
    ws.py                       # WebSocket transport
  admin_api.py                  # operator-facing REST API
  app.py                         # FastAPI app wiring
  main.py                        # uvicorn entrypoint
opampserver_mcp/           # standalone MCP server exposing the admin API as LLM tools
  client.py                # async httpx client for the admin API + error mapping
  server.py                # FastMCP server with the 4 tools
  main.py                  # CLI entrypoint (opampserver-mcp)
scripts/gen_proto.sh      # regenerates protobuf stubs from the vendored .proto sources
config/templates.yaml      # default/group remote-config templates
tests/                     # unit + integration tests
```

## Setup

```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
```

If you bump the pinned `open-telemetry/opamp-spec` commit in `scripts/gen_proto.sh`, re-run it to
regenerate the protobuf stubs:

```bash
bash scripts/gen_proto.sh
```

## Running the server

```bash
uvicorn opampserver.app:app --host 127.0.0.1 --port 4320
# or: python -m opampserver.main
```

Bind to `127.0.0.1` explicitly if another process (e.g. Docker) is already listening on
port 4320 over IPv6 — otherwise `curl http://localhost:4320/...` may hit the wrong listener
while agents using `127.0.0.1` reach this server fine.

The server listens for both HTTP POST and WebSocket connections on `/v1/opamp`. By default it
stores agent state in `opampserver.sqlite3` and loads remote config templates from
`config/templates.yaml` (both relative to the working directory); override either with:

```bash
OPAMPSERVER_DB_PATH=/path/to/state.sqlite3 \
OPAMPSERVER_CONFIG_PATH=/path/to/templates.yaml \
uvicorn opampserver.app:app --port 4320
```

## Trying it out

Point a real agent at it — e.g. an OpenTelemetry Collector with the `opamp` extension configured
against `ws://localhost:4320/v1/opamp` — or send a raw HTTP request with a serialized
`AgentToServer` protobuf message and `Content-Type: application/x-protobuf`.

## Admin API

The admin API has **no authentication**. Bind the server to `127.0.0.1` for local use only,
or place it behind your own auth/proxy if exposed beyond localhost.

Read-only endpoints return agent state from SQLite. Config file bodies are base64-encoded
(`body_b64`) by default; pass `?decode=true` to get plain-text `body` instead (UTF-8, with
invalid bytes replaced).

| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/api/agents` | List all known agents |
| `GET` | `/api/agents/{instance_uid}` | Get one agent |
| `PUT` | `/api/agents/{instance_uid}/config` | Set a per-instance config override |
| `DELETE` | `/api/agents/{instance_uid}/config` | Clear the override |

Query parameters for `GET`:

- `decode` (bool, default `false`) — when `true`, replace `body_b64` with decoded `body` in
  `effective_config`.

Examples:

```bash
# List instance UIDs only
curl -s http://127.0.0.1:4320/api/agents | jq -r '.[].instance_uid'

# Full agent record (config still base64)
curl -s http://127.0.0.1:4320/api/agents/<instance_uid>

# Human-readable effective config (OTel Collector uses "" as the config key)
curl -s "http://127.0.0.1:4320/api/agents/<instance_uid>?decode=true" \
  | jq -r '.effective_config[""].body'

# Push a per-instance override (body is plain text in the request)
curl -X PUT http://127.0.0.1:4320/api/agents/<instance_uid>/config \
  -H "Content-Type: application/json" \
  -d '{"files": {"config.yaml": {"body": "receivers:\n  otlp: {}\n", "content_type": "text/yaml"}}}'

# Remove override (agent falls back to template on next poll)
curl -X DELETE http://127.0.0.1:4320/api/agents/<instance_uid>/config
```

Use `127.0.0.1` rather than `localhost` when testing from the same machine if port 4320 is
shared with another IPv6 listener.

## MCP server

`opampserver_mcp/` is a standalone [MCP](https://modelcontextprotocol.io/) server that exposes
the admin API as tools for LLM clients (Claude Code, Claude Desktop, or any MCP-capable client).
It's a separate process that talks to the OpAMP server over its REST API — no changes to, or
imports from, the `opampserver` package.

Tools: `list_agents` (compact summaries), `get_agent` (full detail, config decoded by default),
`set_agent_config` (validates YAML client-side before pushing; reports whether the config was
delivered immediately over WebSocket), `delete_agent_config` (agent falls back to templates).

Install and run (the OpAMP server must be running):

```bash
pip install -e ".[mcp]"

# stdio transport (default — for local clients that spawn the server)
opampserver-mcp

# streamable HTTP transport — endpoint at http://127.0.0.1:4321/mcp
opampserver-mcp --transport http --port 4321

# point at a non-default OpAMP server
opampserver-mcp --opamp-url http://otherhost:4320
```

Flags have env-var equivalents: `OPAMP_MCP_TRANSPORT`, `OPAMP_BASE_URL`, `OPAMP_MCP_HOST`,
`OPAMP_MCP_PORT`.

Register in **Claude Code**:

```bash
# stdio
claude mcp add opamp -- /path/to/confOpAmpMCP/.venv/bin/opampserver-mcp
# or HTTP (start opampserver-mcp --transport http first)
claude mcp add --transport http opamp http://127.0.0.1:4321/mcp
```

Register in **Claude Desktop** (`claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "opamp": {
      "command": "/path/to/confOpAmpMCP/.venv/bin/opampserver-mcp"
    }
  }
}
```

Example prompts once connected: *"List all OpAMP agents and their health"*, *"Show the effective
config for the agent named otelcol"*, *"Push a config to agent `<uid>` that enables the OTLP gRPC
receiver"*, *"Reset agent `<uid>` back to the template config"*.

## Testing

```bash
pytest -q
```

32 tests cover: `AgentRegistry` sequence-gap detection and partial-message merging, config-engine
template/group/override resolution and hash-diffing, health-tree summarization, admin API decode
flag behavior, full integration round trips over both HTTP and WebSocket (including a live
admin-triggered config push), and the MCP server tools (invoked through a real in-memory MCP
client session against the in-process FastAPI app).