mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-28 12:46:31 +08:00
Compare commits
1 Commits
fix/deskto
...
bb/rive-mc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e1dc5c4b8 |
140
optional-skills/creative/rive-mcp/SKILL.md
Normal file
140
optional-skills/creative/rive-mcp/SKILL.md
Normal file
@@ -0,0 +1,140 @@
|
||||
---
|
||||
name: rive-mcp
|
||||
description: "Create and edit Rive animations through MCP."
|
||||
version: 1.0.0
|
||||
author: Hermes Agent
|
||||
license: MIT
|
||||
platforms: [linux, macos, windows]
|
||||
metadata:
|
||||
hermes:
|
||||
category: creative
|
||||
tags: [Rive, Animation, MCP, Motion, State Machines, Design-to-Code]
|
||||
related_skills: [blender-mcp]
|
||||
prerequisites:
|
||||
commands: [hermes]
|
||||
---
|
||||
|
||||
# Rive Skill
|
||||
|
||||
Optional skill — **not active until installed**:
|
||||
|
||||
```bash
|
||||
hermes skills install official/creative/rive-mcp
|
||||
```
|
||||
|
||||
After install, scripts live under `~/.hermes/skills/creative/rive-mcp/`. In prose
|
||||
below, `{SKILL_DIR}` means that directory (or this repo path before install).
|
||||
|
||||
Use Rive from Hermes through MCP. There are two real integration paths:
|
||||
|
||||
- **Official Rive desktop MCP** — local HTTP server in the Rive desktop editor
|
||||
at `http://127.0.0.1:9791/mcp`.
|
||||
- **RiveMCP** — third-party standalone stdio MCP (`npx -y rivemcp`) that writes
|
||||
`.riv` / `.rev` files headlessly.
|
||||
|
||||
Do not invent a Rive CLI export path. Official Rive exports runtime `.riv`
|
||||
files from the editor UI; official MCP requires the desktop editor to be open.
|
||||
|
||||
## When to Use
|
||||
|
||||
- The user wants to create or edit Rive artboards, shapes, animations, state
|
||||
machines, View Models, scripts, shaders, or runtime `.riv` files.
|
||||
- The user already has the Rive desktop app open and wants AI to manipulate the
|
||||
current file.
|
||||
- The user wants headless `.riv` / `.rev` generation and accepts RiveMCP's
|
||||
third-party licensing model.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Run the doctor first:
|
||||
|
||||
```bash
|
||||
python {SKILL_DIR}/scripts/rive_doctor.py
|
||||
```
|
||||
|
||||
Official path:
|
||||
|
||||
- Rive desktop editor / Early Access app on macOS or Windows.
|
||||
- Rive app open, with a file and artboard created.
|
||||
- MCP endpoint reachable at `http://127.0.0.1:9791/mcp`.
|
||||
|
||||
RiveMCP path:
|
||||
|
||||
- `node` / `npx`, or a downloaded RiveMCP binary.
|
||||
- RiveMCP has 3 free exports per machine; after that it requires
|
||||
`RIVEMCP_LICENSE_KEY`.
|
||||
|
||||
## How to Run
|
||||
|
||||
### Path A — Official Rive desktop MCP
|
||||
|
||||
Use this when the user is working in the Rive editor and wants live changes.
|
||||
|
||||
```bash
|
||||
hermes mcp add rive --url http://127.0.0.1:9791/mcp
|
||||
```
|
||||
|
||||
When prompted whether the server requires auth, answer **no**. The Rive app
|
||||
must be open before probing. Start a new Hermes session after adding the MCP so
|
||||
the tools load.
|
||||
|
||||
Rive's official docs say to finish a prompt by typing `End Prompt` so the Rive
|
||||
editor applies the AI changes. Treat this as part of the interaction contract.
|
||||
|
||||
### Path B — RiveMCP headless stdio
|
||||
|
||||
Use this when the user wants automation, CI, or file generation without the
|
||||
Rive desktop app.
|
||||
|
||||
```bash
|
||||
hermes mcp add rivemcp --command npx --args -y rivemcp
|
||||
```
|
||||
|
||||
If the user has a paid key:
|
||||
|
||||
```bash
|
||||
export RIVEMCP_LICENSE_KEY=...
|
||||
hermes mcp add rivemcp --command npx --env RIVEMCP_LICENSE_KEY=$RIVEMCP_LICENSE_KEY --args -y rivemcp
|
||||
```
|
||||
|
||||
Start a new Hermes session after adding the MCP.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Goal | Path |
|
||||
| --- | --- |
|
||||
| Live-edit current Rive file | official `rive` HTTP MCP |
|
||||
| Headless `.riv` / `.rev` creation | third-party `rivemcp` stdio MCP |
|
||||
| Inspect official endpoint | `python {SKILL_DIR}/scripts/rive_doctor.py` |
|
||||
| Configure official MCP | `hermes mcp add rive --url http://127.0.0.1:9791/mcp` |
|
||||
| Configure RiveMCP | `hermes mcp add rivemcp --command npx --args -y rivemcp` |
|
||||
|
||||
## Procedure
|
||||
|
||||
1. Ask which path the user wants: **official desktop** or **headless RiveMCP**.
|
||||
2. Run `rive_doctor.py`.
|
||||
3. Configure the selected MCP using the commands above.
|
||||
4. Start a new Hermes session so MCP tools load.
|
||||
5. For official Rive, keep the Rive app open and use `End Prompt` when the
|
||||
editor asks for confirmation.
|
||||
6. For RiveMCP, export `.riv` for runtime or `.rev` for editor handoff.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- **Official Rive MCP is desktop-bound.** It is not a headless server and is
|
||||
currently documented for macOS / Windows desktop editor only.
|
||||
- **RiveMCP is third-party and licensed.** It offers free exports, then needs a
|
||||
license key. Make that explicit before choosing it.
|
||||
- **Do not confuse `.riv` and `.rev`.** `.riv` is runtime output; `.rev` opens
|
||||
in the Rive editor for further manual editing.
|
||||
- **Tool surfaces evolve.** After connecting, inspect the actual MCP tools with
|
||||
`hermes mcp configure rive` or `hermes mcp configure rivemcp`.
|
||||
|
||||
## Verification
|
||||
|
||||
- Official path: `rive_doctor.py` shows port `9791` reachable, and Hermes MCP
|
||||
probe lists Rive tools.
|
||||
- RiveMCP path: Hermes MCP probe lists RiveMCP tools, then export a trivial
|
||||
`.riv` and open it in a Rive runtime/editor.
|
||||
|
||||
See `references/research.md` for source links and verified constraints.
|
||||
60
optional-skills/creative/rive-mcp/references/research.md
Normal file
60
optional-skills/creative/rive-mcp/references/research.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# Rive integration research
|
||||
|
||||
## Official Rive desktop MCP
|
||||
|
||||
Source: <https://rive.mintlify.dev/docs/editor/ai/mcp>
|
||||
|
||||
- Official MCP is currently documented as available in the desktop Editor for
|
||||
Windows and macOS.
|
||||
- The endpoint is local HTTP:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"rive": {
|
||||
"url": "http://127.0.0.1:9791/mcp"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Rive desktop / Early Access app must be open.
|
||||
- User should have a Rive file open and an artboard created.
|
||||
- Rive docs describe a confirmation flow: after prompt processing, type
|
||||
`End Prompt` to allow changes.
|
||||
- Supported feature areas include files/artboards, scene hierarchy edits,
|
||||
shapes/layout/components/assets, animations, state machines, View Models,
|
||||
data binding, Luau scripts, and WGSL shaders.
|
||||
|
||||
## RiveMCP
|
||||
|
||||
Source: <https://github.com/paradoxsyn/rivemcp-releases>
|
||||
|
||||
- Third-party standalone MCP server for programmatically creating/editing Rive
|
||||
animations.
|
||||
- Recommended config:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"rivemcp": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "rivemcp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Exposes 139+ tools across project, shapes, styling, animation, state
|
||||
machines, text, images, bones, nested artboards, events, data binding,
|
||||
advanced layout/scroll/audio/scripting, HLAPI, sprites, import/export, edit.
|
||||
- Can export runtime `.riv` and editor `.rev`.
|
||||
- 3 free exports per machine; license required after that via
|
||||
`RIVEMCP_LICENSE_KEY`.
|
||||
|
||||
## Non-paths
|
||||
|
||||
- Official Rive runtime export is an editor action, not an official CLI export
|
||||
command.
|
||||
- Do not claim headless official Rive support; use RiveMCP if the user needs
|
||||
headless generation.
|
||||
76
optional-skills/creative/rive-mcp/scripts/rive_doctor.py
Normal file
76
optional-skills/creative/rive-mcp/scripts/rive_doctor.py
Normal file
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Preflight checks for the optional Rive MCP skill.
|
||||
|
||||
No installs, no MCP process spawning. Reports whether the official Rive
|
||||
desktop MCP port is reachable and whether the optional headless RiveMCP path
|
||||
has the Node/npx bits needed to launch `npx -y rivemcp`.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import shutil
|
||||
import socket
|
||||
|
||||
|
||||
RIVE_HOST = "127.0.0.1"
|
||||
RIVE_PORT = 9791
|
||||
|
||||
|
||||
def _port_open(host: str, port: int, timeout: float = 0.35) -> bool:
|
||||
try:
|
||||
with socket.create_connection((host, port), timeout=timeout):
|
||||
return True
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
|
||||
def check(*, which=shutil.which, port_open=_port_open) -> dict:
|
||||
node, npx, rivemcp = which("node"), which("npx"), which("rivemcp")
|
||||
return {
|
||||
"official_rive": {
|
||||
"url": f"http://{RIVE_HOST}:{RIVE_PORT}/mcp",
|
||||
"reachable": port_open(RIVE_HOST, RIVE_PORT),
|
||||
},
|
||||
"rivemcp": {
|
||||
"node": bool(node),
|
||||
"npx": bool(npx),
|
||||
"global_binary": bool(rivemcp),
|
||||
"launch": "npx -y rivemcp",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _summary(s: dict) -> str:
|
||||
official, headless = s["official_rive"], s["rivemcp"]
|
||||
lines = [
|
||||
(
|
||||
f"✓ official Rive MCP: {official['url']} reachable"
|
||||
if official["reachable"]
|
||||
else f"✗ official Rive MCP: {official['url']} not reachable "
|
||||
"(open the Rive desktop editor / Early Access app)"
|
||||
)
|
||||
]
|
||||
if headless["npx"]:
|
||||
lines.append("✓ RiveMCP launch path: npx -y rivemcp")
|
||||
elif headless["global_binary"]:
|
||||
lines.append("✓ RiveMCP global binary found: rivemcp")
|
||||
else:
|
||||
lines.append("✗ RiveMCP: need npx or a downloaded rivemcp binary")
|
||||
if not headless["node"] and not headless["global_binary"]:
|
||||
lines.append("⚠ node not found; npm/npx RiveMCP install path unavailable")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def main(argv=None) -> int:
|
||||
ap = argparse.ArgumentParser(description="Check Rive MCP integration paths.")
|
||||
ap.add_argument("--json", action="store_true", help="Emit JSON")
|
||||
args = ap.parse_args(argv)
|
||||
status = check()
|
||||
print(json.dumps(status, indent=2) if args.json else _summary(status))
|
||||
return 0 if status["official_rive"]["reachable"] or status["rivemcp"]["npx"] else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
60
tests/skills/test_rive_mcp_optional_skill.py
Normal file
60
tests/skills/test_rive_mcp_optional_skill.py
Normal file
@@ -0,0 +1,60 @@
|
||||
"""Tests for the optional Rive MCP skill."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
SKILL_DIR = ROOT / "optional-skills" / "creative" / "rive-mcp"
|
||||
|
||||
|
||||
def _load(path: Path, name: str):
|
||||
spec = importlib.util.spec_from_file_location(name, path)
|
||||
assert spec and spec.loader
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(mod)
|
||||
return mod
|
||||
|
||||
|
||||
def _description(skill_dir: Path) -> str:
|
||||
text = (skill_dir / "SKILL.md").read_text()
|
||||
match = re.search(r'^description:\s*"?([^"\n]+)"?\s*$', text, re.MULTILINE)
|
||||
assert match
|
||||
return match.group(1)
|
||||
|
||||
|
||||
class TestRiveMcpSkillShape:
|
||||
def test_skill_lives_under_optional_skills(self):
|
||||
assert (SKILL_DIR / "SKILL.md").is_file()
|
||||
assert not (ROOT / "skills" / "creative" / "rive-mcp").exists()
|
||||
|
||||
def test_description_is_catalog_sized(self):
|
||||
desc = _description(SKILL_DIR)
|
||||
assert len(desc) <= 60, desc
|
||||
assert desc.endswith(".")
|
||||
|
||||
def test_optional_skill_source_fetches_skill(self):
|
||||
from tools.skills_hub import OptionalSkillSource
|
||||
|
||||
source = OptionalSkillSource()
|
||||
assert source.fetch("official/creative/rive-mcp").name == "rive-mcp"
|
||||
|
||||
|
||||
class TestRiveDoctor:
|
||||
def test_official_port_reachable(self):
|
||||
doctor = _load(SKILL_DIR / "scripts" / "rive_doctor.py", "rive_doctor")
|
||||
status = doctor.check(
|
||||
which={"node": "/bin/node", "npx": "/bin/npx"}.get,
|
||||
port_open=lambda host, port: (host, port) == ("127.0.0.1", 9791),
|
||||
)
|
||||
assert status["official_rive"]["reachable"] is True
|
||||
assert status["rivemcp"]["npx"] is True
|
||||
|
||||
def test_no_paths_available(self):
|
||||
doctor = _load(SKILL_DIR / "scripts" / "rive_doctor.py", "rive_doctor_none")
|
||||
status = doctor.check(which=lambda _name: None, port_open=lambda *_a: False)
|
||||
assert status["official_rive"]["reachable"] is False
|
||||
assert status["rivemcp"]["npx"] is False
|
||||
Reference in New Issue
Block a user