736 lines
26 KiB
Rust
Raw Normal View History

//! MCP (Model Context Protocol) tool registry and execution
//!
//! Provides tool definition, registration, and execution for RAG, Guidance,
//! Settings, and IaC tools.
use provisioning_mcp_server::tools::settings::{DeploymentMode, SettingsTools};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use tokio::sync::Mutex;
/// Tool execution result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolExecution {
pub tool_name: String,
pub result: Value,
pub duration_ms: u64,
}
/// MCP tool registry for provisioning system
pub struct ToolRegistry {
tools: std::collections::HashMap<String, ToolDefinition>,
settings_tools: Mutex<SettingsTools>,
}
/// Tool definition for MCP
#[derive(Debug, Clone)]
pub struct ToolDefinition {
pub name: String,
pub description: String,
pub category: ToolCategory,
pub input_schema: Value,
}
/// Tool categories
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ToolCategory {
Rag,
Guidance,
Settings,
Iac,
}
impl ToolRegistry {
/// Create a new tool registry
pub fn new() -> Self {
let mut registry = Self {
tools: std::collections::HashMap::new(),
settings_tools: Mutex::new(SettingsTools::new()),
};
registry.register_all_tools();
registry
}
/// Register all tool categories (RAG, Guidance, Settings, IaC)
fn register_all_tools(&mut self) {
self.register_rag_tools();
self.register_guidance_tools();
self.register_settings_tools();
self.register_iac_tools();
}
/// Register RAG tools
fn register_rag_tools(&mut self) {
self.tools.insert(
"rag_ask_question".to_string(),
ToolDefinition {
name: "rag_ask_question".to_string(),
description: "Ask a question using RAG (Retrieval-Augmented Generation) with knowledge base search".to_string(),
category: ToolCategory::Rag,
input_schema: json!({
"type": "object",
"properties": {
"question": {"type": "string", "description": "The question to ask"},
"context": {"type": "string", "description": "Optional context for the question"},
"top_k": {"type": "integer", "description": "Number of top results to return", "default": 3}
},
"required": ["question"]
}),
},
);
self.tools.insert(
"rag_semantic_search".to_string(),
ToolDefinition {
name: "rag_semantic_search".to_string(),
description: "Perform semantic search on the knowledge base".to_string(),
category: ToolCategory::Rag,
input_schema: json!({
"type": "object",
"properties": {
"query": {"type": "string", "description": "Search query"},
"category": {"type": "string", "description": "Optional category filter"},
"top_k": {"type": "integer", "description": "Number of results", "default": 5}
},
"required": ["query"]
}),
},
);
self.tools.insert(
"rag_get_status".to_string(),
ToolDefinition {
name: "rag_get_status".to_string(),
description: "Get the current status of the RAG knowledge base".to_string(),
category: ToolCategory::Rag,
input_schema: json!({
"type": "object",
"properties": {}
}),
},
);
}
/// Register Guidance tools
fn register_guidance_tools(&mut self) {
self.tools.insert(
"guidance_check_system_status".to_string(),
ToolDefinition {
name: "guidance_check_system_status".to_string(),
description: "Check the current system status and configuration".to_string(),
category: ToolCategory::Guidance,
input_schema: json!({
"type": "object",
"properties": {}
}),
},
);
self.tools.insert(
"guidance_suggest_next_action".to_string(),
ToolDefinition {
name: "guidance_suggest_next_action".to_string(),
description: "Get suggestions for the next action based on current system state".to_string(),
category: ToolCategory::Guidance,
input_schema: json!({
"type": "object",
"properties": {
"context": {"type": "string", "description": "Optional context for suggestion"}
}
}),
},
);
self.tools.insert(
"guidance_find_docs".to_string(),
ToolDefinition {
name: "guidance_find_docs".to_string(),
description: "Find relevant documentation and guides".to_string(),
category: ToolCategory::Guidance,
input_schema: json!({
"type": "object",
"properties": {
"query": {"type": "string", "description": "What to search for"},
"context": {"type": "string", "description": "Optional context"}
},
"required": ["query"]
}),
},
);
self.tools.insert(
"guidance_troubleshoot".to_string(),
ToolDefinition {
name: "guidance_troubleshoot".to_string(),
description: "Troubleshoot an issue or error".to_string(),
category: ToolCategory::Guidance,
input_schema: json!({
"type": "object",
"properties": {
"error": {"type": "string", "description": "Error message or description"},
"context": {"type": "string", "description": "Context about the issue"}
},
"required": ["error"]
}),
},
);
self.tools.insert(
"guidance_validate_config".to_string(),
ToolDefinition {
name: "guidance_validate_config".to_string(),
description: "Validate a configuration file or settings".to_string(),
category: ToolCategory::Guidance,
input_schema: json!({
"type": "object",
"properties": {
"config_path": {"type": "string", "description": "Path to configuration file"}
},
"required": ["config_path"]
}),
},
);
}
/// Register Settings tools
fn register_settings_tools(&mut self) {
self.tools.insert(
"installer_get_settings".to_string(),
ToolDefinition {
name: "installer_get_settings".to_string(),
description: "Get installer settings and configuration".to_string(),
category: ToolCategory::Settings,
input_schema: json!({
"type": "object",
"properties": {
"query": {"type": "string", "description": "Optional settings query"}
}
}),
},
);
self.tools.insert(
"installer_complete_config".to_string(),
ToolDefinition {
name: "installer_complete_config".to_string(),
description: "Complete partial configuration with defaults".to_string(),
category: ToolCategory::Settings,
input_schema: json!({
"type": "object",
"properties": {
"config": {"type": "object", "description": "Partial configuration"}
}
}),
},
);
self.tools.insert(
"installer_validate_config".to_string(),
ToolDefinition {
name: "installer_validate_config".to_string(),
description: "Validate configuration against schema".to_string(),
category: ToolCategory::Settings,
input_schema: json!({
"type": "object",
"properties": {
"config": {"type": "object", "description": "Configuration to validate"}
},
"required": ["config"]
}),
},
);
self.tools.insert(
"installer_get_defaults".to_string(),
ToolDefinition {
name: "installer_get_defaults".to_string(),
description: "Get default settings for a deployment mode".to_string(),
category: ToolCategory::Settings,
input_schema: json!({
"type": "object",
"properties": {
"mode": {"type": "string", "description": "Deployment mode"}
},
"required": ["mode"]
}),
},
);
self.tools.insert(
"installer_platform_recommendations".to_string(),
ToolDefinition {
name: "installer_platform_recommendations".to_string(),
description: "Get platform-specific recommendations".to_string(),
category: ToolCategory::Settings,
input_schema: json!({
"type": "object",
"properties": {}
}),
},
);
self.tools.insert(
"installer_service_recommendations".to_string(),
ToolDefinition {
name: "installer_service_recommendations".to_string(),
description: "Get service recommendations for a deployment mode".to_string(),
category: ToolCategory::Settings,
input_schema: json!({
"type": "object",
"properties": {
"mode": {"type": "string", "description": "Deployment mode"}
},
"required": ["mode"]
}),
},
);
self.tools.insert(
"installer_resource_recommendations".to_string(),
ToolDefinition {
name: "installer_resource_recommendations".to_string(),
description: "Get resource recommendations for a deployment mode".to_string(),
category: ToolCategory::Settings,
input_schema: json!({
"type": "object",
"properties": {
"mode": {"type": "string", "description": "Deployment mode"}
},
"required": ["mode"]
}),
},
);
}
/// Register IaC tools
fn register_iac_tools(&mut self) {
self.tools.insert(
"iac_detect_technologies".to_string(),
ToolDefinition {
name: "iac_detect_technologies".to_string(),
description: "Detect technologies used in infrastructure".to_string(),
category: ToolCategory::Iac,
input_schema: json!({
"type": "object",
"properties": {
"path": {"type": "string", "description": "Path to analyze"}
}
}),
},
);
self.tools.insert(
"iac_analyze_completeness".to_string(),
ToolDefinition {
name: "iac_analyze_completeness".to_string(),
description: "Analyze completeness of infrastructure configuration".to_string(),
category: ToolCategory::Iac,
input_schema: json!({
"type": "object",
"properties": {
"config": {"type": "object", "description": "Configuration to analyze"}
}
}),
},
);
self.tools.insert(
"iac_infer_requirements".to_string(),
ToolDefinition {
name: "iac_infer_requirements".to_string(),
description: "Infer infrastructure requirements from description".to_string(),
category: ToolCategory::Iac,
input_schema: json!({
"type": "object",
"properties": {
"description": {"type": "string", "description": "Infrastructure description"}
},
"required": ["description"]
}),
},
);
}
/// Get all tool definitions
pub fn list_tools(&self) -> Vec<ToolDefinition> {
self.tools.values().cloned().collect()
}
/// Get tools by category
pub fn tools_by_category(&self, category: ToolCategory) -> Vec<ToolDefinition> {
self.tools
.values()
.filter(|t| t.category == category)
.cloned()
.collect()
}
/// Check if tool exists
pub fn has_tool(&self, name: &str) -> bool {
self.tools.contains_key(name)
}
/// Execute a tool (async)
pub async fn execute(&self, tool_name: &str, args: &Value) -> Result<Value, String> {
match tool_name {
// RAG tools
"rag_ask_question" => self.rag_ask_question(args).await,
"rag_semantic_search" => self.rag_semantic_search(args).await,
"rag_get_status" => self.rag_get_status(args).await,
// Guidance tools
"guidance_check_system_status" => self.guidance_check_system_status(args).await,
"guidance_suggest_next_action" => self.guidance_suggest_next_action(args).await,
"guidance_find_docs" => self.guidance_find_docs(args).await,
"guidance_troubleshoot" => self.guidance_troubleshoot(args).await,
"guidance_validate_config" => self.guidance_validate_config(args).await,
// Settings tools
"installer_get_settings" => self.installer_get_settings(args).await,
"installer_complete_config" => self.installer_complete_config(args).await,
"installer_validate_config" => self.installer_validate_config(args).await,
"installer_get_defaults" => self.installer_get_defaults(args).await,
"installer_platform_recommendations" => {
self.installer_platform_recommendations(args).await
}
"installer_service_recommendations" => {
self.installer_service_recommendations(args).await
}
"installer_resource_recommendations" => {
self.installer_resource_recommendations(args).await
}
// IaC tools
"iac_detect_technologies" => self.iac_detect_technologies(args).await,
"iac_analyze_completeness" => self.iac_analyze_completeness(args).await,
"iac_infer_requirements" => self.iac_infer_requirements(args).await,
_ => Err(format!("Unknown tool: {}", tool_name)),
}
}
// ========== RAG TOOL IMPLEMENTATIONS ==========
async fn rag_ask_question(&self, args: &Value) -> Result<Value, String> {
let question = args
.get("question")
.and_then(|v| v.as_str())
.ok_or("question parameter required")?;
Ok(json!({
"status": "success",
"tool": "rag_ask_question",
"message": format!("RAG query would be processed for: {}", question)
}))
}
async fn rag_semantic_search(&self, args: &Value) -> Result<Value, String> {
let query = args
.get("query")
.and_then(|v| v.as_str())
.ok_or("query parameter required")?;
Ok(json!({
"status": "success",
"tool": "rag_semantic_search",
"message": format!("Semantic search would be performed for: {}", query),
"results": []
}))
}
async fn rag_get_status(&self, _args: &Value) -> Result<Value, String> {
Ok(json!({
"status": "active",
"tool": "rag_get_status",
"knowledge_base": {
"documents_loaded": true,
"total_documents": 76,
"categories": ["architecture", "deployment", "security", "reliability"]
}
}))
}
// ========== GUIDANCE TOOL IMPLEMENTATIONS ==========
/// Execute a Nushell command and parse JSON output
async fn execute_nu_command(cmd: &str) -> Result<Value, String> {
use tokio::process::Command;
let output = Command::new("nu")
.arg("-c")
.arg(cmd)
.output()
.await
.map_err(|e| format!("Failed to execute Nushell: {}", e))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(format!("Nushell command failed: {}", stderr));
}
serde_json::from_slice(&output.stdout)
.map_err(|e| format!("Failed to parse JSON output: {}", e))
}
async fn guidance_check_system_status(&self, _args: &Value) -> Result<Value, String> {
Self::execute_nu_command("provisioning status-json").await
}
async fn guidance_suggest_next_action(&self, _args: &Value) -> Result<Value, String> {
Self::execute_nu_command("provisioning next").await
}
async fn guidance_find_docs(&self, args: &Value) -> Result<Value, String> {
let query = args
.get("query")
.and_then(|v| v.as_str())
.ok_or("query parameter required")?;
let cmd = format!("provisioning guide {}", query);
Self::execute_nu_command(&cmd).await
}
async fn guidance_troubleshoot(&self, _args: &Value) -> Result<Value, String> {
Self::execute_nu_command("provisioning health-json").await
}
async fn guidance_validate_config(&self, args: &Value) -> Result<Value, String> {
let config_path = args
.get("config_path")
.and_then(|v| v.as_str())
.ok_or("config_path parameter required")?;
let cmd = format!("validate-system-config {}", config_path);
Self::execute_nu_command(&cmd).await
}
// ========== SETTINGS TOOL IMPLEMENTATIONS ==========
async fn installer_get_settings(&self, args: &Value) -> Result<Value, String> {
let query = args.get("query").and_then(|v| v.as_str());
let mut settings_tools = self.settings_tools.lock().await;
2026-01-17 04:01:34 +00:00
let settings = settings_tools
.get_settings(query)
.await
2026-01-17 04:01:34 +00:00
.map_err(|e| format!("Failed to get settings: {}", e))?;
Ok(json!({
"status": "success",
"tool": "installer_get_settings",
"platforms": settings.get("platforms"),
"modes": settings.get("modes"),
"default_domain": settings.get("default_domain"),
"auto_generate_secrets": settings.get("auto_generate_secrets"),
"available_services": settings.get("available_services")
}))
}
async fn installer_complete_config(&self, args: &Value) -> Result<Value, String> {
let config = args
.get("config")
.cloned()
.ok_or("config parameter required")?;
let mut settings_tools = self.settings_tools.lock().await;
settings_tools
.complete_config(config)
.await
.map_err(|e| format!("Failed to complete config: {}", e))
}
async fn installer_validate_config(&self, args: &Value) -> Result<Value, String> {
let config = args
.get("config")
.cloned()
.ok_or("config parameter required")?;
let settings_tools = self.settings_tools.lock().await;
settings_tools
.validate_config(config)
.map_err(|e| format!("Failed to validate config: {}", e))
}
async fn installer_get_defaults(&self, args: &Value) -> Result<Value, String> {
let mode = args
.get("mode")
.and_then(|v| v.as_str())
.ok_or("mode parameter required")?;
let settings_tools = self.settings_tools.lock().await;
2026-01-17 04:01:34 +00:00
let defaults = settings_tools
.get_mode_defaults(mode)
2026-01-17 04:01:34 +00:00
.map_err(|e| format!("Failed to get defaults: {}", e))?;
Ok(json!({
"status": "success",
"tool": "installer_get_defaults",
"mode": defaults.get("mode"),
"description": defaults.get("description"),
"service_count": defaults.get("service_count"),
"min_cpu_cores": defaults.get("min_cpu_cores"),
"min_memory_gb": defaults.get("min_memory_gb"),
"recommended_services": defaults.get("recommended_services"),
"auto_generate_secrets": defaults.get("auto_generate_secrets"),
"domain": defaults.get("domain")
}))
}
async fn installer_platform_recommendations(&self, _args: &Value) -> Result<Value, String> {
let mut settings_tools = self.settings_tools.lock().await;
let recommendations = settings_tools
.get_platform_recommendations()
.await
.map_err(|e| format!("Failed to get platform recommendations: {}", e))?;
Ok(json!({
"status": "success",
"tool": "installer_platform_recommendations",
"recommendations": recommendations
}))
}
async fn installer_service_recommendations(&self, args: &Value) -> Result<Value, String> {
let mode_str = args
.get("mode")
.and_then(|v| v.as_str())
.ok_or("mode parameter required")?;
let mode = DeploymentMode::from_str(mode_str)
.ok_or(format!("Invalid deployment mode: {}", mode_str))?;
let settings_tools = self.settings_tools.lock().await;
let recommendations = settings_tools.get_service_recommendations(&mode);
Ok(json!({
"status": "success",
"tool": "installer_service_recommendations",
"mode": mode_str,
"recommendations": recommendations
}))
}
async fn installer_resource_recommendations(&self, args: &Value) -> Result<Value, String> {
let mode_str = args
.get("mode")
.and_then(|v| v.as_str())
.ok_or("mode parameter required")?;
let mode = DeploymentMode::from_str(mode_str)
.ok_or(format!("Invalid deployment mode: {}", mode_str))?;
let settings_tools = self.settings_tools.lock().await;
let recommendations = settings_tools.get_resource_recommendations(&mode);
Ok(json!({
"status": "success",
"tool": "installer_resource_recommendations",
"mode": mode_str,
"recommendations": recommendations
}))
}
// ========== IAC TOOL IMPLEMENTATIONS ==========
async fn iac_detect_technologies(&self, args: &Value) -> Result<Value, String> {
let path = args
.get("path")
.and_then(|v| v.as_str())
.ok_or("path parameter required")?;
let path_obj = std::path::Path::new(path);
let mut technologies = Vec::new();
// Check for Kubernetes
if path_obj.join("kustomization.yaml").exists() || path_obj.join("deployment.yaml").exists()
{
technologies.push("kubernetes");
}
// Check for Docker
if path_obj.join("Dockerfile").exists() || path_obj.join("docker-compose.yml").exists() {
technologies.push("docker");
}
// Check for Terraform
if path_obj.join("main.tf").exists() {
technologies.push("terraform");
}
// Check for KCL (legacy)
if path_obj.join("kcl.mod").exists() {
technologies.push("kcl");
}
// Check for Nickel (current IaC)
if path_obj.join("main.ncl").exists() {
technologies.push("nickel");
}
Ok(json!({
"status": "success",
"tool": "iac_detect_technologies",
"path": path,
"technologies": technologies
}))
}
async fn iac_analyze_completeness(&self, args: &Value) -> Result<Value, String> {
let path = args
.get("path")
.and_then(|v| v.as_str())
.ok_or("path parameter required")?;
let path_obj = std::path::Path::new(path);
let mut missing = Vec::new();
// Check for essential infrastructure files
if !path_obj.join("infrastructure.ncl").exists() {
missing.push("infrastructure.ncl");
}
if !path_obj.join("config.toml").exists() {
missing.push("config.toml");
}
if !path_obj.join("README.md").exists() {
missing.push("README.md");
}
let complete = missing.is_empty();
let completeness_score = if missing.is_empty() { 1.0 } else { 0.7 };
Ok(json!({
"status": "success",
"tool": "iac_analyze_completeness",
"complete": complete,
"completeness_score": completeness_score,
"missing_files": missing
}))
}
async fn iac_infer_requirements(&self, args: &Value) -> Result<Value, String> {
let _description = args
.get("description")
.and_then(|v| v.as_str())
.ok_or("description parameter required")?;
// Basic requirements inference (can be enhanced with ML later)
Ok(json!({
"status": "success",
"tool": "iac_infer_requirements",
"requirements": {
"compute": "2 CPU, 4GB RAM (minimum)",
"storage": "20GB",
"network": "Private network recommended",
"high_availability": false
}
}))
}
}
impl Default for ToolRegistry {
fn default() -> Self {
Self::new()
}
}