mcp-rport
# MCP RPort (Queo IoT)
Servidor [MCP](https://modelcontextprotocol.io) para la consola RPort self-hosted:
**https://iot.console.queo.com.co**
Permite a un agente de Cursor (u otro cliente MCP) listar Raspberry Pi y otros clientes, inspeccionarlos y **abrir o cerrar túneles SSH** contra el servidor RPort. No ejecuta SSH por sí mismo: crea el túnel en RPort y devuelve el comando (`ssh -p PUERTO pi@iot.console.queo.com.co`).
Este MCP **no** expone ejecución remota de comandos/scripts ni el vault de RPort a propósito: un token de API con esas tools equivaldría a control completo de la flota.
## Requisitos
- Node.js 20+
- Usuario en la consola RPort
- **Token de API personal** (no uses la contraseña de login)
## Crear el token en RPort
1. Entra en https://iot.console.queo.com.co
2. Menú de usuario (arriba a la derecha) → **Settings** → **API Tokens**
3. Genera un token. Se muestra **una sola vez**; guárdalo.
4. El token hereda los permisos del usuario. Conviene un usuario que pueda ver clientes y crear túneles, no un admin global.
Autenticación contra la API: HTTP Basic con `usuario:token` hacia `https://iot.console.queo.com.co/api/v1`.
## Instalación
```bash
cd ~/mcp-rport
cp .env.example .env
# Edita .env: RPORT_API_USER y RPORT_API_TOKEN
npm install
npm test
npm run build
```
Comprueba credenciales (imprime JSON de la API, no arranca MCP):
```bash
set -a && source .env && set +a
npm run cli -- status
npm run cli -- me
npm run cli -- clients
```
## Configurar Cursor (MCP)
El servidor se lanza por **stdio**. Cursor ejecuta `bin/run-mcp.sh`, el script carga `.env` y arranca `dist/index.js`. **No** pongas `RPORT_API_TOKEN` ni `RPORT_SSH_PASSWORD` en el JSON: esos secretos viven solo en `.env` (gitignored).
### 1. Preparar el repo
```bash
cd /ruta/a/mcp-rport
cp .env.example .env
chmod 600 .env
# Edita .env: RPORT_API_USER, RPORT_API_TOKEN (y RPORT_SSH_PASSWORD si usarás rport_ssh_exec)
npm install
npm run build
chmod +x bin/run-mcp.sh
```
Sustituye `/ruta/a/mcp-rport` por la ruta absoluta de tu clone. Cursor no expande `~` ni `$HOME` en `command`.
### 2. JSON para `~/.cursor/mcp.json`
Archivo global de MCP de Cursor: **`~/.cursor/mcp.json`**.
Si el archivo **no existe**, créalo con esto (cambia la ruta):
```json
{
"mcpServers": {
"rport": {
"command": "/ruta/a/mcp-rport/bin/run-mcp.sh"
}
}
}
```
Si **ya tienes** otros servidores en `mcpServers`, añade solo la entrada `rport` junto a ellos. Ejemplo mínimo a fusionar:
```json
"rport": {
"command": "/ruta/a/mcp-rport/bin/run-mcp.sh"
}
```
No hace falta `args` ni `env` si usas el wrapper: Node y las variables salen de `.env`.
### 3. MCP a nivel de proyecto (opcional)
En lugar del archivo global, puedes poner el mismo objeto en **`<workspace>/.cursor/mcp.json`**. Sirve si solo quieres RPort en un repo concreto. La forma del JSON es idéntica (`mcpServers.rport.command`).
### 4. Activar en Cursor
1. Guarda `mcp.json`.
2. **Cursor Settings → MCP** (o *Features → MCP*): debería listarse **rport**.
3. Si no aparece o queda en rojo: recarga el servidor, o cierra y abre Cursor.
4. Comprueba que salen las tools `rport_*` (lista, túneles, `rport_ssh_exec`, etc.).
También puedes pegar el JSON desde Settings → MCP → *Add new global MCP server*; Cursor escribe el mismo `~/.cursor/mcp.json`.
### 5. Si no arranca
| Síntoma | Qué revisar |
|---------|-------------|
| Servidor rojo / no tools | Ruta de `command` incorrecta; falta `chmod +x bin/run-mcp.sh` |
| `falta dist/index.js` | `npm run build` en el repo |
| `Falta RPORT_API_USER` / token | `.env` incompleto o no está junto al script (`<repo>/.env`) |
| HTTP 401 | Usuario o token de API mal copiados |
| `node: not found` | Cursor se lanzó sin PATH de Node; usa ruta absoluta a `node` en el shebang o arranca Cursor desde una terminal con Node |
Detalle de recarga, skill y prompts: [docs/cursor.md](docs/cursor.md).
### 6. Skill del agente (opcional pero recomendado)
El MCP expone las tools; la **skill** `rport-iot` le dice al agente *cómo* usarlas. Instalación en [docs/cursor.md#skill-rport-iot](docs/cursor.md#skill-rport-iot):
```bash
mkdir -p ~/.cursor/skills/rport-iot
cp /ruta/a/mcp-rport/SKILL.md ~/.cursor/skills/rport-iot/SKILL.md
```
Reinicia Cursor. No requiere entrada en `mcp.json`.
## Tools
| Tool | Qué hace |
|------|----------|
| `rport_get_status` | Salud y versión del servidor |
| `rport_whoami` | Usuario y grupos del token |
| `rport_list_clients` | Inventario (filtros: name, hostname, connected/disconnected, tag, search) |
| `rport_get_client` | Detalle por id; opcional procesos y mountpoints |
| `rport_list_client_groups` | Grupos de clientes |
| `rport_list_tunnels` | Túneles activos + comando de acceso |
| `rport_create_tunnel` | Abre túnel (SSH por defecto al puerto 22) |
| `rport_exec_command` | Comando por el agente RPort (sin SSH; filtros allow/deny) |
| `rport_ssh_exec` | Túnel + SSH `pi`/`queo` con password de `.env` |
| `rport_delete_tunnel` | Cierra túnel (`client_id` + `tunnel_id`) |
### Flujo SSH típico
1. `rport_list_clients` con `connection_state=connected` → anota `id` y hostname
2. `rport_create_tunnel` con ese `client_id` (`remote` default `22`, `scheme` default `ssh`)
3. En tu terminal: el `ssh` que viene en `how_to_use`
4. `rport_delete_tunnel` cuando termines (si no, RPort cierra por idle, default 5 min)
El puerto **local** del túnel es del **servidor RPort**, no de tu laptop ni de Cursor. Por eso el host del SSH es `iot.console.queo.com.co`.
### ACL
Por defecto el MCP pone en la ACL la IP pública del proceso (`GET /api/v1/me/ip`). Esa es la IP de salida de **esta máquina**. Si Cursor corre aquí y tú haces SSH desde aquí, encaja. Si el agente corre en cloud y tú haces SSH desde casa, hay que pasar `acl` con **ambas** IPs.
`RPORT_DEFAULT_ACL` en `.env` fija una ACL fija. `*` o `0.0.0.0/0` deja el túnel sin restricción (evitar).
## Variables de entorno
Ver `.env.example`.
| Variable | Default | Uso |
|----------|---------|-----|
| `RPORT_API_URL` | `https://iot.console.queo.com.co` | Base del servidor |
| `RPORT_API_USER` | (obligatorio) | Usuario RPort |
| `RPORT_API_TOKEN` | (obligatorio) | Token de API |
| `RPORT_SSH_USER` | `pi` | Usuario SSH preferido (`pi` o `queo`) |
| `RPORT_SSH_PASSWORD` | (opcional) | Password SSH para `rport_ssh_exec`; solo en `.env` |
| `RPORT_DEFAULT_ACL` | vacío → IP del MCP | ACL por defecto de túneles |
## Documentación extra
- [docs/arquitectura.md](docs/arquitectura.md) — diseño del servidor y mapeo a la API
- [docs/api-rport.md](docs/api-rport.md) — endpoints usados
- [docs/seguridad.md](docs/seguridad.md) — amenazas y lo que no se expone
- [docs/cursor.md](docs/cursor.md) — recarga, skill y ejemplos de prompts
API oficial: https://apidoc.openrport.io/
Túneles: https://docs.rport.io/get-started/managing-tunnels/
Auth: https://docs.rport.io/get-started/api-authentication/
RPort OSS dejó de desarrollarse con la 1.0; RealVNC lo comercializa como RealONE. Esta instancia self-hosted sigue usando `/api/v1`.
## Desarrollo
```bash
npm run dev # MCP por stdio (lo lanza Cursor, no a mano en una TTY)
npm test
npm run build
```
No escribas logs a stdout: el protocolo MCP va por stdin/stdout. Errores a stderr.
# mcp-rport
TDQS
Scored across 10 tools
Each tool has a distinct target resource or action: clients, groups, tunnels, status, or command execution. The main overlap is between rport_create_tunnel and rport_ssh_exec, but their descriptions clearly differentiate tunnel-only creation from executing a command over an SSH tunnel.
Tool names consistently use the rport_ prefix with verb_noun patterns like list_clients, get_client, create_tunnel, and delete_tunnel. Minor deviations are rport_whoami and rport_ssh_exec, but they are still clear and do not break the overall naming scheme.
Ten tools is a well-scoped size for an RPort management server. Each tool covers a meaningful capability without unnecessary duplication or excessive granularity.
The set covers the core RPort workflows: checking status, listing and inspecting clients, viewing groups, managing tunnel lifecycle, and executing commands. Minor gaps exist, such as no group mutation or dedicated tunnel detail endpoint, but agents can accomplish the primary use cases without dead ends.