fix: initialize default mcps
This commit is contained in:
@@ -15,7 +15,8 @@
|
|||||||
"Read(//tmp/**)"
|
"Read(//tmp/**)"
|
||||||
],
|
],
|
||||||
"additionalDirectories": [
|
"additionalDirectories": [
|
||||||
"/home/timothy/.hive/skills/writing-hive-skills"
|
"/home/timothy/.hive/skills/writing-hive-skills",
|
||||||
|
"/tmp"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hooks": {
|
"hooks": {
|
||||||
|
|||||||
@@ -36,6 +36,25 @@ _DEFAULT_CONFIG = {
|
|||||||
"refresh_interval_hours": DEFAULT_REFRESH_INTERVAL_HOURS,
|
"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:
|
class MCPRegistry:
|
||||||
"""Manages local MCP server state in ~/.hive/mcp_registry/."""
|
"""Manages local MCP server state in ~/.hive/mcp_registry/."""
|
||||||
@@ -59,6 +78,53 @@ class MCPRegistry:
|
|||||||
if not self._installed_path.exists():
|
if not self._installed_path.exists():
|
||||||
self._write_json(self._installed_path, {"servers": {}})
|
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 ────────────────────────────────────────────────
|
# ── Internal I/O ────────────────────────────────────────────────
|
||||||
|
|
||||||
def _read_installed(self) -> dict:
|
def _read_installed(self) -> dict:
|
||||||
|
|||||||
@@ -263,6 +263,7 @@ def create_app(model: str | None = None) -> web.Application:
|
|||||||
|
|
||||||
registry = MCPRegistry()
|
registry = MCPRegistry()
|
||||||
registry.initialize()
|
registry.initialize()
|
||||||
|
registry.ensure_defaults()
|
||||||
if (queen_pkg_dir / "mcp_registry.json").is_file():
|
if (queen_pkg_dir / "mcp_registry.json").is_file():
|
||||||
_queen_tool_registry.set_mcp_registry_agent_path(queen_pkg_dir)
|
_queen_tool_registry.set_mcp_registry_agent_path(queen_pkg_dir)
|
||||||
registry_configs, selection_max_tools = registry.load_agent_selection(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(
|
async def test_restore_legacy_unphased_assistant_message_preserves_store(
|
||||||
self, tmp_path, runtime, buffer
|
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")
|
store = FileConversationStore(tmp_path / "conv")
|
||||||
await store.write_meta(
|
await store.write_meta(
|
||||||
{
|
{
|
||||||
@@ -864,6 +873,10 @@ class TestCrashRecovery:
|
|||||||
)
|
)
|
||||||
ctx = build_ctx(runtime, spec, buffer, llm, stream_id="queen")
|
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)
|
result = await node.execute(ctx)
|
||||||
|
|
||||||
assert result.success is True
|
assert result.success is True
|
||||||
|
|||||||
Reference in New Issue
Block a user