diff --git a/CLAUDE.md b/CLAUDE.md index 5b0d3719..d8ce5e71 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -123,15 +123,18 @@ hand-maintained per-file tree drifts out of sync with the code. + manager socket (`/run/hive/mcp.sock`), the protocol an agent's harness speaks to `hive-c0re`. Same split rationale as the two above; the shared payload types it references stay in `hive-sh4re`. -- **`hive-agent-sock/`** — wire types for the *in-agent* socket, served by - the harness to in-container producers — matrix / bash MCP daemons and - `forge_notify` are the built-in ones, but any user-configured MCP server - can push todos here too, nothing restricts the `subsystem` set. Carries - the loose-ends-v2 **todo** ops plus - harness-local reminders. ⚠️ Distinct from +- **`hive-agent-sock/`** — what's common across the in-container "agent + plugins" (matrix/bash/subagent MCP daemons, `forge_notify`, any + user-configured MCP server). Wire types for the *in-agent* socket, served + by the harness to those producers — nothing restricts the `subsystem` set + on the todo ops, any producer can push its own. Carries the loose-ends-v2 + **todo** ops plus harness-local reminders. ⚠️ Distinct from `hive-core-agent-sock` above: **this socket never leaves the container** and `hive-c0re` is not in the path at all — no broker round-trip, no - long-poll, no marker files. + long-poll, no marker files. Also carries `extra_mcp`: the + `hyperhive.extraMcpServers` spec + parsing, shared by `hive-agent` and + `hive-subagent-mcp` — not a socket protocol, just the natural home since + both already depend on this crate. - **`hive-types/`** — zero-dependency (bar serde) leaf crate holding the foundational newtypes, chiefly `Ident` (1–63 chars of `[a-z0-9-]`, constructed only via the validating parser). Lets every wire-type crate diff --git a/Cargo.lock b/Cargo.lock index 570e2c91..447ae62b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1654,7 +1654,6 @@ dependencies = [ "hive-agent-sock", "hive-claude", "hive-core-agent-sock", - "hive-extra-mcp", "hive-sh4re", "hive-sock-client", "http-body-util", @@ -1706,6 +1705,8 @@ version = "0.1.0" dependencies = [ "hive-sh4re", "serde", + "serde_json", + "tracing", ] [[package]] @@ -1806,15 +1807,6 @@ dependencies = [ "serde", ] -[[package]] -name = "hive-extra-mcp" -version = "0.1.0" -dependencies = [ - "serde", - "serde_json", - "tracing", -] - [[package]] name = "hive-forge" version = "0.1.0" @@ -2002,7 +1994,6 @@ dependencies = [ "clap", "hive-agent-sock", "hive-claude", - "hive-extra-mcp", "hive-sock-client", "hive-types", "rmcp", diff --git a/Cargo.toml b/Cargo.toml index 2f660c5e..a4d1206f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,6 @@ members = [ "hive-core-agent-sock", "hive-bash-mcp", "hive-c0re", - "hive-extra-mcp", "hive-screen-mcp", "hive-forge", "hive-forge-notify", @@ -86,7 +85,6 @@ hive-jobq = { path = "hive-jobq" } hive-jobq-metrics = { path = "hive-jobq-metrics" } hive-jobq-wire = { path = "hive-jobq-wire" } hive-core-agent-sock = { path = "hive-core-agent-sock" } -hive-extra-mcp = { path = "hive-extra-mcp" } hive-claude = "0.1.1" hive-host-sock = { path = "hive-host-sock" } hive-priv-sock = { path = "hive-priv-sock" } diff --git a/hive-agent-sock/Cargo.toml b/hive-agent-sock/Cargo.toml index 0c6b82e7..b0585ffd 100644 --- a/hive-agent-sock/Cargo.toml +++ b/hive-agent-sock/Cargo.toml @@ -9,4 +9,6 @@ workspace = true [dependencies] serde.workspace = true +serde_json.workspace = true +tracing.workspace = true hive-sh4re.workspace = true diff --git a/hive-agent-sock/README.md b/hive-agent-sock/README.md index e133aad8..59ed906b 100644 --- a/hive-agent-sock/README.md +++ b/hive-agent-sock/README.md @@ -1,14 +1,15 @@ # hive-agent-sock -Wire types for the **in-agent socket** — the one the `hive-agent` harness serves -_inside_ the container to its local producers. The matrix / bash MCP daemons -and `forge_notify` are the _built-in_ producers that ship today, but any -user-configured MCP server declared in an agent's `agent.nix` can dial this -socket and push its own todos with an arbitrary `subsystem` marker — the -wire format doesn't restrict the set. Unlike the host-served sockets, this -one **never leaves the container**. +What's common across the in-container "agent plugins" — the matrix / bash / +subagent MCP daemons and `forge_notify` today, plus any user-configured MCP +server declared in an agent's `agent.nix`. Two things live here: -## What it carries +## Wire types for the in-agent socket + +The socket the `hive-agent` harness serves _inside_ the container to those +local producers. Any producer can dial it and push its own todos with an +arbitrary `subsystem` marker — the wire format doesn't restrict the set. +Unlike the host-served sockets, this one **never leaves the container**. - the loose-ends-v2 **todo** op family - the harness-local **reminder** op family @@ -16,14 +17,23 @@ one **never leaves the container**. More in-agent request families may be added over time — the socket is deliberately named for the agent, not for the todos. -## Why in-container +**Why in-container**: the harness owns the todo + reminder stores locally and +signals its own turn loop directly, so `hive-c0re` is not in either path: no +broker round-trip, no long-poll, no marker files. That locality is the +point — it's also what lets these stores travel with the agent for hive +portability. -The harness owns the todo + reminder stores locally and signals its own turn -loop directly, so `hive-c0re` is not in either path: no broker round-trip, no -long-poll, no marker files. That locality is the point — it's also what lets -these stores travel with the agent for hive portability. +**Not to be confused with `hive-core-agent-sock`**: that crate is the +_host_-served core↔agent protocol on `/run/hive/mcp.sock` (the harness +talking out to `hive-c0re`). This socket is purely intra-container. -## Not to be confused with `hive-core-agent-sock` +## `extra_mcp`: the `hyperhive.extraMcpServers` spec -That crate is the _host_-served core↔agent protocol on `/run/hive/mcp.sock` -(the harness talking out to `hive-c0re`). This socket is purely intra-container. +Not a socket protocol — a shared config type. Parses +`/etc/hyperhive/extra-mcp.json` (the nix-rendered `hyperhive.extraMcpServers` +option) and renders each entry into the shape claude expects in a +`--mcp-config` blob. Shared by `hive-agent` (the main session's servers) and +`hive-subagent-mcp` (the subagent-eligible subset, gated on +`availableToSubagents`) — both already depend on this crate for the socket +types above, so a new plugin-facing shared type belongs here rather than in +a crate of its own. diff --git a/hive-extra-mcp/src/lib.rs b/hive-agent-sock/src/extra_mcp.rs similarity index 94% rename from hive-extra-mcp/src/lib.rs rename to hive-agent-sock/src/extra_mcp.rs index c9cca275..57681c57 100644 --- a/hive-extra-mcp/src/lib.rs +++ b/hive-agent-sock/src/extra_mcp.rs @@ -1,11 +1,12 @@ //! Shared spec for `hyperhive.extraMcpServers` entries: the on-disk JSON //! shape the nix module (`nix/agent-modules/mcp.nix`) renders to //! `/etc/hyperhive/extra-mcp.json`, plus the parsing and per-entry -//! JSON-rendering logic every consumer needs. Split out of `hive-agent` -//! (its only consumer until now) so `hive-subagent-mcp` can build its own -//! filtered subagent `--mcp-config` without depending on a binary crate that -//! has no lib target — same rationale as the `*-sock` crate splits -//! elsewhere in this workspace. +//! JSON-rendering logic every consumer needs. Lives in `hive-agent-sock` +//! rather than a crate of its own: this crate is already what's common +//! across the in-container "agent plugins" (matrix/bash/subagent MCP +//! daemons, `forge_notify`) — the todo-socket wire types above, and now +//! this — so a new plugin-facing shared type belongs here rather than in +//! a fresh single-purpose crate. use std::collections::BTreeMap; use std::path::Path; diff --git a/hive-agent-sock/src/lib.rs b/hive-agent-sock/src/lib.rs index aab4e2e7..9458f1e0 100644 --- a/hive-agent-sock/src/lib.rs +++ b/hive-agent-sock/src/lib.rs @@ -1,23 +1,31 @@ -//! Wire types for the *in-agent* socket, served by the hive-agent harness -//! to the in-container producers. Matrix / bash MCP daemons and -//! `forge_notify` are the built-in ones today, but `subsystem` on the todo -//! ops below is a plain string, not a closed set: any user-configured MCP -//! server declared in an agent's `agent.nix` can dial this socket and push -//! its own todos the same way. Carries the loose-ends-v2 *todo* op family -//! plus the harness-local *reminder* op family; more in-agent request -//! families may be added over time (the socket is deliberately named for -//! the agent, not the todos). +//! Rescoped beyond its original name: what's common across the in-container +//! "agent plugins" (matrix/bash/subagent MCP daemons, `forge_notify`, and any +//! user-configured MCP server) — not just the in-agent socket wire types. +//! +//! **Wire types for the *in-agent* socket**, served by the hive-agent harness +//! to those producers. `subsystem` on the todo ops below is a plain string, +//! not a closed set: any user-configured MCP server declared in an agent's +//! `agent.nix` can dial this socket and push its own todos the same way. +//! Carries the loose-ends-v2 *todo* op family plus the harness-local +//! *reminder* op family; more in-agent request families may be added over +//! time (the socket is deliberately named for the agent, not the todos). //! //! Distinct from `hive-core-agent-sock`, the *host*-served core↔agent //! protocol on `/run/hive/mcp.sock`: this socket never leaves the //! container. The harness owns the todo + reminder stores locally and //! signals its own turn loop directly, so hive-c0re is not in either path — //! no broker round-trip, no long-poll, no marker files. +//! +//! **`extra_mcp`**: the `hyperhive.extraMcpServers` spec + parsing, shared by +//! `hive-agent` (the main session's `--mcp-config`) and `hive-subagent-mcp` +//! (the subagent-eligible subset) — not a socket protocol at all, but this +//! crate is the natural home since both consumers already depend on it. use serde::{Deserialize, Serialize}; use hive_sh4re::inbox::LooseEnd; +pub mod extra_mcp; pub mod paths; /// In-container path of the harness-served in-agent socket. The harness diff --git a/hive-agent/Cargo.toml b/hive-agent/Cargo.toml index d5d31c93..14478708 100644 --- a/hive-agent/Cargo.toml +++ b/hive-agent/Cargo.toml @@ -23,7 +23,6 @@ clap.workspace = true hive-claude.workspace = true hive-agent-sock.workspace = true hive-core-agent-sock.workspace = true -hive-extra-mcp.workspace = true hive-sh4re.workspace = true hive-sock-client.workspace = true libc.workspace = true diff --git a/hive-agent/src/mcp_config.rs b/hive-agent/src/mcp_config.rs index da8fd869..e13a4585 100644 --- a/hive-agent/src/mcp_config.rs +++ b/hive-agent/src/mcp_config.rs @@ -184,7 +184,7 @@ pub fn allowed_mcp_tools(groups: &[hive_sh4re::permissions::ToolGroup]) -> Vec__` so claude can call // them without per-tool operator approval. `["*"]` (the default) // expands to `mcp____*` — every tool from that server. - for (server, spec) in hive_extra_mcp::load_extra_mcp() { + for (server, spec) in hive_agent_sock::extra_mcp::load_extra_mcp() { if server == SERVER_NAME || !extra_server_enabled(&server, groups) { continue; } @@ -300,7 +300,7 @@ fn build_mcp_servers() -> serde_json::Map { // agent isn't entitled to must not even appear in the MCP config, or the // agent could call it directly (there is no later enforcement point). let groups = effective_tool_groups(); - for (name, spec) in hive_extra_mcp::load_extra_mcp() { + for (name, spec) in hive_agent_sock::extra_mcp::load_extra_mcp() { if name == SERVER_NAME { tracing::warn!( "extra MCP server name `{SERVER_NAME}` collides with the built-in surface; ignoring", diff --git a/hive-extra-mcp/Cargo.toml b/hive-extra-mcp/Cargo.toml deleted file mode 100644 index 3954c7bf..00000000 --- a/hive-extra-mcp/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "hive-extra-mcp" -edition.workspace = true -version.workspace = true - -[lints] -workspace = true - -[dependencies] -serde.workspace = true -serde_json.workspace = true -tracing.workspace = true - -# Shared spec for `hyperhive.extraMcpServers` — see src/lib.rs. Split out of -# `hive-agent` (its only consumer until now) so `hive-subagent-mcp` can build -# its own filtered subagent `--mcp-config` without depending on a binary -# crate that has no lib target — same rationale as the `*-sock` crate splits -# elsewhere in this workspace. diff --git a/hive-subagent-mcp/Cargo.toml b/hive-subagent-mcp/Cargo.toml index 9c673464..b3315719 100644 --- a/hive-subagent-mcp/Cargo.toml +++ b/hive-subagent-mcp/Cargo.toml @@ -13,7 +13,6 @@ axum.workspace = true clap.workspace = true hive-agent-sock.workspace = true hive-claude.workspace = true -hive-extra-mcp.workspace = true hive-sock-client.workspace = true hive-types.workspace = true rmcp.workspace = true diff --git a/hive-subagent-mcp/src/mcp_config.rs b/hive-subagent-mcp/src/mcp_config.rs index bced0bc5..74520314 100644 --- a/hive-subagent-mcp/src/mcp_config.rs +++ b/hive-subagent-mcp/src/mcp_config.rs @@ -1,6 +1,6 @@ //! Builds a subagent's own filtered `--mcp-config`: only //! `hyperhive.extraMcpServers` entries with `availableToSubagents = true` -//! (see `hive_extra_mcp::ExtraMcpServer::available_to_subagents`) ever reach +//! (see `hive_agent_sock::extra_mcp::ExtraMcpServer::available_to_subagents`) ever reach //! a subagent's claude invocation. Everything else — the built-in hyperhive //! surface (todos/messaging), the automatically injected `bash` and `subagent` //! entries — stays unreachable by construction: none of those default to @@ -24,11 +24,12 @@ const CONFIG_FILE: &str = "subagent-mcp-config.json"; #[must_use] pub fn build() -> Option { let state_dir = crate::paths::state_dir(); - let servers: serde_json::Map = hive_extra_mcp::load_extra_mcp() - .into_iter() - .filter(|(_, spec)| spec.available_to_subagents()) - .map(|(name, spec)| (name, spec.to_json_entry(&state_dir))) - .collect(); + let servers: serde_json::Map = + hive_agent_sock::extra_mcp::load_extra_mcp() + .into_iter() + .filter(|(_, spec)| spec.available_to_subagents()) + .map(|(name, spec)| (name, spec.to_json_entry(&state_dir))) + .collect(); if servers.is_empty() { return None; }