fix: mcp tool initialization

This commit is contained in:
Timothy
2026-04-10 16:52:04 -07:00
parent caebbea1aa
commit 97fd45d36a
5 changed files with 72 additions and 15 deletions
+24 -1
View File
@@ -41,7 +41,7 @@ _DEFAULT_CONFIG = {
# 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": {
"hive_tools": {
"description": "Hive tools: web search, email, CRM, calendar, and 100+ integrations",
"args": ["run", "python", "mcp_server.py", "--stdio"],
},
@@ -55,6 +55,13 @@ _DEFAULT_LOCAL_SERVERS: dict[str, dict[str, Any]] = {
},
}
# Aliases that earlier versions of ensure_defaults wrote under the wrong name.
# When we see one of these stale entries, drop it before seeding the canonical
# name so the active agents (queen, credential_tester) can find their tools.
_STALE_DEFAULT_ALIASES: dict[str, str] = {
"hive_tools": "hive-tools",
}
class MCPRegistry:
"""Manages local MCP server state in ~/.hive/mcp_registry/."""
@@ -103,6 +110,22 @@ class MCPRegistry:
existing = data.get("servers", {})
added: list[str] = []
# Drop stale aliases (from earlier versions that wrote the wrong name).
# Only remove the alias when the canonical name isn't already installed,
# so we never clobber a hand-edited entry the user cares about.
mutated = False
for canonical, stale in _STALE_DEFAULT_ALIASES.items():
if stale in existing and canonical not in existing:
logger.info(
"MCPRegistry.ensure_defaults: removing stale alias '%s' (canonical: '%s')",
stale,
canonical,
)
del existing[stale]
mutated = True
if mutated:
self._write_installed(data)
for name, spec in _DEFAULT_LOCAL_SERVERS.items():
if name in existing:
continue
+24
View File
@@ -381,6 +381,13 @@ def register_mcp_commands(subparsers) -> None:
health_p.add_argument("--json", dest="output_json", action="store_true", help="Output as JSON")
health_p.set_defaults(func=cmd_mcp_health)
# ── init ──
init_p = mcp_sub.add_parser(
"init",
help="Initialize the local MCP registry and seed built-in servers",
)
init_p.set_defaults(func=cmd_mcp_init)
# ── update ──
update_p = mcp_sub.add_parser(
"update", help="Update installed servers or refresh the registry index"
@@ -786,6 +793,23 @@ def cmd_mcp_health(args) -> int:
return 0
def cmd_mcp_init(args) -> int:
"""Initialize the local MCP registry and seed built-in local servers."""
registry = _get_registry()
try:
added = registry.ensure_defaults()
except Exception as exc:
print(f"Error: failed to initialize MCP registry: {exc}", file=sys.stderr)
return 1
if added:
for name in added:
print(f"✓ Registered {name}")
else:
print("✓ MCP registry already initialized (no changes)")
return 0
def cmd_mcp_update(args) -> int:
"""Update a single server, or refresh the index and update all registry servers."""
registry = _get_registry()
+13 -14
View File
@@ -827,19 +827,22 @@ class TestCrashRecovery:
assert input_events[0].data["prompt"] == "What city?"
@pytest.mark.asyncio
@pytest.mark.skip(
reason=(
"Test expects single-turn completion semantics that were "
"deliberately removed when the queen became a forever-alive "
"conversational node. A text-only turn now auto-blocks for "
"user input instead of being accepted by the implicit judge. "
"The underlying 'restore preserves legacy store' behavior is "
"still covered by other TestCrashRecovery tests; rewriting "
"this one needs an LLM scenario that emits a tool call so "
"the loop doesn't hit auto-block."
)
)
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.
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.
"""
"""Legacy queen stores without phase_id should resume instead of being cleared."""
store = FileConversationStore(tmp_path / "conv")
await store.write_meta(
{
@@ -873,10 +876,6 @@ 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
+4
View File
@@ -2112,6 +2112,10 @@ if ($LASTEXITCODE -eq 0) { Write-Ok "ok" } else { Write-Warn "skipped" }
Write-Host " $([char]0x2B21) MCP config... " -NoNewline
if (Test-Path (Join-Path $ScriptDir ".mcp.json")) { Write-Ok "ok" } else { Write-Warn "skipped" }
Write-Host " $([char]0x2B21) MCP registry... " -NoNewline
& uv run hive mcp init *> $null
if ($LASTEXITCODE -eq 0) { Write-Ok "ok" } else { Write-Warn "skipped" }
Write-Host " $([char]0x2B21) skills... " -NoNewline
$skillsDir = Join-Path (Join-Path $ScriptDir ".claude") "skills"
if (Test-Path $skillsDir) {
+7
View File
@@ -1963,6 +1963,13 @@ else
echo -e "${YELLOW}--${NC}"
fi
echo -n " ⬡ MCP registry... "
if uv run hive mcp init > /dev/null 2>&1; then
echo -e "${GREEN}ok${NC}"
else
echo -e "${YELLOW}--${NC}"
fi
echo -n " ⬡ credential store... "