metis_mcp_server/
lib.rs

1#![allow(clippy::redundant_closure)]
2#![allow(clippy::io_other_error)]
3
4pub mod config;
5pub mod error;
6pub mod error_utils;
7pub mod server;
8pub mod tools;
9
10pub use config::MetisServerConfig;
11pub use error::{McpServerError, Result};
12pub use server::MetisServerHandler;
13
14use anyhow::Result as AnyhowResult;
15use rust_mcp_sdk::{
16    mcp_server::server_runtime,
17    schema::{
18        Implementation, InitializeResult, ServerCapabilities, ServerCapabilitiesTools,
19        LATEST_PROTOCOL_VERSION,
20    },
21    McpServer, StdioTransport, TransportOptions,
22};
23use tracing::info;
24
25fn find_metis_log_path() -> Option<String> {
26    let current_dir = std::env::current_dir().ok()?;
27    let mut current = current_dir;
28
29    // Traverse upward looking for initialized metis project
30    loop {
31        let metis_dir = current.join("metis");
32        let metis_db = metis_dir.join(".metis.db");
33
34        // Only create logs if there's an initialized metis project
35        if metis_dir.is_dir() && metis_db.exists() {
36            return Some(
37                metis_dir
38                    .join("metis-mcp-server.log")
39                    .to_string_lossy()
40                    .to_string(),
41            );
42        }
43
44        // Move to parent directory
45        if let Some(parent) = current.parent() {
46            current = parent.to_path_buf();
47        } else {
48            // Reached filesystem root
49            break;
50        }
51    }
52
53    None
54}
55
56/// Run the MCP server
57pub async fn run() -> AnyhowResult<()> {
58    // Initialize logging only if we find an initialized metis project
59    // Try to initialize, ignore if already initialized
60    let _ = if let Some(log_path) = find_metis_log_path() {
61        // Initialize tracing with file output in metis project
62        let log_file = std::fs::OpenOptions::new()
63            .create(true)
64            .append(true)
65            .open(&log_path)?;
66
67        tracing_subscriber::fmt()
68            .with_writer(log_file)
69            .with_ansi(false)
70            .try_init()
71    } else {
72        // No metis project found - use minimal console logging only
73        tracing_subscriber::fmt()
74            .with_writer(std::io::stderr)
75            .with_ansi(false)
76            .with_max_level(tracing::Level::WARN)
77            .try_init()
78    };
79
80    // Load configuration (minimal for now)
81    let config = MetisServerConfig::from_env()?;
82
83    info!("Starting Metis MCP Server");
84
85    // Create server details
86    let server_details = InitializeResult {
87        server_info: Implementation {
88            name: "Metis Documentation Management System".to_string(),
89            version: "0.1.0".to_string(),
90            title: Some("Metis MCP Server".to_string()),
91        },
92        capabilities: ServerCapabilities {
93            tools: Some(ServerCapabilitiesTools { list_changed: None }),
94            ..Default::default()
95        },
96        meta: None,
97        instructions: Some(
98            r#"# Metis Flight Levels Documentation Management
99
100## Overview
101Metis implements a hierarchical document management system based on Flight Levels methodology. You manage projects by creating and transitioning documents through defined phases using direct file paths.
102
103## Document Hierarchy (Flight Levels)
104Create documents in this order, with each level building on the previous:
105
1061. **Vision** (Level 3) - Overall purpose and direction
107   - Always start here - defines why the project exists
108   - Phases: draft → review → published
109
1102. **Strategy** (Level 2) - How to achieve the vision  
111   - Must reference Vision as parent
112   - Phases: shaping → design → ready → active → completed
113
1143. **Initiative** (Level 1) - Concrete projects implementing strategies
115   - Must reference Strategy as parent
116   - Phases: discovery → design → ready → decompose → active → completed
117
1184. **Task** (Level 0) - Individual work items
119   - Must reference Initiative as parent
120   - Phases: todo → doing → completed
121
1225. **ADR** (Architectural Decision Record) - Technical decisions
123   - Can exist at any level, no parent required
124   - Phases: draft → discussion → decided → superseded
125
126## Direct Path Usage
127All tools use `project_path` pointing to the directory containing `.metis.db`:
128- Initialize: `{"project_path": "/path/to/project", "project_name": "my-project"}`
129- Create doc: `{"project_path": "/path/to/project", "document_type": "vision", "title": "Project Vision"}`
130- Update: `{"project_path": "/path/to/project", "document_path": "vision.md", ...}`
131
132## Essential Workflow
1331. **Start**: `initialize_project` creates `.metis.db` and initial structure
1342. **Build hierarchy**: Create Vision → Strategies → Initiatives → Tasks
1353. **Progress**: Use `validate_exit_criteria` before `transition_phase`
1364. **Update**: Use `update_*` tools for incremental changes
1375. **Query**: Use `list_documents` and `search_documents` to explore
138
139## Phase Transition Rules
140- Always validate exit criteria before transitioning: `validate_exit_criteria`
141- Use `transition_phase` only when ready to progress
142- Phase progression is generally linear (no skipping)
143- Force transitions with `force: true` only when necessary
144
145## Best Practices
146- Create documents in hierarchical order (Vision before Strategy, etc.)
147- Define clear exit criteria for each phase
148- Use `blocked_by` to track dependencies
149- Validate documents after creation/updates
150- Keep documents focused on their level's scope
151- Update parent documents when children complete
152
153## Common Patterns
154- Start with Vision, then 2-3 key Strategies
155- Each Strategy typically has 3-5 Initiatives  
156- Each Initiative usually has 5-15 Tasks
157- Create ADRs for significant technical decisions
158- Review hierarchy regularly as work progresses"#.to_string(),
159        ),
160        protocol_version: LATEST_PROTOCOL_VERSION.to_string(),
161    };
162
163    // Create transport
164    let transport = StdioTransport::new(TransportOptions::default())
165        .map_err(|e| anyhow::anyhow!("Failed to create transport: {}", e))?;
166
167    // Create handler
168    let handler = MetisServerHandler::new(config);
169
170    // Create and start server
171    let server = server_runtime::create_server(server_details, transport, handler);
172
173    info!("MCP Server starting on stdio transport");
174    server
175        .start()
176        .await
177        .map_err(|e| anyhow::anyhow!("MCP server failed to start: {}", e))?;
178
179    Ok(())
180}