fix: initialize default mcps

This commit is contained in:
Timothy
2026-04-10 16:42:03 -07:00
parent 574a3a284e
commit caebbea1aa
4 changed files with 83 additions and 2 deletions
+2 -1
View File
@@ -15,7 +15,8 @@
"Read(//tmp/**)"
],
"additionalDirectories": [
"/home/timothy/.hive/skills/writing-hive-skills"
"/home/timothy/.hive/skills/writing-hive-skills",
"/tmp"
]
},
"hooks": {
+66
View File
@@ -36,6 +36,25 @@ _DEFAULT_CONFIG = {
"refresh_interval_hours": DEFAULT_REFRESH_INTERVAL_HOURS,
}
# Default local MCP servers that ship with Hive. Seeded on first startup so
# fresh users get working file I/O, browser automation, and the hive tool
# suite without having to run `hive mcp add` manually. ``cwd`` is filled in
# at registration time with the absolute path to the ``tools/`` directory.
_DEFAULT_LOCAL_SERVERS: dict[str, dict[str, Any]] = {
"hive-tools": {
"description": "Hive tools: web search, email, CRM, calendar, and 100+ integrations",
"args": ["run", "python", "mcp_server.py", "--stdio"],
},
"gcu-tools": {
"description": "Browser automation: click, type, navigate, screenshot, snapshot",
"args": ["run", "python", "-m", "gcu.server", "--stdio"],
},
"files-tools": {
"description": "File I/O: read, write, edit, search, list, run commands",
"args": ["run", "python", "files_server.py", "--stdio"],
},
}
class MCPRegistry:
"""Manages local MCP server state in ~/.hive/mcp_registry/."""
@@ -59,6 +78,53 @@ class MCPRegistry:
if not self._installed_path.exists():
self._write_json(self._installed_path, {"servers": {}})
def ensure_defaults(self) -> list[str]:
"""Seed the built-in local MCP servers (hive-tools, gcu-tools, files-tools).
Idempotent servers already present are left untouched. Skips seeding
entirely when the source-tree ``tools/`` directory cannot be located
(e.g. when Hive is installed from a wheel rather than a checkout).
Returns the list of names that were newly registered.
"""
self.initialize()
# parents: [0]=loader, [1]=framework, [2]=core, [3]=repo root
tools_dir = Path(__file__).resolve().parents[3] / "tools"
if not tools_dir.is_dir():
logger.debug(
"MCPRegistry.ensure_defaults: tools dir %s missing; skipping default seed",
tools_dir,
)
return []
cwd = str(tools_dir)
data = self._read_installed()
existing = data.get("servers", {})
added: list[str] = []
for name, spec in _DEFAULT_LOCAL_SERVERS.items():
if name in existing:
continue
try:
self.add_local(
name=name,
transport="stdio",
command="uv",
args=list(spec["args"]),
cwd=cwd,
description=spec["description"],
)
added.append(name)
except MCPError as exc:
logger.warning(
"MCPRegistry.ensure_defaults: failed to seed '%s': %s", name, exc
)
if added:
logger.info("MCPRegistry: seeded default local servers: %s", added)
return added
# ── Internal I/O ────────────────────────────────────────────────
def _read_installed(self) -> dict:
+1
View File
@@ -263,6 +263,7 @@ def create_app(model: str | None = None) -> web.Application:
registry = MCPRegistry()
registry.initialize()
registry.ensure_defaults()
if (queen_pkg_dir / "mcp_registry.json").is_file():
_queen_tool_registry.set_mcp_registry_agent_path(queen_pkg_dir)
registry_configs, selection_max_tools = registry.load_agent_selection(queen_pkg_dir)
+14 -1
View File
@@ -830,7 +830,16 @@ class TestCrashRecovery:
async def test_restore_legacy_unphased_assistant_message_preserves_store(
self, tmp_path, runtime, buffer
):
"""Legacy queen stores without phase_id should resume instead of being cleared."""
"""Legacy queen stores without phase_id should resume instead of being cleared.
The queen is a forever-alive conversational node: it no longer
terminates after a single text-only turn (the old implicit-judge
ACCEPT path was deliberately removed so Charlotte/etc. can greet,
clarify, summarize without the loop exiting). To keep this test
verifying "restore preserves the legacy store" we pre-signal
shutdown so the queen exits cleanly after producing its first
recovered turn.
"""
store = FileConversationStore(tmp_path / "conv")
await store.write_meta(
{
@@ -864,6 +873,10 @@ class TestCrashRecovery:
)
ctx = build_ctx(runtime, spec, buffer, llm, stream_id="queen")
# Pre-signal shutdown — the queen runs one iteration, produces
# its recovered text, then exits the auto-block wait because
# _shutdown is True (got_input returns False → loop terminates).
node.signal_shutdown()
result = await node.execute(ctx)
assert result.success is True