opencode-hermes-mcp 0.4.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- opencode_hermes_mcp-0.4.1/LICENSE +21 -0
- opencode_hermes_mcp-0.4.1/PKG-INFO +289 -0
- opencode_hermes_mcp-0.4.1/README.md +261 -0
- opencode_hermes_mcp-0.4.1/opencode_hermes_mcp/__init__.py +6 -0
- opencode_hermes_mcp-0.4.1/opencode_hermes_mcp/client.py +288 -0
- opencode_hermes_mcp-0.4.1/opencode_hermes_mcp/controller.py +896 -0
- opencode_hermes_mcp-0.4.1/opencode_hermes_mcp/installer.py +1012 -0
- opencode_hermes_mcp-0.4.1/opencode_hermes_mcp/models.py +234 -0
- opencode_hermes_mcp-0.4.1/opencode_hermes_mcp/pin.txt +1 -0
- opencode_hermes_mcp-0.4.1/opencode_hermes_mcp/server.py +259 -0
- opencode_hermes_mcp-0.4.1/opencode_hermes_mcp/smoke_client.py +87 -0
- opencode_hermes_mcp-0.4.1/opencode_hermes_mcp.egg-info/PKG-INFO +289 -0
- opencode_hermes_mcp-0.4.1/opencode_hermes_mcp.egg-info/SOURCES.txt +17 -0
- opencode_hermes_mcp-0.4.1/opencode_hermes_mcp.egg-info/dependency_links.txt +1 -0
- opencode_hermes_mcp-0.4.1/opencode_hermes_mcp.egg-info/entry_points.txt +2 -0
- opencode_hermes_mcp-0.4.1/opencode_hermes_mcp.egg-info/requires.txt +7 -0
- opencode_hermes_mcp-0.4.1/opencode_hermes_mcp.egg-info/top_level.txt +1 -0
- opencode_hermes_mcp-0.4.1/pyproject.toml +49 -0
- opencode_hermes_mcp-0.4.1/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Arthur Hottier
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: opencode-hermes-mcp
|
|
3
|
+
Version: 0.4.1
|
|
4
|
+
Summary: Deterministic MCP controller between Hermes (supervisor LLM) and the permanent OpenCode server. OpenCode pin defined in opencode_hermes_mcp/pin.txt (currently 1.18.21).
|
|
5
|
+
Author-email: Arthur Hottier <arthur.ho2tier@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ArthurHtr/opencode-hermes-mcp
|
|
8
|
+
Project-URL: Repository, https://github.com/ArthurHtr/opencode-hermes-mcp
|
|
9
|
+
Project-URL: Issue Tracker, https://github.com/ArthurHtr/opencode-hermes-mcp/issues
|
|
10
|
+
Keywords: mcp,model-context-protocol,opencode,hermes,controller,llm
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: mcp==1.12.4
|
|
22
|
+
Requires-Dist: rich>=13
|
|
23
|
+
Requires-Dist: pyyaml>=6
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
26
|
+
Requires-Dist: ruff>=0.5; extra == "dev"
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# opencode-hermes-mcp
|
|
30
|
+
|
|
31
|
+
[](LICENSE)
|
|
32
|
+
[](#prerequisites)
|
|
33
|
+
[-brightgreen.svg)](#version-pin-opencode-11821)
|
|
34
|
+
|
|
35
|
+
Deterministic MCP controller between **Hermes** (supervisor LLM) and the
|
|
36
|
+
permanent **OpenCode** server. The controller is a state machine — no LLM —
|
|
37
|
+
that blocks on OpenCode turns and surfaces questions/permissions to Hermes so
|
|
38
|
+
the supervisor LLM can decide and resume the *same* turn.
|
|
39
|
+
|
|
40
|
+
## Architecture
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
Hermes (LLM) --MCP stdio--> opencode_hermes_mcp.server (FastMCP, 6 tools) --HTTP + SSE--> OpenCode server :4096
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
- **Layer 1 — Hermes**: the supervisor LLM. It delegates a coding task with
|
|
47
|
+
`opencode_run` and decides when the controller reports
|
|
48
|
+
`needs_agent_input` (question / permission).
|
|
49
|
+
- **Layer 2 — this controller** (`opencode_hermes_mcp/`: `server.py` +
|
|
50
|
+
`controller.py` + `client.py` + `models.py`): a NO-LLM process spawned by
|
|
51
|
+
Hermes over MCP stdio. It submits the task, watches SSE + REST, blocks until
|
|
52
|
+
the turn completes / errors / needs input, and posts the supervisor's
|
|
53
|
+
decisions back into the same OpenCode turn (the prompt is never resubmitted).
|
|
54
|
+
- **Layer 3 — OpenCode server**: a permanent `opencode serve` process
|
|
55
|
+
(systemd user service `opencode-server`, loopback :4096, HTTP basic auth).
|
|
56
|
+
Its LLM is any supported provider (OpenAI-compatible endpoint, OpenAI, or
|
|
57
|
+
Anthropic), configured in `~/.config/opencode/opencode.json`.
|
|
58
|
+
|
|
59
|
+
Tools exposed to Hermes: `opencode_run`, `opencode_answer`,
|
|
60
|
+
`opencode_permission`, `opencode_abort`, `opencode_inspect` (diagnostic
|
|
61
|
+
only), `opencode_sessions`.
|
|
62
|
+
|
|
63
|
+
## Prerequisites
|
|
64
|
+
|
|
65
|
+
- Hermes installed (`~/.hermes/config.yaml` present)
|
|
66
|
+
- `python3` >= 3.11 (with PyYAML for the Hermes config patch)
|
|
67
|
+
- Network access (OpenCode binary install, `mcp` package, LLM endpoint)
|
|
68
|
+
- `systemd` user sessions (for the `opencode-server` service)
|
|
69
|
+
|
|
70
|
+
## Installation (2 commands)
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
git clone <repo-url> opencode-hermes-mcp && cd opencode-hermes-mcp
|
|
74
|
+
scripts/install.sh
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`scripts/install.sh` is a thin wrapper around the **setup wizard**
|
|
78
|
+
(`opencode_hermes_mcp/installer.py`, Python + `rich`): banner, numbered
|
|
79
|
+
steps, styled prompts, progress, and a summary panel. The wizard bootstraps
|
|
80
|
+
itself — if the repo venv is missing (or lacks `rich` / `pyyaml` /
|
|
81
|
+
`mcp==1.12.4` / the editable package), it creates it and re-execs, so a bare
|
|
82
|
+
`python3` >= 3.11 is the only prerequisite.
|
|
83
|
+
|
|
84
|
+
The install is idempotent — re-running it skips what is already in place.
|
|
85
|
+
It installs the pinned OpenCode binary, the venv (the `opencode_hermes_mcp`
|
|
86
|
+
package with `mcp==1.12.4` pinned), the LLM provider config + secret, the
|
|
87
|
+
server credentials, the two launchers, the systemd user service, and patches
|
|
88
|
+
`~/.hermes/config.yaml` (backup kept as `.bak`). It finishes with a health
|
|
89
|
+
check (bounded `curl --max-time 3`, last error surfaced) +
|
|
90
|
+
`python -m opencode_hermes_mcp.smoke_client` (must print
|
|
91
|
+
`tool surface OK`).
|
|
92
|
+
|
|
93
|
+
### LLM providers
|
|
94
|
+
|
|
95
|
+
The installer is provider-agnostic. Three providers are supported:
|
|
96
|
+
|
|
97
|
+
| Provider | Use | npm package |
|
|
98
|
+
| --- | --- | --- |
|
|
99
|
+
| `openai-compatible` | any OpenAI-compatible endpoint (Unsloth, Ollama, vLLM, llama-server, ...) — default | `@ai-sdk/openai-compatible` |
|
|
100
|
+
| `openai` | official OpenAI API | `@ai-sdk/openai` |
|
|
101
|
+
| `anthropic` | official Anthropic API | `@ai-sdk/anthropic` |
|
|
102
|
+
|
|
103
|
+
Interactive: pick the provider from the menu, then answer the prompts —
|
|
104
|
+
base URL + API key + model for `openai-compatible`, API key + model for
|
|
105
|
+
`openai` / `anthropic` — then the LLM speed (`slow` for a local LLM, which
|
|
106
|
+
adds `timeout:false` / `headerTimeout:false` / `chunkTimeout:120000` to the
|
|
107
|
+
provider options; `fast` is the default) and the model limits (context /
|
|
108
|
+
output, defaults 128000 / 32000).
|
|
109
|
+
|
|
110
|
+
Non-interactive (`--yes`), everything comes from env. Local
|
|
111
|
+
OpenAI-compatible endpoint (Ollama / vLLM / Unsloth / ...):
|
|
112
|
+
|
|
113
|
+
```sh
|
|
114
|
+
OPENCODE_PROVIDER=openai-compatible \
|
|
115
|
+
OPENCODE_LLM_BASE_URL=http://127.0.0.1:11434/v1 \
|
|
116
|
+
OPENCODE_API_KEY=... \
|
|
117
|
+
OPENCODE_LLM_MODEL=qwen3.8-27b \
|
|
118
|
+
OPENCODE_LLM_SPEED=slow \
|
|
119
|
+
scripts/install.sh --yes
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
OpenAI (cloud):
|
|
123
|
+
|
|
124
|
+
```sh
|
|
125
|
+
OPENCODE_PROVIDER=openai OPENCODE_API_KEY=sk-... OPENCODE_LLM_MODEL=gpt-4o \
|
|
126
|
+
scripts/install.sh --yes
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Anthropic (cloud):
|
|
130
|
+
|
|
131
|
+
```sh
|
|
132
|
+
OPENCODE_PROVIDER=anthropic OPENCODE_API_KEY=sk-ant-... \
|
|
133
|
+
OPENCODE_LLM_MODEL=claude-sonnet-4-5 scripts/install.sh --yes
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Flags: `--yes` (non-interactive, uses env `OPENCODE_PROVIDER` /
|
|
137
|
+
`OPENCODE_LLM_BASE_URL` / `OPENCODE_API_KEY` / `OPENCODE_LLM_MODEL` /
|
|
138
|
+
`OPENCODE_LLM_SPEED` / `OPENCODE_CONTEXT_LIMIT` / `OPENCODE_OUTPUT_LIMIT`),
|
|
139
|
+
`--port N` (default 4096), `--skip-binary`, `--force-config`, `--dry-run`,
|
|
140
|
+
`--skip-verify` (skip the final health + smoke verification — useful for
|
|
141
|
+
sandbox/CI).
|
|
142
|
+
|
|
143
|
+
`UNSLOTH_API_KEY` is still accepted as a deprecated fallback for
|
|
144
|
+
`OPENCODE_API_KEY` (existing scripts keep working).
|
|
145
|
+
|
|
146
|
+
**A new Hermes session is required** after installation to load the MCP
|
|
147
|
+
server.
|
|
148
|
+
|
|
149
|
+
## Hermes integration (manual)
|
|
150
|
+
|
|
151
|
+
The installer patches `~/.hermes/config.yaml` for you, but it does **not**
|
|
152
|
+
install a Hermes skill on purpose (Hermes's skill layout may change). The
|
|
153
|
+
package ships the full manual instead:
|
|
154
|
+
|
|
155
|
+
- [`docs/hermes-integration.md`](docs/hermes-integration.md) — what the MCP
|
|
156
|
+
is for, the exact config entry written, manual integration (by hand), the
|
|
157
|
+
six tools, troubleshooting, uninstall.
|
|
158
|
+
- [`docs/skill.example.md`](docs/skill.example.md) — a ready-to-copy Hermes
|
|
159
|
+
skill (the delegation protocol) to drop into `~/.hermes/skills/` and adapt.
|
|
160
|
+
|
|
161
|
+
## Usage
|
|
162
|
+
|
|
163
|
+
Hermes delegates work through the MCP tools — no manual CLI needed:
|
|
164
|
+
|
|
165
|
+
- `opencode_run(directory, task, agent)` — submit a task; blocks until the
|
|
166
|
+
turn completes, errors, or needs input. `agent` is required for a new
|
|
167
|
+
session (a primary agent of the project, e.g. `build`, `plan`, or a
|
|
168
|
+
project-specific agent).
|
|
169
|
+
- When a tool returns `state=needs_agent_input`, Hermes decides:
|
|
170
|
+
`opencode_answer` (pick exact option labels) or `opencode_permission`
|
|
171
|
+
(`once` / `always` / `reject`) — both resume the same turn.
|
|
172
|
+
- `opencode_abort` stops a stuck run; `opencode_sessions` lists sessions for
|
|
173
|
+
a directory; `opencode_inspect` is for exceptional diagnostics only (never
|
|
174
|
+
poll a running task).
|
|
175
|
+
|
|
176
|
+
The Hermes-side wiring (written by `scripts/install.sh` into
|
|
177
|
+
`~/.hermes/config.yaml`):
|
|
178
|
+
|
|
179
|
+
```yaml
|
|
180
|
+
mcp_servers:
|
|
181
|
+
opencode:
|
|
182
|
+
command: ~/.local/bin/opencode-mcp-launch.sh
|
|
183
|
+
enabled: true
|
|
184
|
+
timeout: 14400
|
|
185
|
+
connect_timeout: 30
|
|
186
|
+
supports_parallel_tool_calls: false
|
|
187
|
+
timeouts:
|
|
188
|
+
tools:
|
|
189
|
+
sequential_call: 14400
|
|
190
|
+
concurrent_batch: 14400
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
The launcher reads the OpenCode server credentials from
|
|
194
|
+
`~/.config/hermes/opencode-server.json` and execs
|
|
195
|
+
`python -m opencode_hermes_mcp.server` in the repo venv — `config.yaml` stays
|
|
196
|
+
secret-free.
|
|
197
|
+
|
|
198
|
+
## TUI attach helpers (watch OpenCode live)
|
|
199
|
+
|
|
200
|
+
`install.sh` also drops two helpers into `~/.local/bin/` (sources:
|
|
201
|
+
`scripts/helpers/`):
|
|
202
|
+
|
|
203
|
+
```sh
|
|
204
|
+
ocattach <repo-abs> [ses_...] # open the OpenCode TUI on a repo / session
|
|
205
|
+
oc-current # attach to the session Hermes is supervising NOW
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
- `ocattach` opens the OpenCode TUI (`opencode attach`) against the permanent
|
|
209
|
+
server `:4096` — no tmux needed. Without a session id it opens the latest
|
|
210
|
+
session / lets you pick one.
|
|
211
|
+
- `oc-current` reads the newest `~/.local/state/opencode-hermes-mcp/turn_*.json`
|
|
212
|
+
(the controller's in-flight turn state) and attaches to that session — use
|
|
213
|
+
it while Hermes is driving OpenCode, to watch the reasoning live.
|
|
214
|
+
|
|
215
|
+
Both read the server credentials from `~/.config/hermes/opencode-server.json`
|
|
216
|
+
(same source as the controller launcher). **Do not press Esc/Ctrl+C in the TUI
|
|
217
|
+
while a turn is active** — that aborts the in-flight turn on the OpenCode side.
|
|
218
|
+
|
|
219
|
+
## Upgrade / uninstall
|
|
220
|
+
|
|
221
|
+
```sh
|
|
222
|
+
scripts/upgrade.sh # controller only: git pull + venv deps + restart + smoke
|
|
223
|
+
scripts/upgrade.sh --binary # install the PINNED OpenCode binary (idempotent) — see "Version pin" below
|
|
224
|
+
scripts/uninstall.sh # service, launchers, venv, hermes entry, credentials
|
|
225
|
+
scripts/uninstall.sh --purge # + OpenCode provider config + API key secret
|
|
226
|
+
scripts/uninstall.sh --purge-binary # + the OpenCode binary
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
`uninstall.sh` never touches the git clone, the OpenCode provider config, the
|
|
230
|
+
API key secret, or the binary (unless the purge flags say so).
|
|
231
|
+
|
|
232
|
+
## Version pin: OpenCode 1.18.21
|
|
233
|
+
|
|
234
|
+
The controller is **validated against OpenCode `1.18.21` only** (its endpoint
|
|
235
|
+
contract was verified against that binary's live `/doc`, not the web docs).
|
|
236
|
+
The pin is a **single source of truth** in `opencode_hermes_mcp/pin.txt`
|
|
237
|
+
(one line, no `v` prefix): `installer.py` and `scripts/upgrade.sh` both read
|
|
238
|
+
it, falling back to the built-in constant when the file is missing or empty
|
|
239
|
+
(e.g. pip installs where the file is not shipped next to the code). `install.sh` pins
|
|
240
|
+
the binary to that version; `upgrade.sh` never upgrades the binary by
|
|
241
|
+
default.
|
|
242
|
+
|
|
243
|
+
`scripts/upgrade.sh --binary` (no version) installs the pinned version and is
|
|
244
|
+
idempotent (no-op if the binary is already at the pin). `--binary latest` is
|
|
245
|
+
the explicit opt-in to the bleeding edge; `--binary X.Y.Z` installs the
|
|
246
|
+
requested version. For anything other than the pin, the script warns you and
|
|
247
|
+
you MUST re-validate the controller before trusting it:
|
|
248
|
+
|
|
249
|
+
```sh
|
|
250
|
+
.venv/bin/python tests/run_tests.py
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
(all checks must pass; the suite drives the controller over MCP stdio against
|
|
254
|
+
the live server). If it fails, pin back: `scripts/upgrade.sh --binary`.
|
|
255
|
+
|
|
256
|
+
## Timeouts
|
|
257
|
+
|
|
258
|
+
Three independent timeouts bound the pipeline: the **controller** run timeout
|
|
259
|
+
(`DEFAULT_RUN_TIMEOUT` = 3600 s — a single `opencode_run`/`opencode_answer`/
|
|
260
|
+
`opencode_permission` call gives up after an hour), the **MCP** server
|
|
261
|
+
timeout in `~/.hermes/config.yaml` (`mcp_servers.opencode.timeout` = 14400 s,
|
|
262
|
+
`connect_timeout` = 30 s), and the **Hermes tools** timeouts
|
|
263
|
+
(`timeouts.tools.sequential_call` / `concurrent_batch` = 14400 s) — the outer
|
|
264
|
+
two are set 4x above the controller's so a long-but-healthy turn is never
|
|
265
|
+
killed by the supervisor layer.
|
|
266
|
+
|
|
267
|
+
## Development
|
|
268
|
+
|
|
269
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for the dev setup, how to run the
|
|
270
|
+
smoke test and the integration suite, and the contribution conventions.
|
|
271
|
+
|
|
272
|
+
## Files
|
|
273
|
+
|
|
274
|
+
| File | Role |
|
|
275
|
+
| --- | --- |
|
|
276
|
+
| `opencode_hermes_mcp/server.py` | FastMCP stdio server (the 6 tools) |
|
|
277
|
+
| `opencode_hermes_mcp/controller.py` | state machine: submit / wait / resume / classify |
|
|
278
|
+
| `opencode_hermes_mcp/client.py` | HTTP + SSE client for the OpenCode server |
|
|
279
|
+
| `opencode_hermes_mcp/models.py` | data helpers for turns / interactions |
|
|
280
|
+
| `opencode_hermes_mcp/smoke_client.py` | no-LLM smoke test (tool surface + basic calls) |
|
|
281
|
+
| `tests/run_tests.py` | full integration suite (live LLM turns) |
|
|
282
|
+
| `opencode_hermes_mcp/installer.py` | setup wizard (Python + rich; self-bootstrapping venv) |
|
|
283
|
+
| `opencode_hermes_mcp/pin.txt` | the OpenCode version pin (single source of truth, one line) |
|
|
284
|
+
| `scripts/install.sh` / `uninstall.sh` / `upgrade.sh` | lifecycle (`install.sh` is a thin wrapper around the wizard) |
|
|
285
|
+
| `scripts/helpers/ocattach` / `oc-current` | TUI attach helpers (installed to `~/.local/bin/`) |
|
|
286
|
+
|
|
287
|
+
## License
|
|
288
|
+
|
|
289
|
+
[MIT](LICENSE) — Copyright (c) 2026 Arthur Hottier.
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# opencode-hermes-mcp
|
|
2
|
+
|
|
3
|
+
[](LICENSE)
|
|
4
|
+
[](#prerequisites)
|
|
5
|
+
[-brightgreen.svg)](#version-pin-opencode-11821)
|
|
6
|
+
|
|
7
|
+
Deterministic MCP controller between **Hermes** (supervisor LLM) and the
|
|
8
|
+
permanent **OpenCode** server. The controller is a state machine — no LLM —
|
|
9
|
+
that blocks on OpenCode turns and surfaces questions/permissions to Hermes so
|
|
10
|
+
the supervisor LLM can decide and resume the *same* turn.
|
|
11
|
+
|
|
12
|
+
## Architecture
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
Hermes (LLM) --MCP stdio--> opencode_hermes_mcp.server (FastMCP, 6 tools) --HTTP + SSE--> OpenCode server :4096
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
- **Layer 1 — Hermes**: the supervisor LLM. It delegates a coding task with
|
|
19
|
+
`opencode_run` and decides when the controller reports
|
|
20
|
+
`needs_agent_input` (question / permission).
|
|
21
|
+
- **Layer 2 — this controller** (`opencode_hermes_mcp/`: `server.py` +
|
|
22
|
+
`controller.py` + `client.py` + `models.py`): a NO-LLM process spawned by
|
|
23
|
+
Hermes over MCP stdio. It submits the task, watches SSE + REST, blocks until
|
|
24
|
+
the turn completes / errors / needs input, and posts the supervisor's
|
|
25
|
+
decisions back into the same OpenCode turn (the prompt is never resubmitted).
|
|
26
|
+
- **Layer 3 — OpenCode server**: a permanent `opencode serve` process
|
|
27
|
+
(systemd user service `opencode-server`, loopback :4096, HTTP basic auth).
|
|
28
|
+
Its LLM is any supported provider (OpenAI-compatible endpoint, OpenAI, or
|
|
29
|
+
Anthropic), configured in `~/.config/opencode/opencode.json`.
|
|
30
|
+
|
|
31
|
+
Tools exposed to Hermes: `opencode_run`, `opencode_answer`,
|
|
32
|
+
`opencode_permission`, `opencode_abort`, `opencode_inspect` (diagnostic
|
|
33
|
+
only), `opencode_sessions`.
|
|
34
|
+
|
|
35
|
+
## Prerequisites
|
|
36
|
+
|
|
37
|
+
- Hermes installed (`~/.hermes/config.yaml` present)
|
|
38
|
+
- `python3` >= 3.11 (with PyYAML for the Hermes config patch)
|
|
39
|
+
- Network access (OpenCode binary install, `mcp` package, LLM endpoint)
|
|
40
|
+
- `systemd` user sessions (for the `opencode-server` service)
|
|
41
|
+
|
|
42
|
+
## Installation (2 commands)
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
git clone <repo-url> opencode-hermes-mcp && cd opencode-hermes-mcp
|
|
46
|
+
scripts/install.sh
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`scripts/install.sh` is a thin wrapper around the **setup wizard**
|
|
50
|
+
(`opencode_hermes_mcp/installer.py`, Python + `rich`): banner, numbered
|
|
51
|
+
steps, styled prompts, progress, and a summary panel. The wizard bootstraps
|
|
52
|
+
itself — if the repo venv is missing (or lacks `rich` / `pyyaml` /
|
|
53
|
+
`mcp==1.12.4` / the editable package), it creates it and re-execs, so a bare
|
|
54
|
+
`python3` >= 3.11 is the only prerequisite.
|
|
55
|
+
|
|
56
|
+
The install is idempotent — re-running it skips what is already in place.
|
|
57
|
+
It installs the pinned OpenCode binary, the venv (the `opencode_hermes_mcp`
|
|
58
|
+
package with `mcp==1.12.4` pinned), the LLM provider config + secret, the
|
|
59
|
+
server credentials, the two launchers, the systemd user service, and patches
|
|
60
|
+
`~/.hermes/config.yaml` (backup kept as `.bak`). It finishes with a health
|
|
61
|
+
check (bounded `curl --max-time 3`, last error surfaced) +
|
|
62
|
+
`python -m opencode_hermes_mcp.smoke_client` (must print
|
|
63
|
+
`tool surface OK`).
|
|
64
|
+
|
|
65
|
+
### LLM providers
|
|
66
|
+
|
|
67
|
+
The installer is provider-agnostic. Three providers are supported:
|
|
68
|
+
|
|
69
|
+
| Provider | Use | npm package |
|
|
70
|
+
| --- | --- | --- |
|
|
71
|
+
| `openai-compatible` | any OpenAI-compatible endpoint (Unsloth, Ollama, vLLM, llama-server, ...) — default | `@ai-sdk/openai-compatible` |
|
|
72
|
+
| `openai` | official OpenAI API | `@ai-sdk/openai` |
|
|
73
|
+
| `anthropic` | official Anthropic API | `@ai-sdk/anthropic` |
|
|
74
|
+
|
|
75
|
+
Interactive: pick the provider from the menu, then answer the prompts —
|
|
76
|
+
base URL + API key + model for `openai-compatible`, API key + model for
|
|
77
|
+
`openai` / `anthropic` — then the LLM speed (`slow` for a local LLM, which
|
|
78
|
+
adds `timeout:false` / `headerTimeout:false` / `chunkTimeout:120000` to the
|
|
79
|
+
provider options; `fast` is the default) and the model limits (context /
|
|
80
|
+
output, defaults 128000 / 32000).
|
|
81
|
+
|
|
82
|
+
Non-interactive (`--yes`), everything comes from env. Local
|
|
83
|
+
OpenAI-compatible endpoint (Ollama / vLLM / Unsloth / ...):
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
OPENCODE_PROVIDER=openai-compatible \
|
|
87
|
+
OPENCODE_LLM_BASE_URL=http://127.0.0.1:11434/v1 \
|
|
88
|
+
OPENCODE_API_KEY=... \
|
|
89
|
+
OPENCODE_LLM_MODEL=qwen3.8-27b \
|
|
90
|
+
OPENCODE_LLM_SPEED=slow \
|
|
91
|
+
scripts/install.sh --yes
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
OpenAI (cloud):
|
|
95
|
+
|
|
96
|
+
```sh
|
|
97
|
+
OPENCODE_PROVIDER=openai OPENCODE_API_KEY=sk-... OPENCODE_LLM_MODEL=gpt-4o \
|
|
98
|
+
scripts/install.sh --yes
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Anthropic (cloud):
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
OPENCODE_PROVIDER=anthropic OPENCODE_API_KEY=sk-ant-... \
|
|
105
|
+
OPENCODE_LLM_MODEL=claude-sonnet-4-5 scripts/install.sh --yes
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Flags: `--yes` (non-interactive, uses env `OPENCODE_PROVIDER` /
|
|
109
|
+
`OPENCODE_LLM_BASE_URL` / `OPENCODE_API_KEY` / `OPENCODE_LLM_MODEL` /
|
|
110
|
+
`OPENCODE_LLM_SPEED` / `OPENCODE_CONTEXT_LIMIT` / `OPENCODE_OUTPUT_LIMIT`),
|
|
111
|
+
`--port N` (default 4096), `--skip-binary`, `--force-config`, `--dry-run`,
|
|
112
|
+
`--skip-verify` (skip the final health + smoke verification — useful for
|
|
113
|
+
sandbox/CI).
|
|
114
|
+
|
|
115
|
+
`UNSLOTH_API_KEY` is still accepted as a deprecated fallback for
|
|
116
|
+
`OPENCODE_API_KEY` (existing scripts keep working).
|
|
117
|
+
|
|
118
|
+
**A new Hermes session is required** after installation to load the MCP
|
|
119
|
+
server.
|
|
120
|
+
|
|
121
|
+
## Hermes integration (manual)
|
|
122
|
+
|
|
123
|
+
The installer patches `~/.hermes/config.yaml` for you, but it does **not**
|
|
124
|
+
install a Hermes skill on purpose (Hermes's skill layout may change). The
|
|
125
|
+
package ships the full manual instead:
|
|
126
|
+
|
|
127
|
+
- [`docs/hermes-integration.md`](docs/hermes-integration.md) — what the MCP
|
|
128
|
+
is for, the exact config entry written, manual integration (by hand), the
|
|
129
|
+
six tools, troubleshooting, uninstall.
|
|
130
|
+
- [`docs/skill.example.md`](docs/skill.example.md) — a ready-to-copy Hermes
|
|
131
|
+
skill (the delegation protocol) to drop into `~/.hermes/skills/` and adapt.
|
|
132
|
+
|
|
133
|
+
## Usage
|
|
134
|
+
|
|
135
|
+
Hermes delegates work through the MCP tools — no manual CLI needed:
|
|
136
|
+
|
|
137
|
+
- `opencode_run(directory, task, agent)` — submit a task; blocks until the
|
|
138
|
+
turn completes, errors, or needs input. `agent` is required for a new
|
|
139
|
+
session (a primary agent of the project, e.g. `build`, `plan`, or a
|
|
140
|
+
project-specific agent).
|
|
141
|
+
- When a tool returns `state=needs_agent_input`, Hermes decides:
|
|
142
|
+
`opencode_answer` (pick exact option labels) or `opencode_permission`
|
|
143
|
+
(`once` / `always` / `reject`) — both resume the same turn.
|
|
144
|
+
- `opencode_abort` stops a stuck run; `opencode_sessions` lists sessions for
|
|
145
|
+
a directory; `opencode_inspect` is for exceptional diagnostics only (never
|
|
146
|
+
poll a running task).
|
|
147
|
+
|
|
148
|
+
The Hermes-side wiring (written by `scripts/install.sh` into
|
|
149
|
+
`~/.hermes/config.yaml`):
|
|
150
|
+
|
|
151
|
+
```yaml
|
|
152
|
+
mcp_servers:
|
|
153
|
+
opencode:
|
|
154
|
+
command: ~/.local/bin/opencode-mcp-launch.sh
|
|
155
|
+
enabled: true
|
|
156
|
+
timeout: 14400
|
|
157
|
+
connect_timeout: 30
|
|
158
|
+
supports_parallel_tool_calls: false
|
|
159
|
+
timeouts:
|
|
160
|
+
tools:
|
|
161
|
+
sequential_call: 14400
|
|
162
|
+
concurrent_batch: 14400
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The launcher reads the OpenCode server credentials from
|
|
166
|
+
`~/.config/hermes/opencode-server.json` and execs
|
|
167
|
+
`python -m opencode_hermes_mcp.server` in the repo venv — `config.yaml` stays
|
|
168
|
+
secret-free.
|
|
169
|
+
|
|
170
|
+
## TUI attach helpers (watch OpenCode live)
|
|
171
|
+
|
|
172
|
+
`install.sh` also drops two helpers into `~/.local/bin/` (sources:
|
|
173
|
+
`scripts/helpers/`):
|
|
174
|
+
|
|
175
|
+
```sh
|
|
176
|
+
ocattach <repo-abs> [ses_...] # open the OpenCode TUI on a repo / session
|
|
177
|
+
oc-current # attach to the session Hermes is supervising NOW
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
- `ocattach` opens the OpenCode TUI (`opencode attach`) against the permanent
|
|
181
|
+
server `:4096` — no tmux needed. Without a session id it opens the latest
|
|
182
|
+
session / lets you pick one.
|
|
183
|
+
- `oc-current` reads the newest `~/.local/state/opencode-hermes-mcp/turn_*.json`
|
|
184
|
+
(the controller's in-flight turn state) and attaches to that session — use
|
|
185
|
+
it while Hermes is driving OpenCode, to watch the reasoning live.
|
|
186
|
+
|
|
187
|
+
Both read the server credentials from `~/.config/hermes/opencode-server.json`
|
|
188
|
+
(same source as the controller launcher). **Do not press Esc/Ctrl+C in the TUI
|
|
189
|
+
while a turn is active** — that aborts the in-flight turn on the OpenCode side.
|
|
190
|
+
|
|
191
|
+
## Upgrade / uninstall
|
|
192
|
+
|
|
193
|
+
```sh
|
|
194
|
+
scripts/upgrade.sh # controller only: git pull + venv deps + restart + smoke
|
|
195
|
+
scripts/upgrade.sh --binary # install the PINNED OpenCode binary (idempotent) — see "Version pin" below
|
|
196
|
+
scripts/uninstall.sh # service, launchers, venv, hermes entry, credentials
|
|
197
|
+
scripts/uninstall.sh --purge # + OpenCode provider config + API key secret
|
|
198
|
+
scripts/uninstall.sh --purge-binary # + the OpenCode binary
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
`uninstall.sh` never touches the git clone, the OpenCode provider config, the
|
|
202
|
+
API key secret, or the binary (unless the purge flags say so).
|
|
203
|
+
|
|
204
|
+
## Version pin: OpenCode 1.18.21
|
|
205
|
+
|
|
206
|
+
The controller is **validated against OpenCode `1.18.21` only** (its endpoint
|
|
207
|
+
contract was verified against that binary's live `/doc`, not the web docs).
|
|
208
|
+
The pin is a **single source of truth** in `opencode_hermes_mcp/pin.txt`
|
|
209
|
+
(one line, no `v` prefix): `installer.py` and `scripts/upgrade.sh` both read
|
|
210
|
+
it, falling back to the built-in constant when the file is missing or empty
|
|
211
|
+
(e.g. pip installs where the file is not shipped next to the code). `install.sh` pins
|
|
212
|
+
the binary to that version; `upgrade.sh` never upgrades the binary by
|
|
213
|
+
default.
|
|
214
|
+
|
|
215
|
+
`scripts/upgrade.sh --binary` (no version) installs the pinned version and is
|
|
216
|
+
idempotent (no-op if the binary is already at the pin). `--binary latest` is
|
|
217
|
+
the explicit opt-in to the bleeding edge; `--binary X.Y.Z` installs the
|
|
218
|
+
requested version. For anything other than the pin, the script warns you and
|
|
219
|
+
you MUST re-validate the controller before trusting it:
|
|
220
|
+
|
|
221
|
+
```sh
|
|
222
|
+
.venv/bin/python tests/run_tests.py
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
(all checks must pass; the suite drives the controller over MCP stdio against
|
|
226
|
+
the live server). If it fails, pin back: `scripts/upgrade.sh --binary`.
|
|
227
|
+
|
|
228
|
+
## Timeouts
|
|
229
|
+
|
|
230
|
+
Three independent timeouts bound the pipeline: the **controller** run timeout
|
|
231
|
+
(`DEFAULT_RUN_TIMEOUT` = 3600 s — a single `opencode_run`/`opencode_answer`/
|
|
232
|
+
`opencode_permission` call gives up after an hour), the **MCP** server
|
|
233
|
+
timeout in `~/.hermes/config.yaml` (`mcp_servers.opencode.timeout` = 14400 s,
|
|
234
|
+
`connect_timeout` = 30 s), and the **Hermes tools** timeouts
|
|
235
|
+
(`timeouts.tools.sequential_call` / `concurrent_batch` = 14400 s) — the outer
|
|
236
|
+
two are set 4x above the controller's so a long-but-healthy turn is never
|
|
237
|
+
killed by the supervisor layer.
|
|
238
|
+
|
|
239
|
+
## Development
|
|
240
|
+
|
|
241
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for the dev setup, how to run the
|
|
242
|
+
smoke test and the integration suite, and the contribution conventions.
|
|
243
|
+
|
|
244
|
+
## Files
|
|
245
|
+
|
|
246
|
+
| File | Role |
|
|
247
|
+
| --- | --- |
|
|
248
|
+
| `opencode_hermes_mcp/server.py` | FastMCP stdio server (the 6 tools) |
|
|
249
|
+
| `opencode_hermes_mcp/controller.py` | state machine: submit / wait / resume / classify |
|
|
250
|
+
| `opencode_hermes_mcp/client.py` | HTTP + SSE client for the OpenCode server |
|
|
251
|
+
| `opencode_hermes_mcp/models.py` | data helpers for turns / interactions |
|
|
252
|
+
| `opencode_hermes_mcp/smoke_client.py` | no-LLM smoke test (tool surface + basic calls) |
|
|
253
|
+
| `tests/run_tests.py` | full integration suite (live LLM turns) |
|
|
254
|
+
| `opencode_hermes_mcp/installer.py` | setup wizard (Python + rich; self-bootstrapping venv) |
|
|
255
|
+
| `opencode_hermes_mcp/pin.txt` | the OpenCode version pin (single source of truth, one line) |
|
|
256
|
+
| `scripts/install.sh` / `uninstall.sh` / `upgrade.sh` | lifecycle (`install.sh` is a thin wrapper around the wizard) |
|
|
257
|
+
| `scripts/helpers/ocattach` / `oc-current` | TUI attach helpers (installed to `~/.local/bin/`) |
|
|
258
|
+
|
|
259
|
+
## License
|
|
260
|
+
|
|
261
|
+
[MIT](LICENSE) — Copyright (c) 2026 Arthur Hottier.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"""opencode-hermes-mcp — deterministic MCP controller between Hermes (supervisor
|
|
2
|
+
LLM) and the permanent OpenCode server. No LLM: a state machine + HTTP/SSE
|
|
3
|
+
client, pinned to the OpenCode version in opencode_hermes_mcp/pin.txt
|
|
4
|
+
(currently 1.18.21)."""
|
|
5
|
+
|
|
6
|
+
__version__ = "0.4.1"
|