Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'

This commit is contained in:
ruv
2026-02-28 14:39:40 -05:00
7854 changed files with 3522914 additions and 0 deletions

View File

@@ -0,0 +1,309 @@
//! Additional MCP Handlers
//!
//! Extended handler implementations for specialized edge-net capabilities.
use super::protocol::*;
use serde_json::{json, Value};
/// Vector search handler parameters
pub struct VectorSearchParams {
pub query: Vec<f32>,
pub k: usize,
pub filter: Option<Value>,
}
/// Embedding generation parameters
pub struct EmbeddingParams {
pub text: String,
pub model: Option<String>,
}
/// Semantic match parameters
pub struct SemanticMatchParams {
pub text: String,
pub categories: Vec<String>,
}
/// Task execution result
#[derive(Debug, Clone)]
pub struct TaskResult {
pub task_id: String,
pub status: TaskStatus,
pub result: Option<Value>,
pub error: Option<String>,
pub cost: u64,
}
/// Task status
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TaskStatus {
Queued,
Running,
Completed,
Failed,
Cancelled,
}
impl TaskStatus {
pub fn as_str(&self) -> &'static str {
match self {
TaskStatus::Queued => "queued",
TaskStatus::Running => "running",
TaskStatus::Completed => "completed",
TaskStatus::Failed => "failed",
TaskStatus::Cancelled => "cancelled",
}
}
}
/// Handler for vector operations
pub struct VectorHandler;
impl VectorHandler {
/// Create vector search response
pub fn search_response(id: Option<Value>, results: Vec<(String, f32)>) -> McpResponse {
let result_list: Vec<Value> = results
.into_iter()
.map(|(id, score)| json!({ "id": id, "score": score }))
.collect();
McpResponse::success(id, json!({
"content": [{
"type": "text",
"text": format!("Found {} results", result_list.len())
}],
"results": result_list
}))
}
/// Create embedding response
pub fn embedding_response(id: Option<Value>, embedding: Vec<f32>) -> McpResponse {
McpResponse::success(id, json!({
"content": [{
"type": "text",
"text": format!("Generated {}-dimensional embedding", embedding.len())
}],
"embedding": embedding,
"dimensions": embedding.len()
}))
}
}
/// Handler for RAC coherence operations
pub struct CoherenceHandler;
impl CoherenceHandler {
/// Create conflict detection response
pub fn conflict_response(
id: Option<Value>,
conflicts: Vec<(String, String, f32)>,
) -> McpResponse {
let conflict_list: Vec<Value> = conflicts
.into_iter()
.map(|(id1, id2, severity)| {
json!({
"claim1": id1,
"claim2": id2,
"severity": severity
})
})
.collect();
McpResponse::success(id, json!({
"content": [{
"type": "text",
"text": format!("Detected {} conflicts", conflict_list.len())
}],
"conflicts": conflict_list
}))
}
/// Create resolution response
pub fn resolution_response(
id: Option<Value>,
resolution_id: &str,
accepted: Vec<String>,
deprecated: Vec<String>,
) -> McpResponse {
McpResponse::success(id, json!({
"content": [{
"type": "text",
"text": format!(
"Resolution {}: accepted {}, deprecated {}",
resolution_id,
accepted.len(),
deprecated.len()
)
}],
"resolutionId": resolution_id,
"accepted": accepted,
"deprecated": deprecated
}))
}
}
/// Handler for economic operations
pub struct EconomicsHandler;
impl EconomicsHandler {
/// Create stake response
pub fn stake_response(
id: Option<Value>,
staked: u64,
locked_until: u64,
multiplier: f32,
) -> McpResponse {
McpResponse::success(id, json!({
"content": [{
"type": "text",
"text": format!(
"Staked {} rUv ({}x multiplier, locked until {})",
staked, multiplier, locked_until
)
}],
"staked": staked,
"lockedUntil": locked_until,
"multiplier": multiplier
}))
}
/// Create reward distribution response
pub fn reward_response(
id: Option<Value>,
recipients: Vec<(String, u64)>,
total: u64,
) -> McpResponse {
let recipient_list: Vec<Value> = recipients
.into_iter()
.map(|(node, amount)| json!({ "nodeId": node, "amount": amount }))
.collect();
McpResponse::success(id, json!({
"content": [{
"type": "text",
"text": format!("Distributed {} rUv to {} recipients", total, recipient_list.len())
}],
"recipients": recipient_list,
"totalDistributed": total
}))
}
}
/// Handler for network operations
pub struct NetworkHandler;
impl NetworkHandler {
/// Create peer list response
pub fn peers_response(id: Option<Value>, peers: Vec<PeerInfo>) -> McpResponse {
let peer_list: Vec<Value> = peers
.into_iter()
.map(|p| {
json!({
"nodeId": p.node_id,
"publicKey": p.public_key,
"reputation": p.reputation,
"latency": p.latency_ms,
"connected": p.connected
})
})
.collect();
McpResponse::success(id, json!({
"content": [{
"type": "text",
"text": format!("{} peers connected", peer_list.len())
}],
"peers": peer_list,
"count": peer_list.len()
}))
}
/// Create network health response
pub fn health_response(id: Option<Value>, health: NetworkHealth) -> McpResponse {
McpResponse::success(id, json!({
"content": [{
"type": "text",
"text": format!(
"Network Health: {}% (peers: {}, avg latency: {}ms)",
(health.score * 100.0) as u32,
health.peer_count,
health.avg_latency_ms
)
}],
"score": health.score,
"peerCount": health.peer_count,
"avgLatency": health.avg_latency_ms,
"messageRate": health.message_rate,
"bandwidth": health.bandwidth_kbps
}))
}
}
/// Peer information
pub struct PeerInfo {
pub node_id: String,
pub public_key: String,
pub reputation: f32,
pub latency_ms: u32,
pub connected: bool,
}
/// Network health metrics
pub struct NetworkHealth {
pub score: f32,
pub peer_count: usize,
pub avg_latency_ms: u32,
pub message_rate: f32,
pub bandwidth_kbps: u32,
}
/// Helper for creating error responses
pub fn error_response(id: Option<Value>, code: i32, message: &str) -> McpResponse {
McpResponse::error(id, McpError::new(code, message))
}
/// Helper for creating not implemented responses
pub fn not_implemented(id: Option<Value>, feature: &str) -> McpResponse {
McpResponse::success(id, json!({
"content": [{
"type": "text",
"text": format!("{} is not yet implemented", feature)
}],
"status": "not_implemented",
"feature": feature
}))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_vector_search_response() {
let results = vec![
("doc1".to_string(), 0.95),
("doc2".to_string(), 0.87),
];
let response = VectorHandler::search_response(Some(json!(1)), results);
assert!(response.result.is_some());
}
#[test]
fn test_task_status() {
assert_eq!(TaskStatus::Completed.as_str(), "completed");
assert_eq!(TaskStatus::Running.as_str(), "running");
}
#[test]
fn test_network_health_response() {
let health = NetworkHealth {
score: 0.85,
peer_count: 10,
avg_latency_ms: 50,
message_rate: 100.0,
bandwidth_kbps: 1000,
};
let response = NetworkHandler::health_response(Some(json!(1)), health);
assert!(response.result.is_some());
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,207 @@
//! MCP Protocol Types
//!
//! JSON-RPC 2.0 based protocol types for Model Context Protocol.
use serde::{Deserialize, Serialize};
use serde_json::Value;
/// MCP Request message (JSON-RPC 2.0)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpRequest {
/// JSON-RPC version (always "2.0")
pub jsonrpc: String,
/// Request ID (can be null for notifications)
pub id: Option<Value>,
/// Method name
pub method: String,
/// Optional parameters
pub params: Option<Value>,
}
/// MCP Response message
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpResponse {
/// JSON-RPC version
pub jsonrpc: String,
/// Request ID (matches request)
pub id: Option<Value>,
/// Result (on success)
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<Value>,
/// Error (on failure)
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<McpError>,
}
impl McpResponse {
/// Create a success response
pub fn success(id: Option<Value>, result: Value) -> Self {
Self {
jsonrpc: "2.0".to_string(),
id,
result: Some(result),
error: None,
}
}
/// Create an error response
pub fn error(id: Option<Value>, error: McpError) -> Self {
Self {
jsonrpc: "2.0".to_string(),
id,
result: None,
error: Some(error),
}
}
}
/// MCP Error
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpError {
/// Error code (JSON-RPC standard codes)
pub code: i32,
/// Human-readable message
pub message: String,
/// Optional additional data
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Value>,
}
impl McpError {
/// Create a new error
pub fn new(code: i32, message: impl Into<String>) -> Self {
Self {
code,
message: message.into(),
data: None,
}
}
/// Add additional data to error
pub fn with_data(mut self, data: Value) -> Self {
self.data = Some(data);
self
}
}
/// Standard JSON-RPC error codes
pub struct ErrorCodes;
impl ErrorCodes {
/// Parse error - Invalid JSON
pub const PARSE_ERROR: i32 = -32700;
/// Invalid request - Not a valid Request object
pub const INVALID_REQUEST: i32 = -32600;
/// Method not found
pub const METHOD_NOT_FOUND: i32 = -32601;
/// Invalid params
pub const INVALID_PARAMS: i32 = -32602;
/// Internal error
pub const INTERNAL_ERROR: i32 = -32603;
}
/// MCP Tool definition
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpTool {
/// Tool name (unique identifier)
pub name: String,
/// Human-readable description
pub description: String,
/// JSON Schema for input parameters
#[serde(rename = "inputSchema")]
pub input_schema: Value,
}
/// MCP Resource definition
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpResource {
/// Resource URI
pub uri: String,
/// Human-readable name
pub name: String,
/// Description
pub description: String,
/// MIME type
#[serde(rename = "mimeType")]
pub mime_type: String,
}
/// MCP Prompt definition
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpPrompt {
/// Prompt name
pub name: String,
/// Description
pub description: String,
/// Optional arguments
pub arguments: Option<Vec<PromptArgument>>,
}
/// Prompt argument definition
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PromptArgument {
/// Argument name
pub name: String,
/// Description
pub description: String,
/// Whether argument is required
pub required: bool,
}
/// MCP Notification (no response expected)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpNotification {
/// JSON-RPC version
pub jsonrpc: String,
/// Method name
pub method: String,
/// Optional parameters
pub params: Option<Value>,
}
impl McpNotification {
/// Create a new notification
pub fn new(method: impl Into<String>, params: Option<Value>) -> Self {
Self {
jsonrpc: "2.0".to_string(),
method: method.into(),
params,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_request_serialization() {
let req = McpRequest {
jsonrpc: "2.0".to_string(),
id: Some(json!(1)),
method: "tools/list".to_string(),
params: None,
};
let json = serde_json::to_string(&req).unwrap();
assert!(json.contains("tools/list"));
}
#[test]
fn test_response_success() {
let resp = McpResponse::success(Some(json!(1)), json!({"status": "ok"}));
assert!(resp.error.is_none());
assert!(resp.result.is_some());
}
#[test]
fn test_response_error() {
let resp = McpResponse::error(
Some(json!(1)),
McpError::new(ErrorCodes::METHOD_NOT_FOUND, "Not found"),
);
assert!(resp.error.is_some());
assert!(resp.result.is_none());
}
}

View File

@@ -0,0 +1,353 @@
//! Browser-based MCP Transport Layer
//!
//! Provides MessagePort and BroadcastChannel transports for browser environments.
//!
//! ## Usage
//!
//! ```javascript
//! // Main thread
//! const worker = new Worker('edge-net-worker.js');
//! const transport = new WasmMcpTransport(worker);
//!
//! // Send request
//! const response = await transport.send({
//! method: "tools/call",
//! params: { name: "credits_balance", arguments: { node_id: "..." } }
//! });
//!
//! // Worker thread
//! import { WasmMcpServer, WasmMcpWorkerHandler } from '@ruvector/edge-net';
//!
//! const server = new WasmMcpServer();
//! const handler = new WasmMcpWorkerHandler(server, self);
//! handler.start();
//! ```
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::{MessageEvent, MessagePort, Worker, BroadcastChannel};
use serde_json::json;
use std::sync::Arc;
use parking_lot::RwLock;
use std::collections::HashMap;
use super::{McpRequest, McpResponse, WasmMcpServer};
/// Pending request tracker
struct PendingRequest {
resolve: js_sys::Function,
reject: js_sys::Function,
}
/// Browser-based MCP transport using MessagePort
#[wasm_bindgen]
pub struct WasmMcpTransport {
/// Message port for communication
port: MessagePort,
/// Pending requests awaiting responses
pending: Arc<RwLock<HashMap<String, PendingRequest>>>,
/// Request ID counter
next_id: Arc<RwLock<u64>>,
}
#[wasm_bindgen]
impl WasmMcpTransport {
/// Create transport from a Worker
#[wasm_bindgen(constructor)]
pub fn new(worker: &Worker) -> Result<WasmMcpTransport, JsValue> {
// Create a message channel
let channel = web_sys::MessageChannel::new()?;
let port1 = channel.port1();
let port2 = channel.port2();
// Send port2 to worker
let transfer = js_sys::Array::new();
transfer.push(&port2);
worker.post_message_with_transfer(&port2, &transfer)?;
Ok(Self {
port: port1,
pending: Arc::new(RwLock::new(HashMap::new())),
next_id: Arc::new(RwLock::new(0)),
})
}
/// Create transport from existing MessagePort
#[wasm_bindgen(js_name = fromPort)]
pub fn from_port(port: MessagePort) -> WasmMcpTransport {
Self {
port,
pending: Arc::new(RwLock::new(HashMap::new())),
next_id: Arc::new(RwLock::new(0)),
}
}
/// Initialize transport (set up message handler)
#[wasm_bindgen]
pub fn init(&self) -> Result<(), JsValue> {
let pending = self.pending.clone();
// Create message handler closure
let handler = Closure::wrap(Box::new(move |event: MessageEvent| {
let data = event.data();
// Parse response
if let Ok(json_str) = data.dyn_into::<js_sys::JsString>() {
let json: String = json_str.into();
if let Ok(response) = serde_json::from_str::<McpResponse>(&json) {
// Find pending request
if let Some(id) = &response.id {
let id_str = id.to_string();
let mut pending = pending.write();
if let Some(req) = pending.remove(&id_str) {
let response_js = JsValue::from_str(&json);
if response.error.is_some() {
let _ = req.reject.call1(&JsValue::NULL, &response_js);
} else {
let _ = req.resolve.call1(&JsValue::NULL, &response_js);
}
}
}
}
}
}) as Box<dyn FnMut(MessageEvent)>);
self.port.set_onmessage(Some(handler.as_ref().unchecked_ref()));
handler.forget(); // Don't drop the closure
self.port.start();
Ok(())
}
/// Send an MCP request and get a Promise for the response
#[wasm_bindgen]
pub fn send(&self, request: JsValue) -> js_sys::Promise {
let port = self.port.clone();
let pending = self.pending.clone();
let next_id = self.next_id.clone();
js_sys::Promise::new(&mut move |resolve, reject| {
// Generate request ID
let id = {
let mut counter = next_id.write();
*counter += 1;
*counter
};
// Parse and augment request
let request: Result<McpRequest, _> = serde_wasm_bindgen::from_value(request.clone());
let mut req = match request {
Ok(r) => r,
Err(e) => {
let _ = reject.call1(&JsValue::NULL, &JsValue::from_str(&e.to_string()));
return;
}
};
// Set request ID
req.id = Some(json!(id));
req.jsonrpc = "2.0".to_string();
// Store pending request
{
let mut pending = pending.write();
pending.insert(id.to_string(), PendingRequest {
resolve: resolve.clone(),
reject: reject.clone(),
});
}
// Send request
let json = serde_json::to_string(&req).unwrap();
if let Err(e) = port.post_message(&JsValue::from_str(&json)) {
let _ = reject.call1(&JsValue::NULL, &e);
}
})
}
/// Close the transport
#[wasm_bindgen]
pub fn close(&self) {
self.port.close();
}
}
/// Worker-side handler for MCP requests
#[wasm_bindgen]
pub struct WasmMcpWorkerHandler {
server: WasmMcpServer,
port: Option<MessagePort>,
}
#[wasm_bindgen]
impl WasmMcpWorkerHandler {
/// Create handler with MCP server
#[wasm_bindgen(constructor)]
pub fn new(server: WasmMcpServer) -> WasmMcpWorkerHandler {
Self {
server,
port: None,
}
}
/// Start handling messages (call in worker)
#[wasm_bindgen]
pub fn start(&mut self) -> Result<(), JsValue> {
let server = std::mem::replace(&mut self.server, WasmMcpServer::new()?);
let server = Arc::new(server);
// In worker context, listen for port from main thread
let global = js_sys::global();
let onmessage = Closure::wrap(Box::new(move |event: MessageEvent| {
let data = event.data();
// Check if this is a MessagePort being transferred
if let Ok(port) = data.dyn_into::<MessagePort>() {
// Set up handler for this port
let server_for_handler = Arc::clone(&server);
let port_for_response = port.clone();
let handler = Closure::wrap(Box::new(move |event: MessageEvent| {
let data = event.data();
if let Ok(json_str) = data.dyn_into::<js_sys::JsString>() {
let json: String = json_str.into();
let server_for_async = Arc::clone(&server_for_handler);
let port_clone = port_for_response.clone();
// Parse and handle request
wasm_bindgen_futures::spawn_local(async move {
if let Ok(response) = server_for_async.handle_request(&json).await {
// Send response back
let _ = port_clone.post_message(&JsValue::from_str(&response));
}
});
}
}) as Box<dyn FnMut(MessageEvent)>);
port.set_onmessage(Some(handler.as_ref().unchecked_ref()));
handler.forget();
port.start();
}
}) as Box<dyn FnMut(MessageEvent)>);
// Set global onmessage
js_sys::Reflect::set(
&global,
&JsValue::from_str("onmessage"),
onmessage.as_ref(),
)?;
onmessage.forget();
Ok(())
}
}
impl Clone for WasmMcpServer {
fn clone(&self) -> Self {
// Create a new server with shared state
// NOTE: Identity is not cloned (contains private key)
// NOTE: Learning engine is not cloned (state is complex)
Self {
identity: None,
ledger: self.ledger.clone(),
coherence: self.coherence.clone(),
learning: None,
config: self.config.clone(),
request_counter: self.request_counter.clone(),
rate_limit: self.rate_limit.clone(), // Share rate limit state
}
}
}
/// BroadcastChannel-based transport for multi-tab communication
#[wasm_bindgen]
pub struct WasmMcpBroadcast {
channel: BroadcastChannel,
server: Option<WasmMcpServer>,
}
#[wasm_bindgen]
impl WasmMcpBroadcast {
/// Create a broadcast transport
#[wasm_bindgen(constructor)]
pub fn new(channel_name: &str) -> Result<WasmMcpBroadcast, JsValue> {
let channel = BroadcastChannel::new(channel_name)?;
Ok(Self {
channel,
server: None,
})
}
/// Set as server mode (responds to requests)
#[wasm_bindgen(js_name = setServer)]
pub fn set_server(&mut self, server: WasmMcpServer) {
self.server = Some(server);
}
/// Start listening for requests (server mode)
#[wasm_bindgen]
pub fn listen(&self) -> Result<(), JsValue> {
if self.server.is_none() {
return Err(JsValue::from_str("No server set"));
}
let channel = self.channel.clone();
let server = self.server.as_ref().unwrap().clone();
let handler = Closure::wrap(Box::new(move |event: MessageEvent| {
let data = event.data();
if let Ok(json_str) = data.dyn_into::<js_sys::JsString>() {
let json: String = json_str.into();
let channel_clone = channel.clone();
let server_clone = server.clone();
wasm_bindgen_futures::spawn_local(async move {
if let Ok(response) = server_clone.handle_request(&json).await {
let _ = channel_clone.post_message(&JsValue::from_str(&response));
}
});
}
}) as Box<dyn FnMut(MessageEvent)>);
self.channel.set_onmessage(Some(handler.as_ref().unchecked_ref()));
handler.forget();
Ok(())
}
/// Send a request (client mode)
#[wasm_bindgen]
pub fn send(&self, request_json: &str) -> Result<(), JsValue> {
self.channel.post_message(&JsValue::from_str(request_json))
}
/// Close the channel
#[wasm_bindgen]
pub fn close(&self) {
self.channel.close();
}
}
#[cfg(test)]
mod tests {
use super::*;
// Transport tests require browser environment
#[cfg(target_arch = "wasm32")]
mod wasm_tests {
use super::*;
use wasm_bindgen_test::*;
wasm_bindgen_test_configure!(run_in_browser);
#[wasm_bindgen_test]
fn test_broadcast_creation() {
let broadcast = WasmMcpBroadcast::new("test-channel").unwrap();
broadcast.close();
}
}
}