#![allow(clippy::redundant_closure)]
#![allow(clippy::io_other_error)]
pub mod config;
pub mod error;
pub mod error_utils;
pub mod server;
pub mod tools;
pub use config::MetisServerConfig;
pub use error::{McpServerError, Result};
pub use server::MetisServerHandler;
use anyhow::Result as AnyhowResult;
use metis_core::{
application::services::workspace::WorkspaceDetectionService, dal::database::Database,
domain::configuration::FlightLevelConfig,
};
use rust_mcp_sdk::{
mcp_server::server_runtime,
schema::{
Implementation, InitializeResult, ServerCapabilities, ServerCapabilitiesTools,
LATEST_PROTOCOL_VERSION,
},
McpServer, StdioTransport, TransportOptions,
};
use tracing::info;
fn find_metis_log_path() -> Option<String> {
let detection_service = WorkspaceDetectionService::new();
if let Ok(Some(metis_dir)) = detection_service.find_workspace() {
return Some(
metis_dir
.join("metis-mcp-server.log")
.to_string_lossy()
.to_string(),
);
}
None
}
fn get_current_configuration() -> Option<FlightLevelConfig> {
let detection_service = WorkspaceDetectionService::new();
if let Ok(Some(metis_dir)) = detection_service.find_workspace() {
let db_path = metis_dir.join("metis.db");
if let Ok(db) = Database::new(db_path.to_string_lossy().as_ref()) {
if let Ok(mut config_repo) = db.configuration_repository() {
if let Ok(config) = config_repo.get_flight_level_config() {
return Some(config);
}
}
}
}
None
}
fn generate_dynamic_instructions() -> String {
let config = get_current_configuration();
let static_instructions = include_str!("../instructions.md");
if let Some(config) = config {
let config_section = format!(
r#"
## Current Project Configuration
**Active Preset**: {}
**Enabled Document Types**: {}
**Hierarchy**: {}
### Available Operations
{}
### Configuration Notes
- To change configuration: Use `metis config set --preset <preset>` where preset is one of: full, streamlined, direct
- To enable/disable specific levels: Use `metis config set --strategies <true/false> --initiatives <true/false>`
"#,
config.preset_name(),
config
.enabled_document_types()
.iter()
.map(|t| t.to_string())
.collect::<Vec<_>>()
.join(", "),
config.hierarchy_display(),
generate_operation_notes(&config)
);
format!("{}{}", config_section, static_instructions)
} else {
format!(
r#"
## Configuration Status
**Status**: No active Metis workspace detected. Initialize a project first using `initialize_project`.
{}
"#,
static_instructions
)
}
}
fn generate_operation_notes(config: &FlightLevelConfig) -> String {
let mut notes = Vec::new();
if !config.strategies_enabled {
notes.push("- Strategy creation is disabled in this configuration");
}
if !config.initiatives_enabled {
notes.push("- Initiative creation is disabled in this configuration");
}
if config.strategies_enabled && config.initiatives_enabled {
notes.push("- All document types are available for creation");
} else if !config.strategies_enabled && config.initiatives_enabled {
notes.push("- Direct creation: Vision → Initiative → Task workflow");
} else if config.strategies_enabled && !config.initiatives_enabled {
notes.push("- Streamlined workflow: Vision → Strategy → Task");
} else {
notes.push("- Minimal workflow: Vision → Task (direct mode)");
}
notes.join("\n")
}
pub async fn run() -> AnyhowResult<()> {
let _ = if let Some(log_path) = find_metis_log_path() {
let log_file = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&log_path)?;
tracing_subscriber::fmt()
.with_writer(log_file)
.with_ansi(false)
.try_init()
} else {
tracing_subscriber::fmt()
.with_writer(std::io::stderr)
.with_ansi(false)
.with_max_level(tracing::Level::WARN)
.try_init()
};
let config = MetisServerConfig::from_env()?;
info!("Starting Metis MCP Server");
let server_details = InitializeResult {
server_info: Implementation {
name: "Metis Documentation Management System".to_string(),
version: "0.4.2".to_string(),
title: Some("Metis MCP Server".to_string()),
},
capabilities: ServerCapabilities {
tools: Some(ServerCapabilitiesTools { list_changed: None }),
..Default::default()
},
meta: None,
instructions: Some(generate_dynamic_instructions()),
protocol_version: LATEST_PROTOCOL_VERSION.to_string(),
};
let transport = StdioTransport::new(TransportOptions::default())
.map_err(|e| anyhow::anyhow!("Failed to create transport: {}", e))?;
let handler = MetisServerHandler::new(config);
let server = server_runtime::create_server(server_details, transport, handler);
info!("MCP Server starting on stdio transport");
server
.start()
.await
.map_err(|e| anyhow::anyhow!("MCP server failed to start: {}", e))?;
Ok(())
}