fix: initialize default mcps
This commit is contained in:
@@ -15,7 +15,8 @@
|
||||
"Read(//tmp/**)"
|
||||
],
|
||||
"additionalDirectories": [
|
||||
"/home/timothy/.hive/skills/writing-hive-skills"
|
||||
"/home/timothy/.hive/skills/writing-hive-skills",
|
||||
"/tmp"
|
||||
]
|
||||
},
|
||||
"hooks": {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user