block / block/buzz

`mcp_hooks` is unreachable for custom harnesses — MCP_HOOK_SERVERS reaches exactly one runtime

Open
#5,571 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
32.7k
Forks
4.3k
Avg merge
1d 13h
Merged PRs (30d)
253

Description

**Motivation**

`MCP_HOOK_SERVERS` is documented as a portable convention — `docs/MCP_DRIVEN_HOOKS.md:106`:

> `MCP_HOOK_SERVERS` is a standard env var name intended for cross-agent adoption.

A custom ACP harness cannot adopt it. Not "it's awkward" — there is no field to
set and no code path that would read one. The convention's practical reach today
is exactly one runtime: `buzz-agent`.

All references verified against `main` at
`be48ce98bd163899197b79a82ad5b2bcf0bc9b54`.

**The three facts**

1. `mcp_hooks` is a field on the *static built-in* catalog only —
`KnownAcpRuntime.mcp_hooks` (`desktop/src-tauri/src/managed_agents/discovery/runtime_metadata.rs:12`),
`true` for `buzz-agent` (`discovery.rs:188`), `false` for goose, claude and
codex (`:86`, `:121`, `:154`).

2. Both gates that act on it resolve through that catalog and nothing else.

- Local spawn, `managed_agents/runtime.rs:546-549`:
```rust
let runtime_meta = known_acp_runtime(effective_command);
if runtime_meta.is_some_and(|r| r.mcp_hooks) {
command.env("MCP_HOOK_SERVERS", "*");
}
```
- Provider deploy, `commands/agents_deploy.rs:60-73`: `policy_env` gets
`MCP_HOOK_SERVERS=*` only inside `if let Some(runtime) = runtime`.

`known_acp_runtime` (`discovery.rs:268-279`) searches `KNOWN_ACP_RUNTIMES` by
id, command basename or alias. A custom harness id is not in that array, so
it resolves to `None`, so `is_some_and` is `false`, on both paths.

3. `HarnessDefinition` — the user-authored custom-harness schema,
`managed_agents/custom_harnesses.rs:49-70` — carries `id`, `label`,
`command`, `args`, `env`, `installInstructionsUrl`, `installHint`. There is
no hooks field, so even if a gate wanted to consult the definition there
would be nothing to consult.

A corollary that shows the shape of the problem: because
`normalize_command_identity` (`discovery.rs:239-259`) matches on the executable
basename, the *only* way a custom harness gets hook tools today is to name its
binary `buzz-agent`. Capability is being inferred from a filename.

**Why I think this is collateral rather than intent**

The schema's own doc comment states a real security line
(`custom_harnesses.rs:44-48`):

> Only the fields a custom harness definition is permitted to carry are
> included here — install commands and avatar URLs are intentionally absent
> (security line: no remote icon URLs from user-editable config).

That line is correct and I am not asking to cross it. User-editable icon URLs
plus user-editable install commands is a phishing kit, and a JSON file dropped
into a directory is the wrong place for either.

`mcpHooks` is not on that side of the line. It grants no new code execution and
names no new resource: it enables the `_Stop` / `_PostCompact` tool calls
against MCP servers the same user already configured, and hook behaviour is
already bounded independently of who enabled it — 2.5s per-hook timeout,
3 objections per prompt, off by default (`docs/MCP_DRIVEN_HOOKS.md`, Configuration).
It is a one-bit declaration of *"my harness implements this protocol"*, which is
a fact only the harness author knows.

Equally, `mcp_hooks: false` for goose, claude and codex is not a gap and I am
not asking to flip it — those runtimes genuinely do not implement the hook
tools, and the catalog is telling the truth about them
(`runtime/tests.rs:102`: `"codex-acp does not handle MCP_HOOK_SERVERS"`). The
built-in table can be accurate because Buzz owns those entries. For a custom
harness Buzz cannot know, and currently provides no way to be told.

**Proposed solution**

Add an optional `mcpHooks: bool` (default `false`) to `HarnessDefinition`, and
have both gates read the effective harness descriptor rather than
`known_acp_runtime` directly.

The precedent already exists and is one field wide: **PR #4078** ("feat(desktop):
support MCP sidecars for custom harnesses", open) adds an optional
`mcp_command: Option` to `HarnessDefinition` and threads it through
`save_custom_harness` → the effective harness descriptor → spawn and restart
hashing. `mcpHooks` is a sibling of that field on the same path. If #4078 lands
first, this becomes a small follow-up rather than new plumbing.

**Alternatives considered**

- *Infer it.* There is nothing to infer from — the hook tools are discovered on
the MCP server side, not the harness side, and the harness's willingness to
call `_Stop` is not observable before a turn ends.
- *A user-level "enable hooks" toggle on the agent instead of the harness
definition.* Wrong altitude: hook support is a property of the harness binary,
not of one agent that uses it, and it would then need setting per agent.
- *Leave it and set `MCP_HOOK_SERVERS` from `HarnessDefinition.env`.* This does
work today for the local-spawn path and is what an out-of-tree provider ends
up doing on the deploy path. It is a workaround, not the contract: it puts a
reserved-namespace control-plane variable into a user env map, and nothing
validates or reports it.

**Does PR #3196 change the picture?**

No — checked, and worth stating because the two limits are easy to conflate.
**#3196** ("feat(acp): allow `BUZZ_ACP_MCP_COMMAND` to run several MCP servers",
open since 2026-07-27) changes `build_mcp_servers` in `crates/buzz-acp`; it
touches neither `mcp_hooks` nor `HarnessDefinition`. It lifts the *other*
constraint (#2899, one MCP server per agent), which makes hooks considerably
more useful once they are reachable — a hook server and a tool server can
finally coexist — but it does not make them reachable.

Reach today: `buzz-agent` only, one server. Reach with #3196 merged:
`buzz-agent` only, many servers.

**Additional context**

Searched issues and PRs before filing. Closest existing work: **#4078** (the
`mcpCommand` precedent above), **#3196** / **#2899** (server count, not hook
enablement), **#4550** (per-agent MCP injection for preset/custom runtimes),
**#3385** (BYOH harness receives no authenticated reply tool — #4078 closes it),
**#3780** (portable agent-session hook *semantics*, a layer above this).
Nothing found that asks for `mcp_hooks` on a custom harness.

Found while building an out-of-tree `buzz-backend-*` provider that runs Buzz
agents in [Orca](https://stably.ai) worktrees, shipping an MCP hook server
alongside a persona pack. The pack had to pin `runtime: buzz-agent` and add a
test that fails if anyone changes it, because the persona format can express a
runtime whose hooks will be silently discarded and cannot express whether they
will be. Buzz Desktop 0.5.8, macOS.

Contributor guide

Open the contributing guide

Research direction

Start with HarnessDefinition in managed_agents/custom_harnesses.rs and compare the mcp_command plumbing from PR #4078. Trace the effective harness descriptor into managed_agents/runtime.rs and commands/agents_deploy.rs, then inspect the built-in lookup in discovery.rs. Done means custom harnesses can explicitly declare hook support on both paths while the default remains disabled and built-in runtime behavior is unchanged.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
desktop-dev, devtools
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.