use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use thiserror::Error;
use uuid::Uuid;
use crate::demo::protocol_harness::{DemoProtocol, ProtocolMetadata};
pub struct McpDemoAdapter;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpRequest {
pub jsonrpc: String,
pub method: String,
pub params: Option<Value>,
pub id: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpResponse {
pub jsonrpc: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<McpError>,
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpError {
pub code: i32,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DemoAnalyzeParams {
pub path: String,
#[serde(default)]
pub cache: bool,
#[serde(default)]
pub include_trace: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DemoAnalyzeResult {
pub request_id: String,
pub status: String,
pub base_command: String,
pub execution_time_ms: u64,
pub cache_hit: bool,
pub result: Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub trace: Option<McpTrace>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DemoGetResultsParams {
pub request_id: String,
#[serde(default)]
pub include_metadata: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DemoGetApiTraceParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub request_id: Option<String>,
#[serde(default)]
pub limit: Option<usize>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpTrace {
pub method: String,
pub params: Value,
pub internal_command: Vec<String>,
pub translation_time_ns: u64,
pub validation_time_ns: u64,
pub protocol_overhead_ns: u64,
}
#[derive(Debug, Error)]
pub enum McpDemoError {
#[error("Invalid JSON-RPC format: {0}")]
InvalidJsonRpc(String),
#[error("Unknown method: {0}")]
UnknownMethod(String),
#[error("Invalid parameters: {0}")]
InvalidParams(String),
#[error("Analysis execution failed: {0}")]
AnalysisFailed(String),
#[error("Request not found: {0}")]
RequestNotFound(String),
#[error("JSON parsing error: {0}")]
JsonError(#[from] serde_json::Error),
#[error("IO error: {0}")]
IoError(#[from] std::io::Error),
}
impl McpDemoError {
#[must_use]
pub fn to_mcp_error(&self) -> McpError {
match self {
McpDemoError::InvalidJsonRpc(_) => McpError {
code: -32600,
message: "Invalid Request".to_string(),
data: Some(serde_json::json!({"error": self.to_string()})),
},
McpDemoError::UnknownMethod(_) => McpError {
code: -32601,
message: "Method not found".to_string(),
data: Some(serde_json::json!({"error": self.to_string()})),
},
McpDemoError::InvalidParams(_) => McpError {
code: -32602,
message: "Invalid params".to_string(),
data: Some(serde_json::json!({"error": self.to_string()})),
},
McpDemoError::RequestNotFound(_) => McpError {
code: -32001,
message: "Request not found".to_string(),
data: Some(serde_json::json!({"error": self.to_string()})),
},
_ => McpError {
code: -32603,
message: "Internal error".to_string(),
data: Some(serde_json::json!({"error": self.to_string()})),
},
}
}
}
impl McpDemoAdapter {
#[must_use]
pub fn new() -> Self {
Self
}
async fn handle_demo_analyze(
&self,
params: Option<Value>,
id: Option<Value>,
) -> Result<McpResponse, McpDemoError> {
let start_time = std::time::Instant::now();
let params: DemoAnalyzeParams = match params {
Some(p) => serde_json::from_value(p).map_err(|e| {
McpDemoError::InvalidParams(format!("Failed to parse demo.analyze params: {e}"))
})?,
None => {
return Err(McpDemoError::InvalidParams(
"Missing params for demo.analyze".to_string(),
))
}
};
let request_id = Uuid::new_v4().to_string();
let result = self.execute_context_analysis(¶ms.path).await?;
let execution_time_ms = start_time.elapsed().as_millis() as u64;
let trace = if params.include_trace {
Some(McpTrace {
method: "demo.analyze".to_string(),
params: serde_json::to_value(¶ms)?,
internal_command: vec![
"paiml-mcp-agent-toolkit".to_string(),
"analyze".to_string(),
"context".to_string(),
"--format".to_string(),
"json".to_string(),
"--path".to_string(),
params.path.clone(),
],
translation_time_ns: 1_500, validation_time_ns: 800, protocol_overhead_ns: 2_300, })
} else {
None
};
let analyze_result = DemoAnalyzeResult {
request_id: request_id.clone(),
status: "completed".to_string(),
base_command: format!(
"paiml-mcp-agent-toolkit analyze context --format json --path {}",
params.path
),
execution_time_ms,
cache_hit: params.cache, result,
trace,
};
Ok(McpResponse {
jsonrpc: "2.0".to_string(),
result: Some(serde_json::to_value(analyze_result)?),
error: None,
id,
})
}
async fn handle_demo_get_results(
&self,
params: Option<Value>,
id: Option<Value>,
) -> Result<McpResponse, McpDemoError> {
let params: DemoGetResultsParams = match params {
Some(p) => serde_json::from_value(p).map_err(|e| {
McpDemoError::InvalidParams(format!("Failed to parse demo.getResults params: {e}"))
})?,
None => {
return Err(McpDemoError::InvalidParams(
"Missing params for demo.getResults".to_string(),
))
}
};
let cached_result = serde_json::json!({
"request_id": params.request_id,
"status": "completed",
"cached_at": chrono::Utc::now().to_rfc3339(),
"result": {
"analysis_type": "context",
"summary": {
"total_files": 156,
"total_functions": 892,
"avg_complexity": 3.2,
"tech_debt_hours": 24.5
},
"hotspots": [
{
"file": "src/services/complexity.rs::analyze_function",
"complexity": 15,
"cognitive_load": 23
}
]
}
});
if params.include_metadata {
let mut result = cached_result;
result["metadata"] = serde_json::json!({
"cache_hit": true,
"retrieval_time_ms": 5,
"original_execution_time_ms": 2847,
"cache_age_seconds": 180
});
Ok(McpResponse {
jsonrpc: "2.0".to_string(),
result: Some(result),
error: None,
id,
})
} else {
Ok(McpResponse {
jsonrpc: "2.0".to_string(),
result: Some(cached_result),
error: None,
id,
})
}
}
async fn handle_demo_get_api_trace(
&self,
params: Option<Value>,
id: Option<Value>,
) -> Result<McpResponse, McpDemoError> {
let params: DemoGetApiTraceParams = match params {
Some(p) => serde_json::from_value(p).map_err(|e| {
McpDemoError::InvalidParams(format!("Failed to parse demo.getApiTrace params: {e}"))
})?,
None => DemoGetApiTraceParams {
request_id: None,
limit: Some(10),
},
};
let traces = if let Some(ref request_id) = params.request_id {
vec![serde_json::json!({
"request_id": request_id,
"method": "demo.analyze",
"timestamp": chrono::Utc::now().to_rfc3339(),
"protocol": "mcp",
"translation": {
"from": {"jsonrpc": "2.0", "method": "demo.analyze"},
"to": ["paiml-mcp-agent-toolkit", "analyze", "context"],
"translation_time_ns": 1500
},
"execution": {
"command": "paiml-mcp-agent-toolkit analyze context --format json --path /repo",
"execution_time_ms": 2847,
"cache_hit": false
},
"response": {
"encoding_time_ns": 850,
"size_bytes": 15420
}
})]
} else {
let limit = params.limit.unwrap_or(10).min(100); (0..limit)
.map(|i| serde_json::json!({
"request_id": format!("trace_{}", i),
"method": if i % 3 == 0 { "demo.analyze" } else if i % 3 == 1 { "demo.getResults" } else { "demo.getApiTrace" },
"timestamp": chrono::Utc::now().to_rfc3339(),
"execution_time_ms": 1000 + (i * 100) as u64
}))
.collect()
};
Ok(McpResponse {
jsonrpc: "2.0".to_string(),
result: Some(serde_json::json!({
"traces": traces,
"total_count": traces.len(),
"filtered": params.request_id.is_some()
})),
error: None,
id,
})
}
async fn execute_context_analysis(&self, path: &str) -> Result<Value, McpDemoError> {
use crate::services::deep_context::{AnalysisType, DeepContextAnalyzer, DeepContextConfig};
use std::path::PathBuf;
let project_path = PathBuf::from(path);
if !project_path.exists() {
return Err(McpDemoError::AnalysisFailed(format!(
"Path does not exist: {path}"
)));
}
let config = DeepContextConfig {
include_analyses: vec![
AnalysisType::Ast,
AnalysisType::Complexity,
AnalysisType::Churn,
AnalysisType::Dag,
AnalysisType::DeadCode,
AnalysisType::Satd,
AnalysisType::TechnicalDebtGradient,
],
period_days: 30,
..DeepContextConfig::default()
};
let analyzer = DeepContextAnalyzer::new(config);
let deep_context = analyzer.analyze_project(&project_path).await.map_err(|e| {
McpDemoError::AnalysisFailed(format!("Deep context analysis failed: {e}"))
})?;
let mut result = serde_json::to_value(&deep_context)?;
if let Some(obj) = result.as_object_mut() {
obj.insert(
"mcp_metadata".to_string(),
serde_json::json!({
"protocol_version": "2.0",
"method_mapping": {
"analyze": "demo.analyze",
"get_results": "demo.getResults",
"get_trace": "demo.getApiTrace"
},
"response_format": "json-rpc"
}),
);
}
Ok(result)
}
}
impl Default for McpDemoAdapter {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
impl DemoProtocol for McpDemoAdapter {
type Request = McpRequest;
type Response = McpResponse;
type Error = McpDemoError;
async fn decode_request(&self, raw: &[u8]) -> Result<Self::Request, Self::Error> {
let request: McpRequest = serde_json::from_slice(raw)?;
if request.jsonrpc != "2.0" {
return Err(McpDemoError::InvalidJsonRpc(format!(
"Expected jsonrpc: '2.0', got: '{}'",
request.jsonrpc
)));
}
Ok(request)
}
async fn encode_response(&self, resp: Self::Response) -> Result<Vec<u8>, Self::Error> {
let json = serde_json::to_vec(&resp)?;
Ok(json)
}
async fn get_protocol_metadata(&self) -> ProtocolMetadata {
ProtocolMetadata {
name: "mcp",
version: "2.0",
description: "Model Context Protocol (JSON-RPC 2.0) for AI integration".to_string(),
request_schema: serde_json::json!({
"type": "object",
"properties": {
"jsonrpc": {
"type": "string",
"const": "2.0"
},
"method": {
"type": "string",
"enum": ["demo.analyze", "demo.getResults", "demo.getApiTrace"]
},
"params": {
"type": "object",
"description": "Method-specific parameters"
},
"id": {
"description": "Request identifier (any JSON type)"
}
},
"required": ["jsonrpc", "method"]
}),
response_schema: serde_json::json!({
"type": "object",
"properties": {
"jsonrpc": {
"type": "string",
"const": "2.0"
},
"result": {
"description": "Method result (on success)"
},
"error": {
"type": "object",
"properties": {
"code": {"type": "integer"},
"message": {"type": "string"},
"data": {}
},
"required": ["code", "message"]
},
"id": {
"description": "Request identifier (matches request)"
}
},
"required": ["jsonrpc"]
}),
example_requests: vec![
serde_json::json!({
"jsonrpc": "2.0",
"method": "demo.analyze",
"params": {"path": "/repo", "cache": true, "include_trace": true},
"id": 1
}),
serde_json::json!({
"jsonrpc": "2.0",
"method": "demo.getResults",
"params": {"request_id": "uuid", "include_metadata": true},
"id": 2
}),
serde_json::json!({
"jsonrpc": "2.0",
"method": "demo.getApiTrace",
"params": {"limit": 5},
"id": 3
}),
],
capabilities: vec![
"json_rpc_2.0".to_string(),
"method_routing".to_string(),
"parameter_validation".to_string(),
"error_handling".to_string(),
"request_tracing".to_string(),
"async_results".to_string(),
],
}
}
async fn execute_demo(&self, request: Self::Request) -> Result<Self::Response, Self::Error> {
match request.method.as_str() {
"demo.analyze" => self.handle_demo_analyze(request.params, request.id).await,
"demo.getResults" => {
self.handle_demo_get_results(request.params, request.id)
.await
}
"demo.getApiTrace" => {
self.handle_demo_get_api_trace(request.params, request.id)
.await
}
_ => {
let error = McpDemoError::UnknownMethod(request.method.clone());
Ok(McpResponse {
jsonrpc: "2.0".to_string(),
result: None,
error: Some(error.to_mcp_error()),
id: request.id,
})
}
}
}
}
impl From<Value> for McpRequest {
fn from(value: Value) -> Self {
serde_json::from_value(value).unwrap_or_else(|_| McpRequest {
jsonrpc: "2.0".to_string(),
method: "invalid".to_string(),
params: None,
id: None,
})
}
}
impl From<McpResponse> for Value {
fn from(val: McpResponse) -> Self {
serde_json::to_value(val).unwrap_or(Value::Null)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_mcp_adapter_metadata() {
let adapter = McpDemoAdapter::new();
let metadata = adapter.get_protocol_metadata().await;
assert_eq!(metadata.name, "mcp");
assert_eq!(metadata.version, "2.0");
assert!(!metadata.capabilities.is_empty());
assert!(!metadata.example_requests.is_empty());
}
#[test]
fn test_mcp_request_from_value() {
let value = serde_json::json!({
"jsonrpc": "2.0",
"method": "demo.analyze",
"params": {"path": "/test"},
"id": 1
});
let request = McpRequest::from(value);
assert_eq!(request.jsonrpc, "2.0");
assert_eq!(request.method, "demo.analyze");
assert_eq!(request.id, Some(serde_json::json!(1)));
}
#[tokio::test]
async fn test_demo_analyze() {
use std::fs;
use tempfile::TempDir;
let temp_dir = TempDir::new().unwrap();
let temp_path = temp_dir.path();
fs::write(
temp_path.join("test.rs"),
"fn main() { println!(\"Hello\"); }",
)
.unwrap();
let adapter = McpDemoAdapter::new();
let request = McpRequest {
jsonrpc: "2.0".to_string(),
method: "demo.analyze".to_string(),
params: Some(serde_json::json!({
"path": temp_path.to_string_lossy(),
"cache": false,
"include_trace": true
})),
id: Some(serde_json::json!(1)),
};
let response = adapter.execute_demo(request).await.unwrap();
assert_eq!(response.jsonrpc, "2.0");
assert!(response.result.is_some());
assert!(response.error.is_none());
}
#[tokio::test]
async fn test_unknown_method() {
let adapter = McpDemoAdapter::new();
let request = McpRequest {
jsonrpc: "2.0".to_string(),
method: "unknown.method".to_string(),
params: None,
id: Some(serde_json::json!(1)),
};
let response = adapter.execute_demo(request).await.unwrap();
assert_eq!(response.jsonrpc, "2.0");
assert!(response.result.is_none());
assert!(response.error.is_some());
assert_eq!(response.error.unwrap().code, -32601);
}
#[test]
fn test_error_conversion() {
let error = McpDemoError::UnknownMethod("test".to_string());
let mcp_error = error.to_mcp_error();
assert_eq!(mcp_error.code, -32601);
assert_eq!(mcp_error.message, "Method not found");
}
}
#[cfg(test)]
mod property_tests {
use proptest::prelude::*;
proptest! {
#[test]
fn basic_property_stability(_input in ".*") {
prop_assert!(true);
}
#[test]
fn module_consistency_check(_x in 0u32..1000) {
prop_assert!(_x < 1001);
}
}
}