Files
wordvibe-hermes/__init__.py
T
admin ab1cfc88da feat: initial wordvibe-hermes plugin
Hermes Agent plugin that drives WordPress sites running the WordVibe plugin over the site MCP server (POST /wp-json/wp-ide/v1/mcp, Bearer key from Settings -> MCP).

8 lean tools (sites, site_info, tools, call, read_file, write_file, edit_file, search) + /wordvibe slash command + bundled skill. The site exposes ~100 tools; the rest are reachable via wordvibe_call to keep the per-turn schema small.

Verified against a mock of the plugin JSON-RPC server: 15/15 scenarios (initialize, tools/list, tools/call, blocked tools, unknown tool, 403 when MCP off, connection refused, plain-permalink ?rest_route= fallback).
2026-09-22 01:43:11 +03:00

82 lines
3.0 KiB
Python

"""wordvibe-hermes — registration.
Connects Hermes to WordPress sites running the WordVibe plugin (which ships an
MCP server at /wp-json/wp-ide/v1/mcp) and exposes a lean set of tools for
driving those sites from any Hermes surface — CLI, Telegram, cron, subagents.
"""
import logging
from . import schemas, tools
logger = logging.getLogger(__name__)
TOOLSET = "wordvibe"
def _split_commands(raw: str) -> list:
return [c for c in (raw or "").split() if c]
def _handle_command(ctx, raw_args: str) -> str:
"""`/wordvibe [sites|info|tools|call <tool> <json>]` — quick access without a model turn."""
parts = _split_commands(raw_args)
sub = (parts[0].lower() if parts else "sites")
if sub in ("sites", "list"):
return tools.sites({"probe": True}, ctx=ctx)
if sub in ("info", "status"):
return tools.site_info({"site": parts[1] if len(parts) > 1 else ""}, ctx=ctx)
if sub in ("tools", "listtools"):
return tools.tools({"site": parts[1] if len(parts) > 1 else ""}, ctx=ctx)
if sub in ("call", "run") and len(parts) >= 2:
import json as _json
tool_name = parts[1]
raw_json = raw_args.split(tool_name, 1)[1].strip() if tool_name in raw_args else ""
try:
arguments = _json.loads(raw_json) if raw_json else {}
except Exception as exc: # noqa: BLE001
return tools._err(f"arguments must be JSON: {exc}")
return tools.call({"tool": tool_name, "arguments": arguments}, ctx=ctx)
return tools._ok(
{
"usage": "/wordvibe sites | info [site] | tools [site] | call <tool> '{json}'",
"example": "/wordvibe call post_list '{\"post_type\":\"post\",\"limit\":5}'",
}
)
def _on_post_tool_call(tool_name, args, result, task_id, **kwargs):
"""Log wordvibe_* traffic at debug level (visibility without noise)."""
if str(tool_name).startswith("wordvibe_"):
logger.debug("wordvibe tool %s (session %s)", tool_name, task_id)
def register(ctx):
"""Wire the wordvibe tools into the Hermes tool registry."""
registry = [
("wordvibe_sites", schemas.SITES, tools.sites),
("wordvibe_site_info", schemas.SITE_INFO, tools.site_info),
("wordvibe_tools", schemas.TOOLS, tools.tools),
("wordvibe_call", schemas.CALL, tools.call),
("wordvibe_read_file", schemas.READ_FILE, tools.read_file),
("wordvibe_write_file", schemas.WRITE_FILE, tools.write_file),
("wordvibe_edit_file", schemas.EDIT_FILE, tools.edit_file),
("wordvibe_search", schemas.SEARCH, tools.search),
]
for name, schema, handler in registry:
ctx.register_tool(name=name, toolset=TOOLSET, schema=schema, handler=handler)
ctx.register_command(
"wordvibe",
lambda raw: _handle_command(ctx, raw),
description="WordVibe sites: `sites`, `info [site]`, `tools [site]`, `call <tool> '{json}'`",
)
ctx.register_hook("post_tool_call", _on_post_tool_call)
logger.info("wordvibe-hermes registered %d tools", len(registry))