Some checks failed
Documentation Lint & Validation / Markdown Linting (push) Has been cancelled
Documentation Lint & Validation / Validate mdBook Configuration (push) Has been cancelled
Documentation Lint & Validation / Content & Structure Validation (push) Has been cancelled
mdBook Build & Deploy / Build mdBook (push) Has been cancelled
Rust CI / Security Audit (push) Has been cancelled
Rust CI / Check + Test + Lint (nightly) (push) Has been cancelled
Rust CI / Check + Test + Lint (stable) (push) Has been cancelled
Documentation Lint & Validation / Lint & Validation Summary (push) Has been cancelled
mdBook Build & Deploy / Documentation Quality Check (push) Has been cancelled
mdBook Build & Deploy / Deploy to GitHub Pages (push) Has been cancelled
mdBook Build & Deploy / Notification (push) Has been cancelled
248 lines
8.9 KiB
Rust
248 lines
8.9 KiB
Rust
use std::sync::Arc;
|
|
|
|
use serde_json::json;
|
|
|
|
use crate::{
|
|
backend_client::BackendClient,
|
|
error::{McpError, Result},
|
|
protocol::{JsonRpcError, JsonRpcRequest, JsonRpcResponse, ToolCallParams},
|
|
registry::ToolRegistry,
|
|
};
|
|
|
|
pub struct RequestHandler {
|
|
backend: Arc<BackendClient>,
|
|
registry: Arc<ToolRegistry>,
|
|
}
|
|
|
|
impl RequestHandler {
|
|
pub fn new(backend: Arc<BackendClient>, registry: Arc<ToolRegistry>) -> Self {
|
|
Self { backend, registry }
|
|
}
|
|
|
|
pub async fn handle(&self, request: &JsonRpcRequest) -> JsonRpcResponse {
|
|
if request.jsonrpc != "2.0" {
|
|
return JsonRpcResponse {
|
|
jsonrpc: "2.0".to_string(),
|
|
id: request.id.clone(),
|
|
result: None,
|
|
error: Some(JsonRpcError {
|
|
code: -32600,
|
|
message: "Invalid Request: jsonrpc must be 2.0".to_string(),
|
|
data: None,
|
|
}),
|
|
};
|
|
}
|
|
|
|
let result = match request.method.as_str() {
|
|
"initialize" => self.handle_initialize(request).await,
|
|
"tools/list" => self.handle_tools_list(request).await,
|
|
"tools/call" => self.handle_tools_call(request).await,
|
|
"resources/list" => self.handle_resources_list(request).await,
|
|
"prompts/list" => self.handle_prompts_list(request).await,
|
|
method => Err(McpError::MethodNotFound(method.to_string())),
|
|
};
|
|
|
|
match result {
|
|
Ok(result_value) => JsonRpcResponse {
|
|
jsonrpc: "2.0".to_string(),
|
|
id: request.id.clone(),
|
|
result: Some(result_value),
|
|
error: None,
|
|
},
|
|
Err(e) => {
|
|
let error_obj = e.to_json_rpc_error();
|
|
JsonRpcResponse {
|
|
jsonrpc: "2.0".to_string(),
|
|
id: request.id.clone(),
|
|
result: None,
|
|
error: Some(JsonRpcError {
|
|
code: error_obj["error"]["code"].as_i64().unwrap_or(-32603) as i32,
|
|
message: error_obj["error"]["message"]
|
|
.as_str()
|
|
.unwrap_or("Unknown error")
|
|
.to_string(),
|
|
data: None,
|
|
}),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn handle_initialize(&self, _request: &JsonRpcRequest) -> Result<serde_json::Value> {
|
|
Ok(json!({
|
|
"protocolVersion": "2024-11-05",
|
|
"capabilities": {
|
|
"tools": { "list_changed": false },
|
|
"resources": { "subscribe": false, "list_changed": false },
|
|
"prompts": { "list_changed": false }
|
|
},
|
|
"serverInfo": {
|
|
"name": "vapora-mcp-server",
|
|
"version": env!("CARGO_PKG_VERSION"),
|
|
}
|
|
}))
|
|
}
|
|
|
|
async fn handle_tools_list(&self, _request: &JsonRpcRequest) -> Result<serde_json::Value> {
|
|
let tools: Vec<_> = self
|
|
.registry
|
|
.list()
|
|
.into_iter()
|
|
.map(|t| {
|
|
json!({
|
|
"name": t.name,
|
|
"description": t.description,
|
|
"inputSchema": {
|
|
"type": t.input_schema.schema_type,
|
|
"properties": t.input_schema.properties,
|
|
"required": t.input_schema.required,
|
|
}
|
|
})
|
|
})
|
|
.collect();
|
|
|
|
Ok(json!({ "tools": tools }))
|
|
}
|
|
|
|
async fn handle_tools_call(&self, request: &JsonRpcRequest) -> Result<serde_json::Value> {
|
|
let params: ToolCallParams =
|
|
serde_json::from_value(request.params.clone()).map_err(|e| {
|
|
McpError::InvalidParams {
|
|
method: "tools/call".to_string(),
|
|
reason: e.to_string(),
|
|
}
|
|
})?;
|
|
|
|
let result = match params.name.as_str() {
|
|
"kanban_create_task" => {
|
|
let project_id = params
|
|
.arguments
|
|
.get("project_id")
|
|
.and_then(|v| v.as_str())
|
|
.ok_or(McpError::InvalidParams {
|
|
method: "kanban_create_task".to_string(),
|
|
reason: "Missing project_id".to_string(),
|
|
})?;
|
|
|
|
let title = params
|
|
.arguments
|
|
.get("title")
|
|
.and_then(|v| v.as_str())
|
|
.ok_or(McpError::InvalidParams {
|
|
method: "kanban_create_task".to_string(),
|
|
reason: "Missing title".to_string(),
|
|
})?;
|
|
|
|
self.backend
|
|
.create_task(project_id, title.to_string())
|
|
.await?
|
|
}
|
|
"kanban_update_task" => {
|
|
let task_id = params
|
|
.arguments
|
|
.get("task_id")
|
|
.and_then(|v| v.as_str())
|
|
.ok_or(McpError::InvalidParams {
|
|
method: "kanban_update_task".to_string(),
|
|
reason: "Missing task_id".to_string(),
|
|
})?;
|
|
|
|
let status = params
|
|
.arguments
|
|
.get("status")
|
|
.and_then(|v| v.as_str())
|
|
.ok_or(McpError::InvalidParams {
|
|
method: "kanban_update_task".to_string(),
|
|
reason: "Missing status".to_string(),
|
|
})?;
|
|
|
|
self.backend.update_task(task_id, status).await?
|
|
}
|
|
"get_project_summary" => {
|
|
let project_id = params
|
|
.arguments
|
|
.get("project_id")
|
|
.and_then(|v| v.as_str())
|
|
.ok_or(McpError::InvalidParams {
|
|
method: "get_project_summary".to_string(),
|
|
reason: "Missing project_id".to_string(),
|
|
})?;
|
|
|
|
self.backend.get_project_summary(project_id).await?
|
|
}
|
|
"list_agents" => self.backend.list_agents().await?,
|
|
"get_agent_capabilities" => {
|
|
let agent_id = params
|
|
.arguments
|
|
.get("agent_id")
|
|
.and_then(|v| v.as_str())
|
|
.ok_or(McpError::InvalidParams {
|
|
method: "get_agent_capabilities".to_string(),
|
|
reason: "Missing agent_id".to_string(),
|
|
})?;
|
|
|
|
self.backend.get_agent_capabilities(agent_id).await?
|
|
}
|
|
"assign_task_to_agent" => {
|
|
let role = params
|
|
.arguments
|
|
.get("role")
|
|
.and_then(|v| v.as_str())
|
|
.ok_or(McpError::InvalidParams {
|
|
method: "assign_task_to_agent".to_string(),
|
|
reason: "Missing role".to_string(),
|
|
})?;
|
|
|
|
let title = params
|
|
.arguments
|
|
.get("task_title")
|
|
.and_then(|v| v.as_str())
|
|
.ok_or(McpError::InvalidParams {
|
|
method: "assign_task_to_agent".to_string(),
|
|
reason: "Missing task_title".to_string(),
|
|
})?;
|
|
|
|
let description = params
|
|
.arguments
|
|
.get("task_description")
|
|
.and_then(|v| v.as_str())
|
|
.unwrap_or("");
|
|
|
|
self.backend
|
|
.assign_task_to_agent(role, title.to_string(), description.to_string())
|
|
.await?
|
|
}
|
|
tool_name => {
|
|
return Err(McpError::MethodNotFound(format!("Tool: {}", tool_name)));
|
|
}
|
|
};
|
|
|
|
Ok(json!({
|
|
"content": [{
|
|
"type": "text",
|
|
"text": serde_json::to_string(&result)?
|
|
}],
|
|
"isError": false
|
|
}))
|
|
}
|
|
|
|
async fn handle_resources_list(&self, _request: &JsonRpcRequest) -> Result<serde_json::Value> {
|
|
Ok(json!({
|
|
"resources": [
|
|
{ "uri": "vapora://projects", "name": "Projects", "description": "All projects" },
|
|
{ "uri": "vapora://tasks", "name": "Tasks", "description": "All tasks" },
|
|
{ "uri": "vapora://agents", "name": "Agents", "description": "Agent registry" },
|
|
]
|
|
}))
|
|
}
|
|
|
|
async fn handle_prompts_list(&self, _request: &JsonRpcRequest) -> Result<serde_json::Value> {
|
|
Ok(json!({
|
|
"prompts": [
|
|
{ "name": "analyze_task", "description": "Analyze task and suggest improvements" },
|
|
{ "name": "code_review_prompt", "description": "Generate code review feedback" },
|
|
]
|
|
}))
|
|
}
|
|
}
|