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:
parent
899cdd0165
commit
8b808e44af
2 changed files with 168 additions and 53 deletions
84
Cargo.lock
generated
84
Cargo.lock
generated
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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!({
|
|
||||||
|
fn socket_path() -> PathBuf {
|
||||||
|
dirs::home_dir()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.join(".consciousness/mcp.sock")
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DaemonClient {
|
||||||
|
reader: BufReader<UnixStream>,
|
||||||
|
writer: BufWriter<UnixStream>,
|
||||||
|
next_id: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
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 request(&mut self, method: &str, params: Option<Value>) -> Result<Value, String> {
|
||||||
|
self.next_id += 1;
|
||||||
|
let req = json!({
|
||||||
"jsonrpc": "2.0",
|
"jsonrpc": "2.0",
|
||||||
|
"id": self.next_id,
|
||||||
"method": method,
|
"method": method,
|
||||||
"params": params,
|
"params": params
|
||||||
})).unwrap();
|
});
|
||||||
let mut stdout = io::stdout().lock();
|
let mut line = serde_json::to_string(&req).map_err(|e| e.to_string())?;
|
||||||
let _ = writeln!(stdout, "{json}");
|
line.push('\n');
|
||||||
let _ = stdout.flush();
|
self.writer.write_all(line.as_bytes()).map_err(|e| e.to_string())?;
|
||||||
|
self.writer.flush().map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let mut buf = String::new();
|
||||||
|
self.reader.read_line(&mut buf).map_err(|e| e.to_string())?;
|
||||||
|
let resp: DaemonResponse = serde_json::from_str(&buf)
|
||||||
|
.map_err(|e| format!("parse response: {}", e))?;
|
||||||
|
|
||||||
|
if let Some(err) = resp.error {
|
||||||
|
return Err(format!("daemon error: {}", err));
|
||||||
}
|
}
|
||||||
|
Ok(resp.result.unwrap_or(Value::Null))
|
||||||
// ── Tool definitions ────────────────────────────────────────────
|
|
||||||
|
|
||||||
fn tool_definitions() -> Vec<Value> {
|
|
||||||
consciousness::agent::tools::tools().into_iter()
|
|
||||||
.map(|t| json!({
|
|
||||||
"name": t.name,
|
|
||||||
"description": t.description,
|
|
||||||
"inputSchema": serde_json::from_str::<Value>(t.parameters_json).unwrap_or(json!({})),
|
|
||||||
}))
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Tool dispatch ───────────────────────────────────────────────
|
|
||||||
|
|
||||||
fn dispatch_tool(name: &str, args: &Value) -> Result<String, String> {
|
|
||||||
let tools = consciousness::agent::tools::tools();
|
|
||||||
let tool = tools.iter().find(|t| t.name == name);
|
|
||||||
let Some(tool) = tool else {
|
|
||||||
return Err(format!("unknown tool: {name}"));
|
|
||||||
};
|
|
||||||
|
|
||||||
// Run async handler on a blocking runtime
|
|
||||||
let rt = tokio::runtime::Builder::new_current_thread()
|
|
||||||
.enable_all()
|
|
||||||
.build()
|
|
||||||
.map_err(|e| e.to_string())?;
|
|
||||||
let local = tokio::task::LocalSet::new();
|
|
||||||
local.block_on(&rt, (tool.handler)(None, args.clone()))
|
|
||||||
.map_err(|e| e.to_string())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 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}],
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue