intent_engine/mcp/
server.rs

1//! Intent-Engine MCP Server (Rust Implementation)
2//!
3//! This is a native Rust implementation of the MCP (Model Context Protocol) server
4//! that provides a JSON-RPC 2.0 interface for AI assistants to interact with
5//! intent-engine's task management capabilities.
6//!
7//! Unlike the Python wrapper (mcp-server.py), this implementation directly uses
8//! the Rust library functions, avoiding subprocess overhead and improving performance.
9
10use crate::events::EventManager;
11use crate::project::ProjectContext;
12use crate::report::ReportManager;
13use crate::tasks::TaskManager;
14use crate::workspace::WorkspaceManager;
15use serde::{Deserialize, Serialize};
16use serde_json::{json, Value};
17use std::io::{self, BufRead, Write};
18
19#[derive(Debug, Deserialize)]
20struct JsonRpcRequest {
21    jsonrpc: String,
22    id: Option<Value>,
23    method: String,
24    params: Option<Value>,
25}
26
27#[derive(Debug, Serialize)]
28struct JsonRpcResponse {
29    jsonrpc: String,
30    id: Option<Value>,
31    #[serde(skip_serializing_if = "Option::is_none")]
32    result: Option<Value>,
33    #[serde(skip_serializing_if = "Option::is_none")]
34    error: Option<JsonRpcError>,
35}
36
37#[derive(Debug, Serialize)]
38struct JsonRpcError {
39    code: i32,
40    message: String,
41}
42
43#[derive(Debug, Deserialize)]
44struct ToolCallParams {
45    name: String,
46    arguments: Value,
47}
48
49/// MCP Tool Schema
50const MCP_TOOLS: &str = include_str!("../../mcp-server.json");
51
52/// Run the MCP server
53/// This is the main entry point for MCP server mode
54pub async fn run() -> io::Result<()> {
55    run_server().await
56}
57
58async fn run_server() -> io::Result<()> {
59    let stdin = io::stdin();
60    let mut stdout = io::stdout();
61    let reader = stdin.lock();
62
63    for line in reader.lines() {
64        let line = line?;
65        if line.trim().is_empty() {
66            continue;
67        }
68
69        let response = match serde_json::from_str::<JsonRpcRequest>(&line) {
70            Ok(request) => handle_request(request).await,
71            Err(e) => JsonRpcResponse {
72                jsonrpc: "2.0".to_string(),
73                id: None,
74                result: None,
75                error: Some(JsonRpcError {
76                    code: -32700,
77                    message: format!("Parse error: {}", e),
78                }),
79            },
80        };
81
82        let response_json = serde_json::to_string(&response)?;
83        writeln!(stdout, "{}", response_json)?;
84        stdout.flush()?;
85    }
86
87    Ok(())
88}
89
90async fn handle_request(request: JsonRpcRequest) -> JsonRpcResponse {
91    // Validate JSON-RPC version
92    if request.jsonrpc != "2.0" {
93        return JsonRpcResponse {
94            jsonrpc: "2.0".to_string(),
95            id: request.id,
96            result: None,
97            error: Some(JsonRpcError {
98                code: -32600,
99                message: format!("Invalid JSON-RPC version: {}", request.jsonrpc),
100            }),
101        };
102    }
103
104    let result = match request.method.as_str() {
105        "tools/list" => handle_tools_list(),
106        "tools/call" => handle_tool_call(request.params).await,
107        _ => Err(format!("Unknown method: {}", request.method)),
108    };
109
110    match result {
111        Ok(value) => JsonRpcResponse {
112            jsonrpc: "2.0".to_string(),
113            id: request.id,
114            result: Some(value),
115            error: None,
116        },
117        Err(message) => JsonRpcResponse {
118            jsonrpc: "2.0".to_string(),
119            id: request.id,
120            result: None,
121            error: Some(JsonRpcError {
122                code: -32000,
123                message,
124            }),
125        },
126    }
127}
128
129fn handle_tools_list() -> Result<Value, String> {
130    let config: Value = serde_json::from_str(MCP_TOOLS)
131        .map_err(|e| format!("Failed to parse MCP tools schema: {}", e))?;
132
133    Ok(json!({
134        "tools": config.get("tools").unwrap_or(&json!([]))
135    }))
136}
137
138async fn handle_tool_call(params: Option<Value>) -> Result<Value, String> {
139    let params: ToolCallParams = serde_json::from_value(params.unwrap_or(json!({})))
140        .map_err(|e| format!("Invalid tool call parameters: {}", e))?;
141
142    let result = match params.name.as_str() {
143        "task_add" => handle_task_add(params.arguments).await,
144        "task_start" => handle_task_start(params.arguments).await,
145        "task_pick_next" => handle_task_pick_next(params.arguments).await,
146        "task_spawn_subtask" => handle_task_spawn_subtask(params.arguments).await,
147        "task_switch" => handle_task_switch(params.arguments).await,
148        "task_done" => handle_task_done(params.arguments).await,
149        "task_update" => handle_task_update(params.arguments).await,
150        "task_find" => handle_task_find(params.arguments).await,
151        "task_get" => handle_task_get(params.arguments).await,
152        "event_add" => handle_event_add(params.arguments).await,
153        "event_list" => handle_event_list(params.arguments).await,
154        "current_task_get" => handle_current_task_get(params.arguments).await,
155        "report_generate" => handle_report_generate(params.arguments).await,
156        _ => Err(format!("Unknown tool: {}", params.name)),
157    }?;
158
159    Ok(json!({
160        "content": [{
161            "type": "text",
162            "text": serde_json::to_string_pretty(&result)
163                .unwrap_or_else(|_| "{}".to_string())
164        }]
165    }))
166}
167
168// Tool Handlers
169
170async fn handle_task_add(args: Value) -> Result<Value, String> {
171    let name = args
172        .get("name")
173        .and_then(|v| v.as_str())
174        .ok_or("Missing required parameter: name")?;
175
176    let spec = args.get("spec").and_then(|v| v.as_str());
177    let parent_id = args.get("parent_id").and_then(|v| v.as_i64());
178
179    let ctx = ProjectContext::load_or_init()
180        .await
181        .map_err(|e| format!("Failed to load project context: {}", e))?;
182
183    let task_mgr = TaskManager::new(&ctx.pool);
184    let task = task_mgr
185        .add_task(name, spec, parent_id)
186        .await
187        .map_err(|e| format!("Failed to add task: {}", e))?;
188
189    serde_json::to_value(&task).map_err(|e| format!("Serialization error: {}", e))
190}
191
192async fn handle_task_start(args: Value) -> Result<Value, String> {
193    let task_id = args
194        .get("task_id")
195        .and_then(|v| v.as_i64())
196        .ok_or("Missing required parameter: task_id")?;
197
198    let with_events = args
199        .get("with_events")
200        .and_then(|v| v.as_bool())
201        .unwrap_or(true);
202
203    let ctx = ProjectContext::load_or_init()
204        .await
205        .map_err(|e| format!("Failed to load project context: {}", e))?;
206
207    let task_mgr = TaskManager::new(&ctx.pool);
208    let task = task_mgr
209        .start_task(task_id, with_events)
210        .await
211        .map_err(|e| format!("Failed to start task: {}", e))?;
212
213    serde_json::to_value(&task).map_err(|e| format!("Serialization error: {}", e))
214}
215
216async fn handle_task_pick_next(args: Value) -> Result<Value, String> {
217    let _max_count = args.get("max_count").and_then(|v| v.as_i64());
218    let _capacity = args.get("capacity").and_then(|v| v.as_i64());
219
220    let ctx = ProjectContext::load_or_init()
221        .await
222        .map_err(|e| format!("Failed to load project context: {}", e))?;
223
224    let task_mgr = TaskManager::new(&ctx.pool);
225    let response = task_mgr
226        .pick_next()
227        .await
228        .map_err(|e| format!("Failed to pick next task: {}", e))?;
229
230    serde_json::to_value(&response).map_err(|e| format!("Serialization error: {}", e))
231}
232
233async fn handle_task_spawn_subtask(args: Value) -> Result<Value, String> {
234    let name = args
235        .get("name")
236        .and_then(|v| v.as_str())
237        .ok_or("Missing required parameter: name")?;
238
239    let spec = args.get("spec").and_then(|v| v.as_str());
240
241    let ctx = ProjectContext::load_or_init()
242        .await
243        .map_err(|e| format!("Failed to load project context: {}", e))?;
244
245    let task_mgr = TaskManager::new(&ctx.pool);
246    let subtask = task_mgr
247        .spawn_subtask(name, spec)
248        .await
249        .map_err(|e| format!("Failed to spawn subtask: {}", e))?;
250
251    serde_json::to_value(&subtask).map_err(|e| format!("Serialization error: {}", e))
252}
253
254async fn handle_task_switch(args: Value) -> Result<Value, String> {
255    let task_id = args
256        .get("task_id")
257        .and_then(|v| v.as_i64())
258        .ok_or("Missing required parameter: task_id")?;
259
260    let ctx = ProjectContext::load_or_init()
261        .await
262        .map_err(|e| format!("Failed to load project context: {}", e))?;
263
264    let task_mgr = TaskManager::new(&ctx.pool);
265    let task = task_mgr
266        .switch_to_task(task_id)
267        .await
268        .map_err(|e| format!("Failed to switch task: {}", e))?;
269
270    serde_json::to_value(&task).map_err(|e| format!("Serialization error: {}", e))
271}
272
273async fn handle_task_done(args: Value) -> Result<Value, String> {
274    let task_id = args.get("task_id").and_then(|v| v.as_i64());
275
276    let ctx = ProjectContext::load_or_init()
277        .await
278        .map_err(|e| format!("Failed to load project context: {}", e))?;
279
280    let task_mgr = TaskManager::new(&ctx.pool);
281
282    // If task_id is provided, set it as current first
283    if let Some(id) = task_id {
284        let workspace_mgr = WorkspaceManager::new(&ctx.pool);
285        workspace_mgr
286            .set_current_task(id)
287            .await
288            .map_err(|e| format!("Failed to set current task: {}", e))?;
289    }
290
291    let task = task_mgr
292        .done_task()
293        .await
294        .map_err(|e| format!("Failed to mark task as done: {}", e))?;
295
296    serde_json::to_value(&task).map_err(|e| format!("Serialization error: {}", e))
297}
298
299async fn handle_task_update(args: Value) -> Result<Value, String> {
300    let task_id = args
301        .get("task_id")
302        .and_then(|v| v.as_i64())
303        .ok_or("Missing required parameter: task_id")?;
304
305    let name = args.get("name").and_then(|v| v.as_str());
306    let spec = args.get("spec").and_then(|v| v.as_str());
307    let status = args.get("status").and_then(|v| v.as_str());
308    let complexity = args
309        .get("complexity")
310        .and_then(|v| v.as_i64())
311        .map(|v| v as i32);
312    let priority = args
313        .get("priority")
314        .and_then(|v| v.as_i64())
315        .map(|v| v as i32);
316    let parent_id = args.get("parent_id").and_then(|v| v.as_i64()).map(Some);
317
318    let ctx = ProjectContext::load_or_init()
319        .await
320        .map_err(|e| format!("Failed to load project context: {}", e))?;
321
322    let task_mgr = TaskManager::new(&ctx.pool);
323    let task = task_mgr
324        .update_task(task_id, name, spec, parent_id, status, complexity, priority)
325        .await
326        .map_err(|e| format!("Failed to update task: {}", e))?;
327
328    serde_json::to_value(&task).map_err(|e| format!("Serialization error: {}", e))
329}
330
331async fn handle_task_find(args: Value) -> Result<Value, String> {
332    let status = args.get("status").and_then(|v| v.as_str());
333    let parent = args.get("parent").and_then(|v| v.as_str());
334
335    let parent_opt = parent.map(|p| {
336        if p == "null" {
337            None
338        } else {
339            p.parse::<i64>().ok()
340        }
341    });
342
343    let ctx = ProjectContext::load()
344        .await
345        .map_err(|e| format!("Failed to load project context: {}", e))?;
346
347    let task_mgr = TaskManager::new(&ctx.pool);
348    let tasks = task_mgr
349        .find_tasks(status, parent_opt)
350        .await
351        .map_err(|e| format!("Failed to find tasks: {}", e))?;
352
353    serde_json::to_value(&tasks).map_err(|e| format!("Serialization error: {}", e))
354}
355
356async fn handle_task_get(args: Value) -> Result<Value, String> {
357    let task_id = args
358        .get("task_id")
359        .and_then(|v| v.as_i64())
360        .ok_or("Missing required parameter: task_id")?;
361
362    let with_events = args
363        .get("with_events")
364        .and_then(|v| v.as_bool())
365        .unwrap_or(false);
366
367    let ctx = ProjectContext::load()
368        .await
369        .map_err(|e| format!("Failed to load project context: {}", e))?;
370
371    let task_mgr = TaskManager::new(&ctx.pool);
372
373    if with_events {
374        let task = task_mgr
375            .get_task_with_events(task_id)
376            .await
377            .map_err(|e| format!("Failed to get task: {}", e))?;
378        serde_json::to_value(&task).map_err(|e| format!("Serialization error: {}", e))
379    } else {
380        let task = task_mgr
381            .get_task(task_id)
382            .await
383            .map_err(|e| format!("Failed to get task: {}", e))?;
384        serde_json::to_value(&task).map_err(|e| format!("Serialization error: {}", e))
385    }
386}
387
388async fn handle_event_add(args: Value) -> Result<Value, String> {
389    let task_id = args.get("task_id").and_then(|v| v.as_i64());
390
391    let event_type = args
392        .get("event_type")
393        .and_then(|v| v.as_str())
394        .ok_or("Missing required parameter: event_type")?;
395
396    let data = args
397        .get("data")
398        .and_then(|v| v.as_str())
399        .ok_or("Missing required parameter: data")?;
400
401    let ctx = ProjectContext::load_or_init()
402        .await
403        .map_err(|e| format!("Failed to load project context: {}", e))?;
404
405    // Determine the target task ID
406    let target_task_id = if let Some(id) = task_id {
407        id
408    } else {
409        // Fall back to current_task_id
410        let current_task_id: Option<String> =
411            sqlx::query_scalar("SELECT value FROM workspace_state WHERE key = 'current_task_id'")
412                .fetch_optional(&ctx.pool)
413                .await
414                .map_err(|e| format!("Database error: {}", e))?;
415
416        current_task_id
417            .and_then(|s| s.parse::<i64>().ok())
418            .ok_or_else(|| {
419                "No current task is set and task_id was not provided. \
420                 Use task_start or task_switch to set a task first."
421                    .to_string()
422            })?
423    };
424
425    let event_mgr = EventManager::new(&ctx.pool);
426    let event = event_mgr
427        .add_event(target_task_id, event_type, data)
428        .await
429        .map_err(|e| format!("Failed to add event: {}", e))?;
430
431    serde_json::to_value(&event).map_err(|e| format!("Serialization error: {}", e))
432}
433
434async fn handle_event_list(args: Value) -> Result<Value, String> {
435    let task_id = args
436        .get("task_id")
437        .and_then(|v| v.as_i64())
438        .ok_or("Missing required parameter: task_id")?;
439
440    let limit = args.get("limit").and_then(|v| v.as_i64());
441
442    let ctx = ProjectContext::load()
443        .await
444        .map_err(|e| format!("Failed to load project context: {}", e))?;
445
446    let event_mgr = EventManager::new(&ctx.pool);
447    let events = event_mgr
448        .list_events(task_id, limit)
449        .await
450        .map_err(|e| format!("Failed to list events: {}", e))?;
451
452    serde_json::to_value(&events).map_err(|e| format!("Serialization error: {}", e))
453}
454
455async fn handle_current_task_get(_args: Value) -> Result<Value, String> {
456    let ctx = ProjectContext::load()
457        .await
458        .map_err(|e| format!("Failed to load project context: {}", e))?;
459
460    let workspace_mgr = WorkspaceManager::new(&ctx.pool);
461    let response = workspace_mgr
462        .get_current_task()
463        .await
464        .map_err(|e| format!("Failed to get current task: {}", e))?;
465
466    serde_json::to_value(&response).map_err(|e| format!("Serialization error: {}", e))
467}
468
469async fn handle_report_generate(args: Value) -> Result<Value, String> {
470    let since = args.get("since").and_then(|v| v.as_str()).map(String::from);
471    let status = args
472        .get("status")
473        .and_then(|v| v.as_str())
474        .map(String::from);
475    let filter_name = args
476        .get("filter_name")
477        .and_then(|v| v.as_str())
478        .map(String::from);
479    let filter_spec = args
480        .get("filter_spec")
481        .and_then(|v| v.as_str())
482        .map(String::from);
483    let summary_only = args
484        .get("summary_only")
485        .and_then(|v| v.as_bool())
486        .unwrap_or(true);
487
488    let ctx = ProjectContext::load()
489        .await
490        .map_err(|e| format!("Failed to load project context: {}", e))?;
491
492    let report_mgr = ReportManager::new(&ctx.pool);
493    let report = report_mgr
494        .generate_report(since, status, filter_name, filter_spec, summary_only)
495        .await
496        .map_err(|e| format!("Failed to generate report: {}", e))?;
497
498    serde_json::to_value(&report).map_err(|e| format!("Serialization error: {}", e))
499}