consciousness-mcp: forward to daemon socket instead of direct calls

Now connects to ~/.consciousness/mcp.sock and forwards tool calls to
the consciousness daemon instead of calling tool handlers directly.

Requires the consciousness daemon to be running with MCP server.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
Kent Overstreet 2026-04-12 21:10:01 -04:00
commit 8b808e44af
2 changed files with 168 additions and 53 deletions

84
Cargo.lock generated
View file

@ -550,12 +550,13 @@ dependencies = [
"redb", "redb",
"regex", "regex",
"rkyv", "rkyv",
"rusqlite",
"rustls", "rustls",
"rustls-native-certs", "rustls-native-certs",
"serde", "serde",
"serde_json", "serde_json",
"serde_urlencoded", "serde_urlencoded",
"skillratings", "textwrap",
"tokenizers", "tokenizers",
"tokio", "tokio",
"tokio-rustls", "tokio-rustls",
@ -985,6 +986,18 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "fallible-iterator"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
[[package]]
name = "fallible-streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]] [[package]]
name = "fancy-regex" name = "fancy-regex"
version = "0.11.0" version = "0.11.0"
@ -1283,6 +1296,15 @@ version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
[[package]]
name = "hashlink"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
dependencies = [
"hashbrown 0.15.5",
]
[[package]] [[package]]
name = "heck" name = "heck"
version = "0.5.0" version = "0.5.0"
@ -1592,6 +1614,17 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "libsqlite3-sys"
version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f"
dependencies = [
"cc",
"pkg-config",
"vcpkg",
]
[[package]] [[package]]
name = "line-clipping" name = "line-clipping"
version = "0.3.7" version = "0.3.7"
@ -2489,6 +2522,20 @@ dependencies = [
"syn 1.0.109", "syn 1.0.109",
] ]
[[package]]
name = "rusqlite"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f"
dependencies = [
"bitflags 2.11.0",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
"libsqlite3-sys",
"smallvec",
]
[[package]] [[package]]
name = "rustc_version" name = "rustc_version"
version = "0.4.1" version = "0.4.1"
@ -2752,12 +2799,6 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
[[package]]
name = "skillratings"
version = "0.28.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a6ee7559737c1adcd9184f168a04dc360c84878907c3ecc5c33c2320be1d47a"
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.12" version = "0.4.12"
@ -2770,6 +2811,12 @@ version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "smawk"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c"
[[package]] [[package]]
name = "socket2" name = "socket2"
version = "0.6.3" version = "0.6.3"
@ -2949,6 +2996,17 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "textwrap"
version = "0.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057"
dependencies = [
"smawk",
"unicode-linebreak",
"unicode-width",
]
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.69" version = "1.0.69"
@ -3469,6 +3527,12 @@ version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unicode-linebreak"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
[[package]] [[package]]
name = "unicode-normalization-alignments" name = "unicode-normalization-alignments"
version = "0.1.12" version = "0.1.12"
@ -3537,6 +3601,12 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.5" version = "0.9.5"

View file

@ -1,13 +1,16 @@
// mcp-server — MCP server for Claude Code integration // mcp-server — MCP server for Claude Code integration
// //
// Speaks JSON-RPC over stdio. Exposes memory tools and channel // Speaks JSON-RPC over stdio (to Claude). Forwards tool calls to the
// operations. Replaces the Python MCP bridge entirely. // consciousness daemon over Unix socket (~/.consciousness/mcp.sock).
// //
// Protocol: https://modelcontextprotocol.io/specification // Protocol: https://modelcontextprotocol.io/specification
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::{json, Value}; use serde_json::{json, Value};
use std::io::{self, BufRead, Write}; use std::io::{self, BufRead, Write};
use std::os::unix::net::UnixStream;
use std::io::{BufReader, BufWriter};
use std::path::PathBuf;
// ── JSON-RPC types ────────────────────────────────────────────── // ── JSON-RPC types ──────────────────────────────────────────────
@ -35,6 +38,16 @@ struct ErrorResponse {
id: Value, id: Value,
} }
#[derive(Deserialize)]
struct DaemonResponse {
#[allow(dead_code)]
jsonrpc: String,
result: Option<Value>,
error: Option<Value>,
#[allow(dead_code)]
id: Value,
}
fn respond(id: Value, result: Value) { fn respond(id: Value, result: Value) {
let resp = Response { jsonrpc: "2.0".into(), result, id }; let resp = Response { jsonrpc: "2.0".into(), result, id };
let json = serde_json::to_string(&resp).unwrap(); let json = serde_json::to_string(&resp).unwrap();
@ -55,52 +68,83 @@ fn respond_error(id: Value, code: i64, message: &str) {
let _ = stdout.flush(); let _ = stdout.flush();
} }
fn notify(method: &str, params: Value) { // ── Daemon connection ───────────────────────────────────────────
let json = serde_json::to_string(&json!({
"jsonrpc": "2.0", fn socket_path() -> PathBuf {
"method": method, dirs::home_dir()
"params": params, .unwrap_or_default()
})).unwrap(); .join(".consciousness/mcp.sock")
let mut stdout = io::stdout().lock();
let _ = writeln!(stdout, "{json}");
let _ = stdout.flush();
} }
// ── Tool definitions ──────────────────────────────────────────── struct DaemonClient {
reader: BufReader<UnixStream>,
fn tool_definitions() -> Vec<Value> { writer: BufWriter<UnixStream>,
consciousness::agent::tools::tools().into_iter() next_id: u64,
.map(|t| json!({
"name": t.name,
"description": t.description,
"inputSchema": serde_json::from_str::<Value>(t.parameters_json).unwrap_or(json!({})),
}))
.collect()
} }
// ── Tool dispatch ─────────────────────────────────────────────── impl DaemonClient {
fn connect() -> Result<Self, String> {
let path = socket_path();
let stream = UnixStream::connect(&path)
.map_err(|e| format!("connect to {:?}: {}", path, e))?;
let reader = BufReader::new(stream.try_clone().map_err(|e| e.to_string())?);
let writer = BufWriter::new(stream);
Ok(Self { reader, writer, next_id: 0 })
}
fn dispatch_tool(name: &str, args: &Value) -> Result<String, String> { fn request(&mut self, method: &str, params: Option<Value>) -> Result<Value, String> {
let tools = consciousness::agent::tools::tools(); self.next_id += 1;
let tool = tools.iter().find(|t| t.name == name); let req = json!({
let Some(tool) = tool else { "jsonrpc": "2.0",
return Err(format!("unknown tool: {name}")); "id": self.next_id,
}; "method": method,
"params": params
});
let mut line = serde_json::to_string(&req).map_err(|e| e.to_string())?;
line.push('\n');
self.writer.write_all(line.as_bytes()).map_err(|e| e.to_string())?;
self.writer.flush().map_err(|e| e.to_string())?;
// Run async handler on a blocking runtime let mut buf = String::new();
let rt = tokio::runtime::Builder::new_current_thread() self.reader.read_line(&mut buf).map_err(|e| e.to_string())?;
.enable_all() let resp: DaemonResponse = serde_json::from_str(&buf)
.build() .map_err(|e| format!("parse response: {}", e))?;
.map_err(|e| e.to_string())?;
let local = tokio::task::LocalSet::new(); if let Some(err) = resp.error {
local.block_on(&rt, (tool.handler)(None, args.clone())) return Err(format!("daemon error: {}", err));
.map_err(|e| e.to_string()) }
Ok(resp.result.unwrap_or(Value::Null))
}
} }
// ── Main loop ─────────────────────────────────────────────────── // ── Main loop ───────────────────────────────────────────────────
fn main() { fn main() {
eprintln!("consciousness-mcp: starting"); eprintln!("consciousness-mcp: starting");
// Connect to daemon
let mut client = match DaemonClient::connect() {
Ok(c) => c,
Err(e) => {
eprintln!("consciousness-mcp: failed to connect to daemon: {}", e);
eprintln!("consciousness-mcp: is consciousness running?");
std::process::exit(1);
}
};
// Initialize with daemon
if let Err(e) = client.request("initialize", Some(json!({
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": {"name": "consciousness-mcp", "version": "0.4.0"}
}))) {
eprintln!("consciousness-mcp: daemon initialize failed: {}", e);
std::process::exit(1);
}
let _ = client.request("notifications/initialized", None);
eprintln!("consciousness-mcp: connected to daemon");
let stdin = io::stdin(); let stdin = io::stdin();
let reader = stdin.lock(); let reader = stdin.lock();
@ -132,7 +176,7 @@ fn main() {
"tools": {} "tools": {}
}, },
"serverInfo": { "serverInfo": {
"name": "consciousness", "name": "consciousness-mcp",
"version": "0.4.0" "version": "0.4.0"
} }
})); }));
@ -143,8 +187,10 @@ fn main() {
} }
"tools/list" => { "tools/list" => {
let tools = tool_definitions(); match client.request("tools/list", None) {
respond(req.id, json!({ "tools": tools })); Ok(result) => respond(req.id, result),
Err(e) => respond_error(req.id, -32000, &e),
}
} }
"tools/call" => { "tools/call" => {
@ -155,12 +201,11 @@ fn main() {
.cloned() .cloned()
.unwrap_or(json!({})); .unwrap_or(json!({}));
match dispatch_tool(name, &args) { match client.request("tools/call", Some(json!({
Ok(text) => { "name": name,
respond(req.id, json!({ "arguments": args
"content": [{"type": "text", "text": text}] }))) {
})); Ok(result) => respond(req.id, result),
}
Err(e) => { Err(e) => {
respond(req.id, json!({ respond(req.id, json!({
"content": [{"type": "text", "text": e}], "content": [{"type": "text", "text": e}],