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
14 changes: 14 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.git/
.github/
.venv/
venv/
tests/
dist/
build/
*.egg-info/
__pycache__/
*.py[cod]
.pytest_cache/
.mypy_cache/
.ruff_cache/
.env
39 changes: 39 additions & 0 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Publish Docker image

on:
push:
tags: ["v*.*.*"]
workflow_dispatch:

jobs:
docker:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4

- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Image metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=semver,pattern={{version}}
type=raw,value=latest

- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
24 changes: 24 additions & 0 deletions .github/workflows/publish-pypi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Publish to PyPI

# One-time setup: create the project on PyPI and add this repo as a Trusted
# Publisher (PyPI -> project -> Publishing), then no API token is needed.

on:
release:
types: [published]
workflow_dispatch:

jobs:
pypi:
runs-on: ubuntu-latest
environment: pypi
permissions:
id-token: write # OIDC for trusted publishing
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install build
- run: python -m build
- uses: pypa/gh-action-pypi-publish@release/v1
17 changes: 17 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# VyMCP speaks MCP over stdio, so run the container attached:
# docker run -i --rm -e VYMANAGER_BASE_URL=... -e VYMANAGER_API_TOKEN=... vymcp
FROM python:3.12-slim

WORKDIR /app

# Install the package (build deps are fetched in an isolated PEP 517 env).
COPY pyproject.toml README.md LICENSE ./
COPY src ./src
RUN pip install --no-cache-dir .

# Run as a non-root user.
RUN useradd --create-home --uid 1000 vymcp
USER vymcp

# Configuration comes from the environment (see .env.example); never baked in.
ENTRYPOINT ["vymcp"]
133 changes: 25 additions & 108 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,107 +2,33 @@

[![CI](https://github.com/Community-VyProjects/VyMCP/actions/workflows/ci.yml/badge.svg)](https://github.com/Community-VyProjects/VyMCP/actions/workflows/ci.yml)

> Model Context Protocol (MCP) server for VyOS routers, powered by [VyManager](https://github.com/Community-VyProjects/VyManager).
> MCP server for VyOS routers, powered by [VyManager](https://github.com/Community-VyProjects/VyManager).

VyMCP lets AI agents and MCP-aware tools (Claude, IDEs, automation) **read and manage VyOS routers** through a safe, audited interface. Rather than talking to routers directly, VyMCP wraps the VyManager API — so every action inherits VyManager's per-user authentication, role-based access control, audit logging, and version-aware (VyOS 1.4 / 1.5) configuration handling.
VyMCP lets AI agents read and manage VyOS routers through the VyManager API — inheriting VyManager's authentication, RBAC, audit, and commit-confirm safety. It never talks to routers directly.

> **Status: alpha.** Read tools cover every VyManager feature; guarded write tools cover ~86 features through a generic, discovery-driven proposer (plus curated firewall-group tools). The transport is stdio today (hosted HTTP/SSE is planned). Interfaces may still change.
> **Status: alpha.** stdio transport; interfaces may change.

---
## Install

## Why a server, not a direct integration

VyMCP deliberately **does not** connect to VyOS devices itself. It is a thin, stateless translation layer in front of VyManager's REST API. This keeps a single, enforced source of truth for everything that matters in an enterprise environment:

- **Per-user identity** — a VyMCP request acts as a real VyManager user, never a shared service account.
- **RBAC** — a token can never exceed its owner's permissions.
- **Audit** — every configuration change is attributed to the user, token, and instance.
- **Commit-confirm safety** — writes ride VyManager's existing safety rails.
- **Version awareness** — VyOS 1.4 vs 1.5 differences are handled by VyManager, not reimplemented here.

```
MCP client (Claude / IDE / agent)
│ MCP over HTTP+SSE (or stdio)
VyMCP ── stateless translation layer
│ Authorization: Bearer vym_…
│ X-VyOS-Instance-Id: <instance>
VyManager API ── RBAC · audit · commit-confirm · 1.4/1.5 mappers
VyOS 1.4 / 1.5
```bash
pipx install git+https://github.com/Community-VyProjects/VyMCP # provides `vymcp`
```

## Security model

VyMCP authenticates to VyManager with a **personal access token** that a user mints in the VyManager UI (Sites → API Tokens). Tokens are least-privilege by design:

- **Read-only scope** — a token can be restricted so it can never make configuration changes.
- **Instance/site scoping** — a token can be limited to specific routers or sites (always within the user's own grants).
- **Per-call targeting** — the target router is selected per request via the instance the token is allowed to reach.

This means you can hand VyMCP a token that, for example, can only *read* a single production router — and that limit is enforced by VyManager, not by VyMCP.

## Tools

**Read (always available):**

| Tool | Purpose |
|---|---|
| `list_instances` | The instances this token can reach (returns `instance_id`s for the other tools). |
| `list_features` | Every VyOS feature VyMCP can read. |
| `get_capabilities(feature, instance_id)` | Version-aware capability flags for a feature. |
| `get_config(feature, instance_id)` | Normalized configuration for a feature. |

**Write (only when `VYMANAGER_ENABLE_WRITES=true`):** changes go through a mandatory
**propose → apply** flow — a `propose_*` tool validates and returns a `plan_id` without
touching anything, then `apply_change(plan_id, confirm=true)` executes that one reviewed
plan. Plans are single-use, time-limited, and bound to their target instance.

| Tool | Purpose |
|---|---|
| `describe_feature_operations(feature)` | The operations available for a feature (op names, arg counts, descriptions, and any required top-level/subject fields). |
| `propose_operations(feature, instance_id, operations, fields)` | Generic proposer — build a change for **~86 features** from the discovered vocabulary (no effect until applied). |
| `propose_create_address_group` / `propose_add_address_group_members` / `propose_remove_address_group_members` / `propose_delete_address_group` | Curated firewall address-group proposers (nicer ergonomics for a common case). |
| `apply_change(plan_id, confirm)` | Apply a proposed plan, protected by VyManager's commit-confirm when available. |
| `confirm_change(instance_id)` | Keep a pending commit-confirm change (settles the device, stops the rollback). |
| `discard_changes(instance_id)` | Drop unsaved changes — only when no commit-confirm is pending. |
| `get_pending_changes(instance_id)` | Show the commit-confirm rollback window and any pending change. |

`propose_operations` covers every feature whose batch handler dispatches uniformly — the routing, firewall, NAT, service, VPN, system surface plus interface sub-types. A few features with bespoke handlers (`route`, bridge firewall, `ethernet`) are intentionally **not** exposed generically, because their custom per-op wiring can't be driven safely by introspection alone; manage those in the VyManager UI.

### Guardrails
Or `pip install .` from a clone, or run the Docker image `ghcr.io/community-vyprojects/vymcp` (attached, with `-i`).

- **Read-only by default** — write tools are not even registered unless `VYMANAGER_ENABLE_WRITES=true`, on top of the token's own read-only scope (a read-only token is rejected by VyManager regardless).
- **Vocabulary-bound, not arbitrary** — `propose_operations` only accepts operations that exist in the feature's discovered vocabulary (validated against VyManager's own contract); there is no "set any path" tool.
- **Propose → apply** — `apply_change` requires `confirm=true` and a valid plan id, so the model can only apply a change a human has seen.
- **Never auto-confirms** — applied changes ride VyManager's commit-confirm timer. That timer is a *lockout safety net*: if a change cuts off access to the router, the device auto-reverts (reboots) and you regain access. To keep a change, call `confirm_change`; to undo one that didn't lock you out, `confirm_change` then apply the inverse change.
## Configure

## Requirements
Set environment variables (see `.env.example`):

- A reachable [VyManager](https://github.com/Community-VyProjects/VyManager) instance.
- A VyManager API token (Sites → API Tokens; read-only recommended to start).
- One or more VyOS 1.4 / 1.5 routers registered in VyManager.
- Python 3.10+.
| Variable | Required | Purpose |
|---|---|---|
| `VYMANAGER_BASE_URL` | yes | VyManager API URL |
| `VYMANAGER_API_TOKEN` | yes | A `vym_` token (Sites → API Tokens; read-only recommended) |
| `VYMANAGER_ENABLE_WRITES` | no | Enable write tools (default off) |

## Getting started
`VYMANAGER_VERIFY_SSL` (default `true`) and `VYMANAGER_TIMEOUT` (default `30`) are optional.

Install:

```bash
pip install -e . # or: pip install -e ".[dev]" for tests
```

Configure via environment variables (see `.env.example`):

```bash
export VYMANAGER_BASE_URL="https://vymanager.example.com"
export VYMANAGER_API_TOKEN="vym_…" # read-only recommended
# export VYMANAGER_ENABLE_WRITES=true # opt in to write tools
```

Register with an MCP client (stdio). Example client config:
## Connect an MCP client

```json
{
Expand All @@ -118,29 +44,20 @@ Register with an MCP client (stdio). Example client config:
}
```

## Tools

- **Read** (always): `list_instances`, `list_features`, `get_capabilities`, `get_config`.
- **Write** (only when `VYMANAGER_ENABLE_WRITES=true`): `describe_feature_operations` + `propose_operations` cover ~86 features; `apply_change` / `confirm_change` / `discard_changes` / `get_pending_changes` drive the rollout.

Writes are safe by design: off by default, capped to the token's scope, and gated by a **propose → apply** flow. `apply_change` requires explicit confirmation and rides VyManager's commit-confirm auto-rollback (a lockout safety net) — it never auto-confirms.

## Development

```bash
pip install -e ".[dev]"
pytest # deterministic; no live device needed
ruff check .
mypy
pytest && ruff check . && mypy
```

## Roadmap

- [x] stdio server + token auth to VyManager
- [x] Read tools across all features
- [x] Guarded write tools (propose → apply → commit-confirm)
- [x] Generic, discovery-driven writes across ~86 features
- [ ] Cover the remaining bespoke-handler features (`route`, bridge firewall, `ethernet`)
- [ ] Hosted remote transport (HTTP/SSE) with per-user auth
- [ ] Packaging and deployment docs

## Related projects

- **[VyManager](https://github.com/Community-VyProjects/VyManager)** — the web UI and API that VyMCP builds on.

## License

See `LICENSE` for details.
GPLv3 — see [`LICENSE`](LICENSE).
Loading