feat(archon): MCP acquisition tools (search/enqueue/list/cancel) over a serve-hosted socket#617
Merged
Merged
Conversation
… a serve-hosted socket `harmonia mcp` gained the acquisition surface #609 asks for: an agent with only the MCP connection can search -> enqueue -> observe completion without touching HTTP or the startup banner. The stdio MCP process is bare (no config, DB, or services); the live DynSearchService/DynQueueManager exist only inside `harmonia serve`, and an enqueue/cancel MUST reach the running syntaxis service or the row is invisible until restart (the #499/#469 defect class). So the 4 acquisition tools are not in-process adapters — `harmonia serve` hosts a bridge over a Unix domain socket, and the stdio process forwards `tools/call` to it. Serve side (crates/archon/src/mcp_bridge.rs): right after AppState construction, spawn a UDS listener holding Arc clones of the same search/queue/db AppState got. Framing is newline-delimited JSON-RPC, one request line -> one response line per connection request. Bind is FATAL at startup (mirrors the HTTP bind posture): unlink any stale socket, create the parent dir 0700, chmod the socket 0600 after bind. Shutdown via a shutdown_token child; the socket is unlinked on exit. `#[cfg(unix)]` with a stub error elsewhere. Stdio side (crates/archon/src/mcp.rs): tools/list returns all 8 tools (the 4 offline maintenance tools keep running in-process — db_migrate et al. must work with no server). For the 4 acquisition tools, call_tool connects to the UDS per call under a config timeout and forwards the request. Socket absent / refused -> a clean tool_error ("harmonia server is not running (socket <path> unavailable); start 'harmonia serve'"), never a protocol failure. The tool_success envelope now carries a JSON payload in structuredContent plus a compact text serialization. The 4 tools are thin adapters over the SAME service methods the HTTP routes call, no HTTP re-implementation: - harmonia_search_releases -> DynSearchService::search (redacted results) - harmonia_enqueue_download -> DynSearchService::resolve_release (the #608 by-reference seam) or a magnet arm, then validate_download_url + DynQueueManager::enqueue; exactly one of release_id|magnet - harmonia_list_downloads -> a shared paroche download-row query helper - harmonia_cancel_download -> DynQueueManager::cancel Auth is filesystem: the surface acts as local operator (socket 0600, owner = serve's user). Redaction STAYS ON for MCP output — the client is an LLM whose transcripts leave the box, so paroche::redact strips every outbound download_url; the SSRF guard is retained on both enqueue arms. Config: horismos gains `[mcp]` (socket_path, call_timeout_secs >= zetesis.search_timeout_seconds). The default socket path is derived deterministically as a sibling of database.db_path so BOTH processes agree. `mcp.` is RESTART_REQUIRED (no live-rebind for a local socket). `harmonia mcp` gained --config to resolve the socket path + timeout at startup. Tests: per-tool dispatch tests with local stub services (redaction asserts no SECRET reaches output; both/neither-source, http-in-magnet, private-tracker rejections; release_id resolves to an unredacted queue url + a redacted response); stdio degradation (socket-absent actionable error, offline tools work with no socket, timeout path); and an acceptance test driving search -> enqueue -> poll-list-until-completed over a real UDS bridge + real syntaxis DownloadQueue, entirely via the MCP surface (no HTTP, no exousia, no banner). Closes #609 Gate-Passed: kanon 0.1.6 +stages:fmt,check,clippy,nextest,lint sha:e86adf2cc46b0ec6188b1beb1bd31f809357bd47
938183a to
0b1d459
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #609. An agent can now drive acquisition natively — search → enqueue → observe → cancel — over the
harmonia mcpsurface, no hand-rolled HTTP loop or banner-scraped credentials.harmonia servehosts a Unix-domain-socket listener holding the live search/queue services;harmonia mcpforwards 4 acquisition tools over it (the 4 offline maintenance tools keep running in-process). Both processes derive the same socket path deterministically fromdatabase.db_path. Redaction stays ON for all tool output (LLM transcripts leave the box);enqueue_downloadtakes arelease_id(resolved via #608) or a magnet (http rejected), SSRF-guarded on both arms.Hardened per a 3-lens adversarial review (4 findings, all fixed): the wire read is capped (no unbounded-line OOM); the socket lives in a dedicated
harmonia-mcp/0700 subdir at 0600 (no chmod of a shared parent, and the pre-chmod perms window is closed); a second instance is refused via a liveness check rather than clobbering a live socket; and shutdown drains in-flight tool calls (TaskTracker + grace) instead of dropping them. Gate green (full workspace, incl. the MCP-only acceptance test driving search→enqueue→observe over the bridge).