mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-20 15:33:54 +00:00
feat(mcp): add Blender to the MCP catalog with a curated 4-tool default
Adds optional-mcps/blender (ahujasid/blender-mcp, stdio via uvx). The server advertises 22 tools; 18 front optional asset services with no upstream trim mechanism, so tools.default_enabled pins the install to the core surface (scene/object info, viewport screenshot, code exec) and the rest stay opt-in through 'hermes mcp configure blender'. Manifests can now declare transport.env (static, non-secret subprocess env vars), parsed/validated in _parse_manifest and written by _build_server_config — used here to ship DISABLE_TELEMETRY=true per the no-telemetry-without-opt-in policy. Runtime already honored per-server env; manifests just couldn't declare it.
This commit is contained in:
parent
7af59f474d
commit
9be941dac1
3 changed files with 123 additions and 0 deletions
|
|
@ -77,6 +77,10 @@ class TransportSpec:
|
|||
args: List[str] = field(default_factory=list)
|
||||
url: Optional[str] = None
|
||||
version: Optional[str] = None # informational, pinned
|
||||
# Static environment variables for the stdio subprocess (e.g. telemetry
|
||||
# opt-outs, mode flags). NOT for secrets — credentials go through
|
||||
# auth.env so they are prompted for and land in ~/.hermes/.env.
|
||||
env: Dict[str, str] = field(default_factory=dict)
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
@ -184,12 +188,20 @@ def _parse_manifest(path: Path) -> CatalogEntry:
|
|||
args = transport_raw.get("args") or []
|
||||
if not isinstance(args, list):
|
||||
raise CatalogError(f"{path}: transport.args must be a list")
|
||||
env_raw = transport_raw.get("env") or {}
|
||||
if not isinstance(env_raw, dict) or not all(
|
||||
isinstance(k, str) and isinstance(v, str) for k, v in env_raw.items()
|
||||
):
|
||||
raise CatalogError(
|
||||
f"{path}: transport.env must be a mapping of string to string"
|
||||
)
|
||||
transport = TransportSpec(
|
||||
type=t_type,
|
||||
command=transport_raw.get("command"),
|
||||
args=[str(a) for a in args],
|
||||
url=transport_raw.get("url"),
|
||||
version=transport_raw.get("version"),
|
||||
env=dict(env_raw),
|
||||
)
|
||||
if t_type == "stdio" and not transport.command:
|
||||
raise CatalogError(f"{path}: stdio transport requires 'command'")
|
||||
|
|
@ -468,6 +480,8 @@ def _build_server_config(
|
|||
cfg["command"] = _expand_install_dir(t.command or "", install_dir)
|
||||
if t.args:
|
||||
cfg["args"] = [_expand_install_dir(a, install_dir) for a in t.args]
|
||||
if t.env:
|
||||
cfg["env"] = dict(t.env)
|
||||
elif t.type == "http":
|
||||
cfg["url"] = t.url
|
||||
if entry.auth.type == "oauth":
|
||||
|
|
|
|||
83
optional-mcps/blender/manifest.yaml
Normal file
83
optional-mcps/blender/manifest.yaml
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
# Nous-approved MCP catalog entry.
|
||||
# Presence in this directory = approval. Merged via PR review.
|
||||
manifest_version: 1
|
||||
|
||||
name: blender
|
||||
description: Drive a live Blender session — modeling, scenes, and renders.
|
||||
source: https://github.com/ahujasid/blender-mcp
|
||||
|
||||
# ahujasid/blender-mcp (MIT, PyPI: blender-mcp) is the de-facto standard
|
||||
# Blender MCP. It is a bridge with two halves:
|
||||
# 1. This stdio server (uvx blender-mcp), which Hermes launches.
|
||||
# 2. An addon (addon.py) running inside Blender that opens a local command
|
||||
# socket on 127.0.0.1:9876. The stdio server relays to it.
|
||||
# There is nothing to git-clone on the Hermes side — uvx resolves the package —
|
||||
# so no install block. The in-Blender addon setup is covered in post_install.
|
||||
#
|
||||
# Why not Blender's official MCP (blender.org/lab): it requires Blender 5.1+
|
||||
# and targets scene analysis/documentation rather than asset creation. This
|
||||
# server works across Blender 3.x/4.x and exposes full modeling control.
|
||||
transport:
|
||||
type: stdio
|
||||
command: "uvx"
|
||||
args:
|
||||
- "blender-mcp"
|
||||
env:
|
||||
# Upstream ships anonymous telemetry, on by default. Hermes policy is
|
||||
# no outbound telemetry without explicit opt-in, so the catalog entry
|
||||
# disables it. Remove this line only if you deliberately want it on.
|
||||
DISABLE_TELEMETRY: "true"
|
||||
|
||||
# The Blender addon binds 127.0.0.1 only and has no auth of its own; the
|
||||
# stdio server needs no credentials. Optional integrations (Sketchfab,
|
||||
# Hyper3D Rodin, Hunyuan3D) take API keys entered in the addon's N-panel
|
||||
# inside Blender, not via Hermes env.
|
||||
auth:
|
||||
type: none
|
||||
|
||||
# Tool selection at install time:
|
||||
# The server advertises 22 tools. 18 of them front optional third-party asset
|
||||
# services (PolyHaven, Sketchfab, Hyper3D Rodin, Hunyuan3D) that are dead
|
||||
# weight unless the matching integration is enabled in the addon panel — and
|
||||
# upstream has no server-side trim mechanism, so without a filter every one
|
||||
# of them costs schema tokens on every API call. Default to the core surface:
|
||||
# scene/object inspection, viewport screenshots, and code execution, which is
|
||||
# the complete modeling/render capability. Users who enable an asset service
|
||||
# in the addon can opt into its tools with `hermes mcp configure blender`.
|
||||
tools:
|
||||
default_enabled:
|
||||
- get_scene_info
|
||||
- get_object_info
|
||||
- get_viewport_screenshot
|
||||
- execute_blender_code
|
||||
|
||||
post_install: |
|
||||
This entry launches the bridge server, but the bridge talks to an addon
|
||||
INSIDE a running Blender (3.0+). One-time setup:
|
||||
|
||||
1. Download addon.py from https://github.com/ahujasid/blender-mcp
|
||||
(raw file: https://raw.githubusercontent.com/ahujasid/blender-mcp/main/addon.py)
|
||||
2. Blender > Edit > Preferences > Add-ons > Install... > select addon.py,
|
||||
then enable "Interface: Blender MCP".
|
||||
3. In the 3D viewport press N, open the "BlenderMCP" tab, click
|
||||
"Connect to Claude" (starts the local socket on 127.0.0.1:9876).
|
||||
|
||||
Blender must be RUNNING with the addon connected before the tools work —
|
||||
start Blender first, then your Hermes session. The addon refuses to start
|
||||
under `blender -b` (background mode): its command queue runs on UI timers.
|
||||
On a machine without a display, run Blender under a virtual one:
|
||||
xvfb-run blender (or Xvfb :99 & DISPLAY=:99 blender)
|
||||
GPU rendering (Cycles/OptiX) works fine under Xvfb.
|
||||
|
||||
SECURITY: execute_blender_code runs arbitrary Python inside Blender with no
|
||||
sandbox — same trust level as the terminal tool. Upstream telemetry is
|
||||
disabled via DISABLE_TELEMETRY in this entry's env block.
|
||||
|
||||
The 18 asset-service tools (PolyHaven/Sketchfab/Hyper3D/Hunyuan3D) are off
|
||||
by default. To use one: enable the service in the addon's BlenderMCP panel
|
||||
(API key there if required), then `hermes mcp configure blender` and tick
|
||||
its tools. PolyHaven is free and keyless; the others need accounts.
|
||||
|
||||
If Hermes and Blender run on different machines, note that file paths in
|
||||
execute_blender_code (texture loads, exports) resolve on the BLENDER host's
|
||||
filesystem, not where Hermes runs.
|
||||
|
|
@ -197,6 +197,32 @@ class TestManifestParsing:
|
|||
assert get_entry("official/demo") is not None
|
||||
assert get_entry("missing") is None
|
||||
|
||||
def test_transport_env_parsed_and_written_to_server_config(self, catalog_dir):
|
||||
body = _basic_manifest()
|
||||
body["transport"]["env"] = {"DISABLE_TELEMETRY": "true"}
|
||||
_write_manifest(catalog_dir, "demo", body)
|
||||
from hermes_cli.mcp_catalog import _build_server_config
|
||||
|
||||
e = _entry("demo")
|
||||
assert e.transport.env == {"DISABLE_TELEMETRY": "true"}
|
||||
cfg = _build_server_config(e, None)
|
||||
assert cfg["env"] == {"DISABLE_TELEMETRY": "true"}
|
||||
|
||||
def test_transport_env_absent_leaves_config_without_env_key(self, catalog_dir):
|
||||
_write_manifest(catalog_dir, "demo", _basic_manifest())
|
||||
from hermes_cli.mcp_catalog import _build_server_config
|
||||
|
||||
cfg = _build_server_config(_entry("demo"), None)
|
||||
assert "env" not in cfg
|
||||
|
||||
def test_transport_env_bad_shape_rejected(self, catalog_dir):
|
||||
body = _basic_manifest()
|
||||
body["transport"]["env"] = ["DISABLE_TELEMETRY=true"] # list, not mapping
|
||||
_write_manifest(catalog_dir, "demo", body)
|
||||
from hermes_cli.mcp_catalog import list_catalog
|
||||
|
||||
assert list_catalog() == []
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Install flow
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue