The plugin's MCP server: what Claude Code starts once per session (the
plugin's .mcp.json). It starts at once, makes sure the shared daemon is
running, and offers two read-only tools over it. The mod does not go
through here: it talks to the daemon's socket directly.
23async fn daemon(method: Method, path: &str) -> Result<CallToolResult, ErrorData> { 24 let (_, value) = client::call(method, path, None).await.map_err(|why| ErrorData::internal_error(why, None))?; 25 Ok(CallToolResult::success(vec![ContentBlock::json(value)?])) 26} 27 28#[tool_router] 29impl Server { 30 #[tool(description = "The jevhooks daemon's status: whether it can ask Jev (key present, client built), the pinned model, how many events it has decided and observed, the spend ledger's line, and the thresholds that turn Jev's probability into a verdict. Read-only.")] 31 async fn status(&self) -> Result<CallToolResult, ErrorData> { 32 daemon(Method::GET, "/status").await 33 } 34 35 #[tool(description = "The most recent jevhooks decisions and outcomes, oldest first: for each judged Bash command or turn end, what was judged, Jev's probability, the verdict, the time taken and the cost; and for judged commands, whether they then ran, failed or were denied. Read-only.")] 36 async fn recent(&self, Parameters(request): Parameters<RecentRequest>) -> Result<CallToolResult, ErrorData> { 37 daemon(Method::GET, &format!("/recent?count={}", request.count.unwrap_or(20))).await 38 } 39} 40 41#[tool_handler] 42impl ServerHandler for Server { 43 fn get_info(&self) -> ServerConfig { 44 ServerConfig::new(ServerCapabilities::builder().enable_tools().build()) 45 .with_server_info(Implementation::new("jevhooks", env!("CARGO_PKG_VERSION"))) 46 .with_instructions( 47 "jevhooks asks Jev to judge Bash commands before they run and turn ends before they stop. \ 48 status says whether Jev can be asked; recent lists the latest decisions.", 49 ) 50 } 51}
Serves MCP on stdio until Claude Code closes it.
54pub async fn serve() -> Result<(), Box<dyn std::error::Error>> { 55 // Not awaited: the daemon can take seconds to fetch its key, and the 56 // session should not wait on that to list two tools. 57 tokio::spawn(async { 58 if let Err(why) = client::ensure().await { 59 eprintln!("jevhooks: {why}"); 60 } 61 }); 62 Server.serve(rmcp::transport::stdio()).await?.waiting().await?; 63 Ok(()) 64}