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).
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues