MCP server by brianmok888
hermes-opencode-mcp
MCP-native execution-target executor for OpenCode CLI workflows.
Goal
Provide a standard MCP interface for target-based task execution while keeping the execution backend simple and portable:
MCP client -> hermes-opencode-mcp -> OpenCode CLI -> result/artifacts
This repo intentionally uses the OpenCode CLI as the backend executor, not a local OpenCode runtime API.
Routing boundary
Telegram forum-topic routing belongs in Hermes, not in this MCP server.
- Hermes should map
chat_id+topic_idto an executiontarget_id - Hermes should decide whether a topic message is normal chat or task submission
hermes-opencode-mcpshould only receive explicit execution requests for a chosentarget_id
See TOPIC_ROUTING.md for the recommended boundary, examples, and operator guidance.
Production-oriented capabilities
- persistent task and target state on disk
- single-target concurrency guard (
target busyrejection) - synchronous and async execution flows
- execution identity prefix enforcement:
oc@vm_name@ip_address: - OpenCode CLI adapter with cancellation, timeout, and execution handles
- structured JSON logging with retention-safe metadata-only task events
- startup reconciliation for interrupted queued/running tasks after restart
- stdio MCP server plus Python client helper
Current MCP tools
healthlist_targetsget_targetcreate_tasksubmit_taskget_taskcancel_taskget_artifactsrun_task
Required environment variables
HERMES_MCP_TARGETS_FILEHERMES_MCP_EXECUTOR(mockoropencode)HERMES_MCP_OPENCODE_BINHERMES_MCP_REPO_ROOTHERMES_MCP_STATE_DIR
Optional:
HERMES_MCP_SERVER_NAME(default:hermes-opencode-mcp)HERMES_MCP_SERVER_VERSION(default:0.1.0)HERMES_MCP_LOG_LEVEL(default:INFO)HERMES_MCP_LOG_JSON(1by default; set0for plain text logs)
Example execution target file
See templates/targets.example.json.
Important: do not encode Telegram topic IDs into target definitions or target names. Keep targets platform-agnostic and let Hermes own topic-to-target mapping.
Example Hermes topic routing config
See these repo-local Hermes-side routing examples:
This config is intentionally Hermes-owned, not MCP server config.
For a quick visual overview, see docs/TOPIC_ROUTING_FLOW.md.
Exported Hermes skill copy
This repo also includes a sanitized export copy of the Hermes skill:
If you want an agent to install or recreate that skill in Hermes, use:
That install note includes a reusable prompt that points directly at the exported skill doc.
Running
export PYTHONPATH=/path/to/hermes-opencode-mcp/src
export HERMES_MCP_TARGETS_FILE=/path/to/targets.json
export HERMES_MCP_EXECUTOR=opencode
export HERMES_MCP_OPENCODE_BIN=opencode
export HERMES_MCP_REPO_ROOT=/path/to/hermes-opencode-mcp
export HERMES_MCP_STATE_DIR=/var/lib/hermes-opencode-mcp
python -m hermes_opencode_mcp
Or via the console script:
hermes-opencode-mcp
Systemd packaging
Example deployment assets are included:
- systemd unit:
deploy/systemd/hermes-opencode-mcp.service - environment file template:
deploy/env/hermes-opencode-mcp.env.example
Typical install flow:
sudo install -d /etc/hermes-opencode-mcp /var/lib/hermes-opencode-mcp /opt/hermes-opencode-mcp
sudo cp deploy/env/hermes-opencode-mcp.env.example /etc/hermes-opencode-mcp/hermes-opencode-mcp.env
sudo cp deploy/systemd/hermes-opencode-mcp.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now hermes-opencode-mcp
sudo journalctl -u hermes-opencode-mcp -f
The systemd unit is configured for:
EnvironmentFile=-driven configuration- automatic restart on failure
- journald output for JSON log shipping/retention
- strict filesystem protections with write access limited to
HERMES_MCP_STATE_DIR
Python client example
from hermes_opencode_mcp.client import MCPClient, MCPClientConfig
with MCPClient(
MCPClientConfig(
command="python3",
args=["-m", "hermes_opencode_mcp"],
env={
"PYTHONPATH": "/path/to/hermes-opencode-mcp/src",
"HERMES_MCP_TARGETS_FILE": "/path/to/targets.json",
"HERMES_MCP_EXECUTOR": "opencode",
"HERMES_MCP_OPENCODE_BIN": "opencode",
"HERMES_MCP_REPO_ROOT": "/path/to/hermes-opencode-mcp",
"HERMES_MCP_STATE_DIR": "/var/lib/hermes-opencode-mcp",
},
cwd="/path/to/hermes-opencode-mcp",
)
) as client:
print(client.health())
print(client.list_targets())
result = client.submit_and_wait(
target_id="coding-node-1",
text="Fix the failing test",
directory="/repo/path",
)
print(result)
Hermes -> MCP task payload example
Once Hermes has already mapped a Telegram topic to a target, the MCP-facing request should be explicit and platform-agnostic.
Example logical payload:
{
"target_id": "coding-node-1",
"directory": "/path/to/coding-repo",
"text": "fix the failing import and run tests"
}
Equivalent Python client call:
result = client.submit_and_wait(
target_id="coding-node-1",
directory="/path/to/coding-repo",
text="fix the failing import and run tests",
)
The important design rule is that chat_id and topic_id should already have been resolved by Hermes before this call is made.
Operational notes
- State is persisted under
HERMES_MCP_STATE_DIRin JSON files. - On startup, any persisted
queuedorrunningtask is reconciled to a failed terminal state withdispatch_status=interrupted_on_startupbecause the prior OpenCode process cannot be resumed safely. - Only one queued/running task is allowed per target at a time.
healthnow reportsstate_dir, target count, current running task count, andstartup_recovered_tasks.mockexecutor is for local verification only; production use should setHERMES_MCP_EXECUTOR=opencode.- Logs are structured JSON by default and intentionally avoid storing full task prompts or full execution output; only metadata such as task IDs, target IDs, lengths, and execution handles are emitted.
- If Hermes is the caller, keep platform-specific routing concerns outside this repo; topic/thread mapping should be handled before invoking MCP tools.
Live E2E verification
Use the included live verification script against a real OpenCode-ready target:
python scripts/e2e_live.py \
--target-id coding-node-1 \
--directory /path/to/repo \
--targets-file /etc/hermes-opencode-mcp/targets.json
The script starts the MCP server locally, submits a real submit_and_wait task through the client, and verifies the terminal task contract, including execution prefix and execution handle requirements.
CI
A GitHub Actions workflow is included at .github/workflows/ci.yml.
It runs:
unit-tests: always runspytest -qusing the mock executor pathlive-e2e: runs only when explicit repo/org configuration is present
Conditional live E2E configuration
Set these GitHub Actions Variables:
HERMES_MCP_E2E_ENABLED=1HERMES_MCP_E2E_TARGET_ID=<target_id>HERMES_MCP_E2E_DIRECTORY=<repo_path_on_target>HERMES_MCP_OPENCODE_BIN=opencode(optional ifopencodeis already onPATH)HERMES_MCP_E2E_TEXT=<verification prompt>(optional)
Set this GitHub Actions Secret:
HERMES_MCP_TARGETS_JSON_B64: base64-encoded contents of the targets JSON file
Example secret preparation:
base64 -w0 /path/to/targets.json
Once those are present, the workflow automatically:
- installs the package in editable mode with test deps
- runs mock tests
- decodes the targets config into a temporary file
- invokes
scripts/e2e_live.pyagainst the configured real target
If the variables/secrets are absent, the live E2E job is skipped cleanly and CI still validates the mock path.