Initial commit: Vestige v1.0.0 - Cognitive memory MCP server

FSRS-6 spaced repetition, spreading activation, synaptic tagging,
hippocampal indexing, and 130 years of memory research.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Sam Valladares 2026-01-25 01:31:03 -06:00
commit f9c60eb5a7
169 changed files with 97206 additions and 0 deletions

View file

@ -0,0 +1,174 @@
//! MCP Protocol Messages
//!
//! Request and response types for MCP methods.
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
// ============================================================================
// INITIALIZE
// ============================================================================
/// Initialize request from client
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct InitializeRequest {
pub protocol_version: String,
pub capabilities: ClientCapabilities,
pub client_info: ClientInfo,
}
impl Default for InitializeRequest {
fn default() -> Self {
Self {
protocol_version: "2024-11-05".to_string(),
capabilities: ClientCapabilities::default(),
client_info: ClientInfo {
name: "unknown".to_string(),
version: "0.0.0".to_string(),
},
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ClientCapabilities {
#[serde(skip_serializing_if = "Option::is_none")]
pub roots: Option<HashMap<String, Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sampling: Option<HashMap<String, Value>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ClientInfo {
pub name: String,
pub version: String,
}
/// Initialize response to client
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct InitializeResult {
pub protocol_version: String,
pub server_info: ServerInfo,
pub capabilities: ServerCapabilities,
#[serde(skip_serializing_if = "Option::is_none")]
pub instructions: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ServerInfo {
pub name: String,
pub version: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ServerCapabilities {
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<HashMap<String, Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub resources: Option<HashMap<String, Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub prompts: Option<HashMap<String, Value>>,
}
// ============================================================================
// TOOLS
// ============================================================================
/// Tool description for tools/list
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ToolDescription {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub input_schema: Value,
}
/// Result of tools/list
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ListToolsResult {
pub tools: Vec<ToolDescription>,
}
/// Request for tools/call
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CallToolRequest {
pub name: String,
#[serde(default)]
pub arguments: Option<Value>,
}
/// Result of tools/call
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CallToolResult {
pub content: Vec<ToolResultContent>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_error: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ToolResultContent {
#[serde(rename = "type")]
pub content_type: String,
pub text: String,
}
// ============================================================================
// RESOURCES
// ============================================================================
/// Resource description for resources/list
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ResourceDescription {
pub uri: String,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
}
/// Result of resources/list
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ListResourcesResult {
pub resources: Vec<ResourceDescription>,
}
/// Request for resources/read
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ReadResourceRequest {
pub uri: String,
}
/// Result of resources/read
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ReadResourceResult {
pub contents: Vec<ResourceContent>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ResourceContent {
pub uri: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub blob: Option<String>,
}

View file

@ -0,0 +1,7 @@
//! MCP Protocol Implementation
//!
//! JSON-RPC 2.0 over stdio for the Model Context Protocol.
pub mod messages;
pub mod stdio;
pub mod types;

View file

@ -0,0 +1,84 @@
//! stdio Transport for MCP
//!
//! Handles JSON-RPC communication over stdin/stdout.
use std::io::{self, BufRead, BufReader, Write};
use tracing::{debug, error, warn};
use super::types::{JsonRpcError, JsonRpcRequest, JsonRpcResponse};
use crate::server::McpServer;
/// stdio Transport for MCP server
pub struct StdioTransport;
impl StdioTransport {
pub fn new() -> Self {
Self
}
/// Run the MCP server over stdio
pub async fn run(self, mut server: McpServer) -> Result<(), io::Error> {
let stdin = io::stdin();
let stdout = io::stdout();
let reader = BufReader::new(stdin.lock());
let mut stdout = stdout.lock();
for line in reader.lines() {
let line = match line {
Ok(l) => l,
Err(e) => {
error!("Failed to read line: {}", e);
break;
}
};
if line.is_empty() {
continue;
}
debug!("Received: {}", line);
// Parse JSON-RPC request
let request: JsonRpcRequest = match serde_json::from_str(&line) {
Ok(r) => r,
Err(e) => {
warn!("Failed to parse request: {}", e);
let error_response = JsonRpcResponse::error(None, JsonRpcError::parse_error());
match serde_json::to_string(&error_response) {
Ok(response_json) => {
writeln!(stdout, "{}", response_json)?;
stdout.flush()?;
}
Err(e) => {
error!("Failed to serialize error response: {}", e);
}
}
continue;
}
};
// Handle the request
if let Some(response) = server.handle_request(request).await {
match serde_json::to_string(&response) {
Ok(response_json) => {
debug!("Sending: {}", response_json);
writeln!(stdout, "{}", response_json)?;
stdout.flush()?;
}
Err(e) => {
error!("Failed to serialize response: {}", e);
}
}
}
}
Ok(())
}
}
impl Default for StdioTransport {
fn default() -> Self {
Self::new()
}
}

View file

@ -0,0 +1,201 @@
//! MCP JSON-RPC Types
//!
//! Core types for JSON-RPC 2.0 protocol used by MCP.
use serde::{Deserialize, Serialize};
use serde_json::Value;
/// MCP Protocol Version
pub const MCP_VERSION: &str = "2025-11-25";
/// JSON-RPC version
pub const JSONRPC_VERSION: &str = "2.0";
// ============================================================================
// JSON-RPC REQUEST/RESPONSE
// ============================================================================
/// JSON-RPC Request
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcRequest {
pub jsonrpc: String,
pub id: Option<Value>,
pub method: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<Value>,
}
/// JSON-RPC Response
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcResponse {
pub jsonrpc: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<JsonRpcError>,
}
impl JsonRpcResponse {
pub fn success(id: Option<Value>, result: Value) -> Self {
Self {
jsonrpc: JSONRPC_VERSION.to_string(),
id,
result: Some(result),
error: None,
}
}
pub fn error(id: Option<Value>, error: JsonRpcError) -> Self {
Self {
jsonrpc: JSONRPC_VERSION.to_string(),
id,
result: None,
error: Some(error),
}
}
}
// ============================================================================
// JSON-RPC ERROR
// ============================================================================
/// JSON-RPC Error Codes (standard + MCP-specific)
#[derive(Debug, Clone, Copy)]
pub enum ErrorCode {
// Standard JSON-RPC errors
ParseError = -32700,
InvalidRequest = -32600,
MethodNotFound = -32601,
InvalidParams = -32602,
InternalError = -32603,
// MCP-specific errors (-32000 to -32099)
ConnectionClosed = -32000,
RequestTimeout = -32001,
ResourceNotFound = -32002,
ServerNotInitialized = -32003,
}
impl From<ErrorCode> for i32 {
fn from(code: ErrorCode) -> Self {
code as i32
}
}
/// JSON-RPC Error
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcError {
pub code: i32,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Value>,
}
impl JsonRpcError {
fn new(code: ErrorCode, message: &str) -> Self {
Self {
code: code.into(),
message: message.to_string(),
data: None,
}
}
pub fn parse_error() -> Self {
Self::new(ErrorCode::ParseError, "Parse error")
}
pub fn method_not_found() -> Self {
Self::new(ErrorCode::MethodNotFound, "Method not found")
}
pub fn method_not_found_with_message(message: &str) -> Self {
Self::new(ErrorCode::MethodNotFound, message)
}
pub fn invalid_params(message: &str) -> Self {
Self::new(ErrorCode::InvalidParams, message)
}
pub fn internal_error(message: &str) -> Self {
Self::new(ErrorCode::InternalError, message)
}
pub fn server_not_initialized() -> Self {
Self::new(ErrorCode::ServerNotInitialized, "Server not initialized")
}
pub fn resource_not_found(uri: &str) -> Self {
Self::new(ErrorCode::ResourceNotFound, &format!("Resource not found: {}", uri))
}
}
impl std::fmt::Display for JsonRpcError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[{}] {}", self.code, self.message)
}
}
impl std::error::Error for JsonRpcError {}
// ============================================================================
// TESTS
// ============================================================================
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_request_serialization() {
let request = JsonRpcRequest {
jsonrpc: JSONRPC_VERSION.to_string(),
id: Some(Value::Number(1.into())),
method: "test".to_string(),
params: Some(serde_json::json!({"key": "value"})),
};
let json = serde_json::to_string(&request).unwrap();
let parsed: JsonRpcRequest = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.method, "test");
assert!(parsed.id.is_some()); // Has id, not a notification
}
#[test]
fn test_notification() {
let notification = JsonRpcRequest {
jsonrpc: JSONRPC_VERSION.to_string(),
id: None,
method: "notify".to_string(),
params: None,
};
assert!(notification.id.is_none()); // No id = notification
}
#[test]
fn test_response_success() {
let response = JsonRpcResponse::success(
Some(Value::Number(1.into())),
serde_json::json!({"result": "ok"}),
);
assert!(response.result.is_some());
assert!(response.error.is_none());
}
#[test]
fn test_response_error() {
let response = JsonRpcResponse::error(
Some(Value::Number(1.into())),
JsonRpcError::method_not_found(),
);
assert!(response.result.is_none());
assert!(response.error.is_some());
assert_eq!(response.error.unwrap().code, -32601);
}
}