// process-mcp: computer.socialweb.cicd.process.* over a guest agent's MCP shell, until the guest agent // serves the process contract as plain HTTP. Each entry is one MCP tools/call of the same name to `url` // (iroh:////shell/mcp, webrtc:////shell/mcp) through com.fedfork.exec.http.fetch; its // structuredContent is the result. The arguments are the input less url and what the host adds. // exec start read write kill list {url, ...the tool's arguments} -> the tool's result use abi::{Call, Event, Handle, Run, entries}; use serde_json::{Map, Value, json}; const HOST_KEYS: [&str; 4] = ["url", "did", "now", "caller"]; fn tool(name: &str, run: Run>) -> Result { Ok(match run.event { Event::Start => { let url = run.args.get("url").and_then(Value::as_str).ok_or("url is required: the machine's shell (iroh:///... or webrtc:///...)")?; let arguments: Map = run.args.iter().filter(|(k, _)| !HOST_KEYS.contains(&k.as_str())).map(|(k, v)| (k.clone(), v.clone())).collect(); let body = json!({ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": name, "arguments": arguments } }); let headers = json!({ "content-type": "application/json", "accept": "application/json, text/event-stream", "mcp-protocol-version": "2025-11-25" }); Handle::new().call(Call::new("mcp", "com.fedfork.exec.http.fetch").input(json!({ "method": "POST", "url": url, "headers": headers, "body": body.to_string() }))) } Event::Result { result, .. } => { let res = result.output()?; let status = res["status"].as_u64().unwrap_or(0); let text = res["body"].as_str().unwrap_or_default(); if status != 200 { return Err(format!("{name}: HTTP {status} {}", text.chars().take(200).collect::())); } let msg: Value = serde_json::from_str(text).map_err(|_| format!("{name}: the answer is not JSON"))?; if let Some(e) = msg.get("error") { return Err(format!("{name}: {}", e["message"].as_str().unwrap_or("error"))); } let out = &msg["result"]; if out["isError"] == true { return Err(format!("{name}: {}", out["content"][0]["text"].as_str().unwrap_or("error"))); } let value = match out.get("structuredContent") { Some(v) => v.clone(), None => serde_json::from_str(out["content"][0]["text"].as_str().unwrap_or("null")).map_err(|_| format!("{name}: no result"))?, }; Handle::new().result(value).done() } _ => Handle::new(), }) } fn exec_handler(run: Run>) -> Result { tool("exec", run) } fn start_handler(run: Run>) -> Result { tool("start", run) } fn read_handler(run: Run>) -> Result { tool("read", run) } fn write_handler(run: Run>) -> Result { tool("write", run) } fn kill_handler(run: Run>) -> Result { tool("kill", run) } fn list_handler(run: Run>) -> Result { tool("list", run) } entries!(exec => exec_handler, start => start_handler, read => read_handler, write => write_handler, kill => kill_handler, list => list_handler);