feat(#2659): serve hive-matrix-mcp over persistent streamable-http, drop stdio bridge
This commit is contained in:
parent
ae8d1aaac4
commit
a66b7ab298
21 changed files with 411 additions and 856 deletions
|
|
@ -1,6 +1,8 @@
|
|||
//! `hive-matrix-daemon` binary — long-running matrix-sdk Client + sync
|
||||
//! loop per matrix account. Bridges incoming room events to hyperhive
|
||||
//! wake signals and serves the unix socket the stdio MCP bridge talks to.
|
||||
//! wake signals and serves its MCP tools directly over streamable-http
|
||||
//! on `--http <addr>` — no stdio bridge, no separate bin claude has to
|
||||
//! respawn every turn.
|
||||
//!
|
||||
//! Lifecycle:
|
||||
//! 1. Read the configured account list (`accounts::configured()` —
|
||||
|
|
@ -8,9 +10,9 @@
|
|||
//! 2. For each account: whoami probe → recover `user_id` + `device_id`
|
||||
//! → restore matrix-sdk session (no login flow), install the
|
||||
//! message-event handler, and spawn its own sync loop.
|
||||
//! 3. Serve the unix socket against an account→Client registry; each
|
||||
//! MCP request routes to the account named in its `account` field
|
||||
//! (the primary account when omitted).
|
||||
//! 3. Serve the MCP tools against an account→Client registry; each tool
|
||||
//! call routes to the account named in its `account` arg (the
|
||||
//! primary account when omitted).
|
||||
//!
|
||||
//! Standalone-degraded boot: the PRIMARY account having no token file →
|
||||
//! exit 0 cleanly so systemd's path-watcher restarts us once hive-c0re
|
||||
|
|
@ -27,19 +29,21 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use clap::Parser;
|
||||
use matrix_sdk::{Client, config::SyncSettings};
|
||||
|
||||
mod accounts;
|
||||
mod client;
|
||||
mod handlers;
|
||||
mod paths;
|
||||
mod protocol;
|
||||
mod socket;
|
||||
mod timeline;
|
||||
mod wake;
|
||||
use hive_matrix_mcp::accounts::{AccountCfg, Registry};
|
||||
use hive_matrix_mcp::client::PermanentBringUpError;
|
||||
use hive_matrix_mcp::{accounts, client, mcp, paths, timeline, wake};
|
||||
|
||||
use accounts::{AccountCfg, Registry};
|
||||
use client::PermanentBringUpError;
|
||||
#[derive(Parser)]
|
||||
#[command(name = "hive-matrix-daemon", about = "matrix-sdk client + MCP daemon")]
|
||||
struct Cli {
|
||||
/// Serve the MCP tools over streamable-http on this address (e.g.
|
||||
/// `127.0.0.1:8792`). Bind loopback only.
|
||||
#[arg(long)]
|
||||
http: std::net::SocketAddr,
|
||||
}
|
||||
|
||||
/// A per-account sync loop, boxed so loops for N accounts can be driven
|
||||
/// concurrently on the main task. Deliberately NOT `Send`: matrix-sdk's
|
||||
|
|
@ -71,8 +75,8 @@ async fn main() -> Result<()> {
|
|||
.with_writer(std::io::stderr)
|
||||
.init();
|
||||
|
||||
let cli = Cli::parse();
|
||||
let cfgs = accounts::configured().context("read matrix account config")?;
|
||||
let mcp_socket = paths::daemon_socket();
|
||||
let multi = cfgs.len() > 1;
|
||||
let primary = cfgs[0].name.clone();
|
||||
let mut registry = Registry::new(primary);
|
||||
|
|
@ -131,9 +135,9 @@ async fn main() -> Result<()> {
|
|||
tracing::warn!(error = %format!("{e:#}"), "failed to write matrix-accounts snapshot");
|
||||
}
|
||||
|
||||
// Serve the socket against the registry. Spawned before driving the
|
||||
// sync loops so the MCP bridge can connect as soon as the first
|
||||
// claude turn fires.
|
||||
// Serve the MCP tools against the registry. Spawned before driving
|
||||
// the sync loops so claude can reach the stable http URL as soon as
|
||||
// the first turn fires.
|
||||
let registry = Arc::new(registry);
|
||||
|
||||
// Heartbeat: periodically rewrite the accounts snapshot so its mtime
|
||||
|
|
@ -155,10 +159,11 @@ async fn main() -> Result<()> {
|
|||
}
|
||||
});
|
||||
|
||||
let socket_listener = mcp_socket.clone();
|
||||
let http_addr = cli.http;
|
||||
let mcp_registry = Arc::clone(®istry);
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = socket::serve(&socket_listener, registry).await {
|
||||
tracing::error!(error = %e, "mcp socket server exited");
|
||||
if let Err(e) = mcp::serve_http(http_addr, mcp_registry).await {
|
||||
tracing::error!(error = %e, "mcp http server exited");
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -298,7 +303,7 @@ async fn bring_up_account(
|
|||
// matrix todos so stale ones (rooms read / invites resolved while the
|
||||
// daemon was down) don't linger, then let the first sweep rebuild the
|
||||
// set to match current reality. Best-effort; the sweep converges.
|
||||
let _ = crate::wake::send_todo_clear(None, true).await;
|
||||
let _ = wake::send_todo_clear(None, true).await;
|
||||
let sync_loop: SyncLoop = Box::pin(async move {
|
||||
sync_client
|
||||
.sync_with_callback(SyncSettings::default(), move |_response| {
|
||||
|
|
|
|||
Loading…
Reference in a new issue