A cross-platform IPC (Inter-Process Communication) library using tonic gRPC over Unix Domain Sockets (Linux/Mac) and Named Pipes (Windows).
- Cross-platform IPC communication
- gRPC-based protocol using tonic
- Bidirectional streaming for real-time communication
- Unix Domain Sockets for Linux/Mac
- Named Pipes for Windows
- Async/await support with tokio
- Type-safe communication with Protocol Buffers
- MCP Elicitation protocol support
Add this to your Cargo.toml:
[dependencies]
libdive-desktop = { path = "." }
tokio = { version = "1.0", features = ["rt-multi-thread", "macros"] }
tonic = "0.12"
tokio-stream = "0.1"use libdive_desktop::proto::ipc_service_server::{IpcService, IpcServiceServer};
use libdive_desktop::proto::{
McpStreamRequest, McpStreamResponse, RequestType, ResponseType,
ElicitationResult, EndResponse, elicitation_result::Action,
};
use tonic::{Request, Response, Status};
use tokio_stream::wrappers::ReceiverStream;
use tokio_stream::StreamExt;
#[derive(Debug, Default)]
pub struct MyIpcService;
#[tonic::async_trait]
impl IpcService for MyIpcService {
type McpStreamStream = ReceiverStream<Result<McpStreamResponse, Status>>;
async fn stream(
&self,
request: Request<tonic::Streaming<McpStreamRequest>>,
) -> Result<Response<Self::StreamStream>, Status> {
let mut in_stream = request.into_inner();
let (tx, rx) = tokio::sync::mpsc::channel(128);
tokio::spawn(async move {
while let Some(Ok(msg)) = in_stream.next().await {
let response = match msg.r#type() {
RequestType::Elicitation => {
// Handle elicitation request
let mut content = std::collections::HashMap::new();
content.insert("username".to_string(), "alice".to_string());
McpStreamResponse {
r#type: ResponseType::Elicitation as i32,
payload: Some(libdive_desktop::proto::mcp_stream_response::Payload::Elicitation(
ElicitationResult {
action: Action::Accept as i32,
content,
}
)),
}
}
RequestType::End => {
McpStreamResponse {
r#type: ResponseType::End as i32,
payload: Some(libdive_desktop::proto::mcp_stream_response::Payload::End(
EndResponse { acknowledged: true }
)),
}
}
_ => continue,
};
if tx.send(Ok(response)).await.is_err() {
break;
}
}
});
Ok(Response::new(ReceiverStream::new(rx)))
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let service = MyIpcService::default();
let incoming = libdive_desktop::serve().await?;
tonic::transport::Server::builder()
.add_service(IpcServiceServer::new(service))
.serve_with_incoming(incoming)
.await?;
Ok(())
}use libdive_desktop::proto::ipc_service_client::IpcServiceClient;
use libdive_desktop::proto::{
McpStreamRequest, RequestType, ResponseType,
ElicitationRequest, RequestedSchema, SchemaProperty, EndRequest,
elicitation_result::Action,
};
use std::collections::HashMap;
use tokio_stream::StreamExt;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let channel = libdive_desktop::connect().await?;
let mut client = IpcServiceClient::new(channel);
let (tx, rx) = tokio::sync::mpsc::channel(128);
let stream = tokio_stream::wrappers::ReceiverStream::new(rx);
let mut response_stream = client.mcp_stream(stream).await?.into_inner();
// Handle responses in background
tokio::spawn(async move {
while let Some(Ok(msg)) = response_stream.next().await {
match msg.r#type() {
ResponseType::Elicitation => println!("Got elicitation response"),
ResponseType::End => break,
_ => {}
}
}
});
// Send elicitation request
let mut properties = HashMap::new();
properties.insert("username".to_string(), SchemaProperty {
r#type: "string".to_string(),
..Default::default()
});
tx.send(McpStreamRequest {
r#type: RequestType::Elicitation as i32,
payload: Some(libdive_desktop::proto::mcp_stream_request::Payload::Elicitation(
ElicitationRequest {
message: "Please provide info".to_string(),
requested_schema: Some(RequestedSchema { properties, required: vec![] }),
}
)),
}).await?;
// Send end request
tx.send(McpStreamRequest {
r#type: RequestType::End as i32,
payload: Some(libdive_desktop::proto::mcp_stream_request::Payload::End(
EndRequest { reason: Some("Done".to_string()) }
)),
}).await?;
Ok(())
}| Request Type | Response Type | Description |
|---|---|---|
ELICITATION |
ELICITATION |
Request user input (MCP standard) |
END |
END |
End the stream |
McpStreamRequest {
type: RequestType,
payload: oneof {
elicitation: ElicitationRequest,
end: EndRequest,
}
}
McpStreamResponse {
type: ResponseType,
payload: oneof {
elicitation: ElicitationResult,
end: EndResponse,
}
}
The elicitation protocol follows the MCP standard:
| Schema Type | Constraints |
|---|---|
string |
format, minLength, maxLength |
number |
minimum, maximum |
integer |
minimum, maximum |
boolean |
- |
| Action | Description |
|---|---|
Accept |
User approved and submitted data |
Decline |
User explicitly declined |
Cancel |
User dismissed without choice |
# Build
cargo build --examples
# Run server
cargo run --example unified_server
# Run client (in another terminal)
cargo run --example unified_client┌─────────────────────────────────────────────────────────┐
│ Your Application │
├─────────────────────────────────────────────────────────┤
│ connect() → Channel │ serve() → Stream │
│ (Client) │ (Server) │
├─────────────────────────────────────────────────────────┤
│ libdive-desktop │
│ • Bidirectional streaming │
│ • Cross-platform transport │
│ • MCP Elicitation protocol │
├─────────────────────────────────────────────────────────┤
│ Unix: UnixStream │ Windows: Named Pipe │
└─────────────────────────────────────────────────────────┘
MIT