1use 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
49const MCP_TOOLS: &str = include_str!("../../mcp-server.json");
51
52pub 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) => {
71 if request.id.is_none() {
73 handle_notification(&request).await;
74 continue; }
76 handle_request(request).await
77 },
78 Err(e) => JsonRpcResponse {
79 jsonrpc: "2.0".to_string(),
80 id: None,
81 result: None,
82 error: Some(JsonRpcError {
83 code: -32700,
84 message: format!("Parse error: {}", e),
85 }),
86 },
87 };
88
89 let response_json = serde_json::to_string(&response)?;
90 writeln!(stdout, "{}", response_json)?;
91 stdout.flush()?;
92 }
93
94 Ok(())
95}
96
97async fn handle_notification(request: &JsonRpcRequest) {
98 match request.method.as_str() {
100 "initialized" => {
101 eprintln!("✓ MCP client initialized");
102 },
103 "notifications/cancelled" => {
104 eprintln!("⚠ Request cancelled");
105 },
106 _ => {
107 eprintln!("⚠ Unknown notification: {}", request.method);
108 },
109 }
110}
111
112async fn handle_request(request: JsonRpcRequest) -> JsonRpcResponse {
113 if request.jsonrpc != "2.0" {
115 return JsonRpcResponse {
116 jsonrpc: "2.0".to_string(),
117 id: request.id,
118 result: None,
119 error: Some(JsonRpcError {
120 code: -32600,
121 message: format!("Invalid JSON-RPC version: {}", request.jsonrpc),
122 }),
123 };
124 }
125
126 let result = match request.method.as_str() {
127 "initialize" => handle_initialize(request.params),
128 "ping" => Ok(json!({})), "tools/list" => handle_tools_list(),
130 "tools/call" => handle_tool_call(request.params).await,
131 _ => Err(format!("Method not found: {}", request.method)),
132 };
133
134 match result {
135 Ok(value) => JsonRpcResponse {
136 jsonrpc: "2.0".to_string(),
137 id: request.id,
138 result: Some(value),
139 error: None,
140 },
141 Err(message) => JsonRpcResponse {
142 jsonrpc: "2.0".to_string(),
143 id: request.id,
144 result: None,
145 error: Some(JsonRpcError {
146 code: -32000,
147 message,
148 }),
149 },
150 }
151}
152
153fn handle_initialize(_params: Option<Value>) -> Result<Value, String> {
154 Ok(json!({
157 "protocolVersion": "2024-11-05",
158 "capabilities": {
159 "tools": {
160 "listChanged": false }
162 },
163 "serverInfo": {
164 "name": "intent-engine",
165 "version": env!("CARGO_PKG_VERSION")
166 }
167 }))
168}
169
170fn handle_tools_list() -> Result<Value, String> {
171 let config: Value = serde_json::from_str(MCP_TOOLS)
172 .map_err(|e| format!("Failed to parse MCP tools schema: {}", e))?;
173
174 Ok(json!({
175 "tools": config.get("tools").unwrap_or(&json!([]))
176 }))
177}
178
179async fn handle_tool_call(params: Option<Value>) -> Result<Value, String> {
180 let params: ToolCallParams = serde_json::from_value(params.unwrap_or(json!({})))
181 .map_err(|e| format!("Invalid tool call parameters: {}", e))?;
182
183 let result = match params.name.as_str() {
184 "task_add" => handle_task_add(params.arguments).await,
185 "task_add_dependency" => handle_task_add_dependency(params.arguments).await,
186 "task_start" => handle_task_start(params.arguments).await,
187 "task_pick_next" => handle_task_pick_next(params.arguments).await,
188 "task_spawn_subtask" => handle_task_spawn_subtask(params.arguments).await,
189 "task_switch" => handle_task_switch(params.arguments).await,
190 "task_done" => handle_task_done(params.arguments).await,
191 "task_update" => handle_task_update(params.arguments).await,
192 "task_list" => handle_task_list(params.arguments).await,
193 "task_find" => handle_task_find(params.arguments).await,
194 "task_search" => handle_task_search(params.arguments).await,
195 "task_get" => handle_task_get(params.arguments).await,
196 "task_context" => handle_task_context(params.arguments).await,
197 "task_delete" => handle_task_delete(params.arguments).await,
198 "event_add" => handle_event_add(params.arguments).await,
199 "event_list" => handle_event_list(params.arguments).await,
200 "current_task_get" => handle_current_task_get(params.arguments).await,
201 "report_generate" => handle_report_generate(params.arguments).await,
202 _ => Err(format!("Unknown tool: {}", params.name)),
203 }?;
204
205 Ok(json!({
206 "content": [{
207 "type": "text",
208 "text": serde_json::to_string_pretty(&result)
209 .unwrap_or_else(|_| "{}".to_string())
210 }]
211 }))
212}
213
214async fn handle_task_add(args: Value) -> Result<Value, String> {
217 let name = args
218 .get("name")
219 .and_then(|v| v.as_str())
220 .ok_or("Missing required parameter: name")?;
221
222 let spec = args.get("spec").and_then(|v| v.as_str());
223 let parent_id = args.get("parent_id").and_then(|v| v.as_i64());
224
225 let ctx = ProjectContext::load_or_init()
226 .await
227 .map_err(|e| format!("Failed to load project context: {}", e))?;
228
229 let task_mgr = TaskManager::new(&ctx.pool);
230 let task = task_mgr
231 .add_task(name, spec, parent_id)
232 .await
233 .map_err(|e| format!("Failed to add task: {}", e))?;
234
235 serde_json::to_value(&task).map_err(|e| format!("Serialization error: {}", e))
236}
237
238async fn handle_task_add_dependency(args: Value) -> Result<Value, String> {
239 let blocked_task_id = args
240 .get("blocked_task_id")
241 .and_then(|v| v.as_i64())
242 .ok_or("Missing required parameter: blocked_task_id")?;
243
244 let blocking_task_id = args
245 .get("blocking_task_id")
246 .and_then(|v| v.as_i64())
247 .ok_or("Missing required parameter: blocking_task_id")?;
248
249 let ctx = ProjectContext::load_or_init()
250 .await
251 .map_err(|e| format!("Failed to load project context: {}", e))?;
252
253 let dependency =
254 crate::dependencies::add_dependency(&ctx.pool, blocking_task_id, blocked_task_id)
255 .await
256 .map_err(|e| format!("Failed to add dependency: {}", e))?;
257
258 serde_json::to_value(&dependency).map_err(|e| format!("Serialization error: {}", e))
259}
260
261async fn handle_task_start(args: Value) -> Result<Value, String> {
262 let task_id = args
263 .get("task_id")
264 .and_then(|v| v.as_i64())
265 .ok_or("Missing required parameter: task_id")?;
266
267 let with_events = args
268 .get("with_events")
269 .and_then(|v| v.as_bool())
270 .unwrap_or(true);
271
272 let ctx = ProjectContext::load_or_init()
273 .await
274 .map_err(|e| format!("Failed to load project context: {}", e))?;
275
276 let task_mgr = TaskManager::new(&ctx.pool);
277 let task = task_mgr
278 .start_task(task_id, with_events)
279 .await
280 .map_err(|e| format!("Failed to start task: {}", e))?;
281
282 serde_json::to_value(&task).map_err(|e| format!("Serialization error: {}", e))
283}
284
285async fn handle_task_pick_next(args: Value) -> Result<Value, String> {
286 let _max_count = args.get("max_count").and_then(|v| v.as_i64());
287 let _capacity = args.get("capacity").and_then(|v| v.as_i64());
288
289 let ctx = ProjectContext::load_or_init()
290 .await
291 .map_err(|e| format!("Failed to load project context: {}", e))?;
292
293 let task_mgr = TaskManager::new(&ctx.pool);
294 let response = task_mgr
295 .pick_next()
296 .await
297 .map_err(|e| format!("Failed to pick next task: {}", e))?;
298
299 serde_json::to_value(&response).map_err(|e| format!("Serialization error: {}", e))
300}
301
302async fn handle_task_spawn_subtask(args: Value) -> Result<Value, String> {
303 let name = args
304 .get("name")
305 .and_then(|v| v.as_str())
306 .ok_or("Missing required parameter: name")?;
307
308 let spec = args.get("spec").and_then(|v| v.as_str());
309
310 let ctx = ProjectContext::load_or_init()
311 .await
312 .map_err(|e| format!("Failed to load project context: {}", e))?;
313
314 let task_mgr = TaskManager::new(&ctx.pool);
315 let subtask = task_mgr
316 .spawn_subtask(name, spec)
317 .await
318 .map_err(|e| format!("Failed to spawn subtask: {}", e))?;
319
320 serde_json::to_value(&subtask).map_err(|e| format!("Serialization error: {}", e))
321}
322
323async fn handle_task_switch(args: Value) -> Result<Value, String> {
324 let task_id = args
325 .get("task_id")
326 .and_then(|v| v.as_i64())
327 .ok_or("Missing required parameter: task_id")?;
328
329 let ctx = ProjectContext::load_or_init()
330 .await
331 .map_err(|e| format!("Failed to load project context: {}", e))?;
332
333 let task_mgr = TaskManager::new(&ctx.pool);
334 let task = task_mgr
335 .switch_to_task(task_id)
336 .await
337 .map_err(|e| format!("Failed to switch task: {}", e))?;
338
339 serde_json::to_value(&task).map_err(|e| format!("Serialization error: {}", e))
340}
341
342async fn handle_task_done(args: Value) -> Result<Value, String> {
343 let task_id = args.get("task_id").and_then(|v| v.as_i64());
344
345 let ctx = ProjectContext::load_or_init()
346 .await
347 .map_err(|e| format!("Failed to load project context: {}", e))?;
348
349 let task_mgr = TaskManager::new(&ctx.pool);
350
351 if let Some(id) = task_id {
353 let workspace_mgr = WorkspaceManager::new(&ctx.pool);
354 workspace_mgr
355 .set_current_task(id)
356 .await
357 .map_err(|e| format!("Failed to set current task: {}", e))?;
358 }
359
360 let task = task_mgr
361 .done_task()
362 .await
363 .map_err(|e| format!("Failed to mark task as done: {}", e))?;
364
365 serde_json::to_value(&task).map_err(|e| format!("Serialization error: {}", e))
366}
367
368async fn handle_task_update(args: Value) -> Result<Value, String> {
369 let task_id = args
370 .get("task_id")
371 .and_then(|v| v.as_i64())
372 .ok_or("Missing required parameter: task_id")?;
373
374 let name = args.get("name").and_then(|v| v.as_str());
375 let spec = args.get("spec").and_then(|v| v.as_str());
376 let status = args.get("status").and_then(|v| v.as_str());
377 let complexity = args
378 .get("complexity")
379 .and_then(|v| v.as_i64())
380 .map(|v| v as i32);
381 let priority = match args.get("priority").and_then(|v| v.as_str()) {
382 Some(p) => Some(
383 crate::priority::PriorityLevel::parse_to_int(p)
384 .map_err(|e| format!("Invalid priority: {}", e))?,
385 ),
386 None => None,
387 };
388 let parent_id = args.get("parent_id").and_then(|v| v.as_i64()).map(Some);
389
390 let ctx = ProjectContext::load_or_init()
391 .await
392 .map_err(|e| format!("Failed to load project context: {}", e))?;
393
394 let task_mgr = TaskManager::new(&ctx.pool);
395 let task = task_mgr
396 .update_task(task_id, name, spec, parent_id, status, complexity, priority)
397 .await
398 .map_err(|e| format!("Failed to update task: {}", e))?;
399
400 serde_json::to_value(&task).map_err(|e| format!("Serialization error: {}", e))
401}
402
403async fn handle_task_list(args: Value) -> Result<Value, String> {
404 let status = args.get("status").and_then(|v| v.as_str());
405 let parent = args.get("parent").and_then(|v| v.as_str());
406
407 let parent_opt = parent.map(|p| {
408 if p == "null" {
409 None
410 } else {
411 p.parse::<i64>().ok()
412 }
413 });
414
415 let ctx = ProjectContext::load()
416 .await
417 .map_err(|e| format!("Failed to load project context: {}", e))?;
418
419 let task_mgr = TaskManager::new(&ctx.pool);
420 let tasks = task_mgr
421 .find_tasks(status, parent_opt)
422 .await
423 .map_err(|e| format!("Failed to list tasks: {}", e))?;
424
425 serde_json::to_value(&tasks).map_err(|e| format!("Serialization error: {}", e))
426}
427
428async fn handle_task_find(args: Value) -> Result<Value, String> {
429 eprintln!("⚠️ Warning: 'task_find' is deprecated. Please use 'task_list' instead.");
430 let status = args.get("status").and_then(|v| v.as_str());
431 let parent = args.get("parent").and_then(|v| v.as_str());
432
433 let parent_opt = parent.map(|p| {
434 if p == "null" {
435 None
436 } else {
437 p.parse::<i64>().ok()
438 }
439 });
440
441 let ctx = ProjectContext::load()
442 .await
443 .map_err(|e| format!("Failed to load project context: {}", e))?;
444
445 let task_mgr = TaskManager::new(&ctx.pool);
446 let tasks = task_mgr
447 .find_tasks(status, parent_opt)
448 .await
449 .map_err(|e| format!("Failed to find tasks: {}", e))?;
450
451 serde_json::to_value(&tasks).map_err(|e| format!("Serialization error: {}", e))
452}
453
454async fn handle_task_search(args: Value) -> Result<Value, String> {
455 let query = args
456 .get("query")
457 .and_then(|v| v.as_str())
458 .ok_or("Missing required parameter: query")?;
459
460 let ctx = ProjectContext::load()
461 .await
462 .map_err(|e| format!("Failed to load project context: {}", e))?;
463
464 let task_mgr = TaskManager::new(&ctx.pool);
465 let results = task_mgr
466 .search_tasks(query)
467 .await
468 .map_err(|e| format!("Failed to search tasks: {}", e))?;
469
470 serde_json::to_value(&results).map_err(|e| format!("Serialization error: {}", e))
471}
472
473async fn handle_task_get(args: Value) -> Result<Value, String> {
474 let task_id = args
475 .get("task_id")
476 .and_then(|v| v.as_i64())
477 .ok_or("Missing required parameter: task_id")?;
478
479 let with_events = args
480 .get("with_events")
481 .and_then(|v| v.as_bool())
482 .unwrap_or(false);
483
484 let ctx = ProjectContext::load()
485 .await
486 .map_err(|e| format!("Failed to load project context: {}", e))?;
487
488 let task_mgr = TaskManager::new(&ctx.pool);
489
490 if with_events {
491 let task = task_mgr
492 .get_task_with_events(task_id)
493 .await
494 .map_err(|e| format!("Failed to get task: {}", e))?;
495 serde_json::to_value(&task).map_err(|e| format!("Serialization error: {}", e))
496 } else {
497 let task = task_mgr
498 .get_task(task_id)
499 .await
500 .map_err(|e| format!("Failed to get task: {}", e))?;
501 serde_json::to_value(&task).map_err(|e| format!("Serialization error: {}", e))
502 }
503}
504
505async fn handle_task_context(args: Value) -> Result<Value, String> {
506 let task_id = if let Some(id) = args.get("task_id").and_then(|v| v.as_i64()) {
508 id
509 } else {
510 let ctx = ProjectContext::load()
512 .await
513 .map_err(|e| format!("Failed to load project context: {}", e))?;
514
515 let current_task_id: Option<String> =
516 sqlx::query_scalar("SELECT value FROM workspace_state WHERE key = 'current_task_id'")
517 .fetch_optional(&ctx.pool)
518 .await
519 .map_err(|e| format!("Database error: {}", e))?;
520
521 current_task_id
522 .and_then(|s| s.parse::<i64>().ok())
523 .ok_or_else(|| {
524 "No current task is set and task_id was not provided. \
525 Use task_start or task_switch to set a task first, or provide task_id parameter."
526 .to_string()
527 })?
528 };
529
530 let ctx = ProjectContext::load()
531 .await
532 .map_err(|e| format!("Failed to load project context: {}", e))?;
533
534 let task_mgr = TaskManager::new(&ctx.pool);
535 let context = task_mgr
536 .get_task_context(task_id)
537 .await
538 .map_err(|e| format!("Failed to get task context: {}", e))?;
539
540 serde_json::to_value(&context).map_err(|e| format!("Serialization error: {}", e))
541}
542
543async fn handle_task_delete(args: Value) -> Result<Value, String> {
544 let task_id = args
545 .get("task_id")
546 .and_then(|v| v.as_i64())
547 .ok_or("Missing required parameter: task_id")?;
548
549 let ctx = ProjectContext::load()
550 .await
551 .map_err(|e| format!("Failed to load project context: {}", e))?;
552
553 let task_mgr = TaskManager::new(&ctx.pool);
554 task_mgr
555 .delete_task(task_id)
556 .await
557 .map_err(|e| format!("Failed to delete task: {}", e))?;
558
559 Ok(json!({"success": true, "deleted_task_id": task_id}))
560}
561
562async fn handle_event_add(args: Value) -> Result<Value, String> {
563 let task_id = args.get("task_id").and_then(|v| v.as_i64());
564
565 let event_type = args
566 .get("event_type")
567 .and_then(|v| v.as_str())
568 .ok_or("Missing required parameter: event_type")?;
569
570 let data = args
571 .get("data")
572 .and_then(|v| v.as_str())
573 .ok_or("Missing required parameter: data")?;
574
575 let ctx = ProjectContext::load_or_init()
576 .await
577 .map_err(|e| format!("Failed to load project context: {}", e))?;
578
579 let target_task_id = if let Some(id) = task_id {
581 id
582 } else {
583 let current_task_id: Option<String> =
585 sqlx::query_scalar("SELECT value FROM workspace_state WHERE key = 'current_task_id'")
586 .fetch_optional(&ctx.pool)
587 .await
588 .map_err(|e| format!("Database error: {}", e))?;
589
590 current_task_id
591 .and_then(|s| s.parse::<i64>().ok())
592 .ok_or_else(|| {
593 "No current task is set and task_id was not provided. \
594 Use task_start or task_switch to set a task first."
595 .to_string()
596 })?
597 };
598
599 let event_mgr = EventManager::new(&ctx.pool);
600 let event = event_mgr
601 .add_event(target_task_id, event_type, data)
602 .await
603 .map_err(|e| format!("Failed to add event: {}", e))?;
604
605 serde_json::to_value(&event).map_err(|e| format!("Serialization error: {}", e))
606}
607
608async fn handle_event_list(args: Value) -> Result<Value, String> {
609 let task_id = args
610 .get("task_id")
611 .and_then(|v| v.as_i64())
612 .ok_or("Missing required parameter: task_id")?;
613
614 let limit = args.get("limit").and_then(|v| v.as_i64());
615 let log_type = args
616 .get("type")
617 .and_then(|v| v.as_str())
618 .map(|s| s.to_string());
619 let since = args
620 .get("since")
621 .and_then(|v| v.as_str())
622 .map(|s| s.to_string());
623
624 let ctx = ProjectContext::load()
625 .await
626 .map_err(|e| format!("Failed to load project context: {}", e))?;
627
628 let event_mgr = EventManager::new(&ctx.pool);
629 let events = event_mgr
630 .list_events(task_id, limit, log_type, since)
631 .await
632 .map_err(|e| format!("Failed to list events: {}", e))?;
633
634 serde_json::to_value(&events).map_err(|e| format!("Serialization error: {}", e))
635}
636
637async fn handle_current_task_get(_args: Value) -> Result<Value, String> {
638 let ctx = ProjectContext::load()
639 .await
640 .map_err(|e| format!("Failed to load project context: {}", e))?;
641
642 let workspace_mgr = WorkspaceManager::new(&ctx.pool);
643 let response = workspace_mgr
644 .get_current_task()
645 .await
646 .map_err(|e| format!("Failed to get current task: {}", e))?;
647
648 serde_json::to_value(&response).map_err(|e| format!("Serialization error: {}", e))
649}
650
651async fn handle_report_generate(args: Value) -> Result<Value, String> {
652 let since = args.get("since").and_then(|v| v.as_str()).map(String::from);
653 let status = args
654 .get("status")
655 .and_then(|v| v.as_str())
656 .map(String::from);
657 let filter_name = args
658 .get("filter_name")
659 .and_then(|v| v.as_str())
660 .map(String::from);
661 let filter_spec = args
662 .get("filter_spec")
663 .and_then(|v| v.as_str())
664 .map(String::from);
665 let summary_only = args
666 .get("summary_only")
667 .and_then(|v| v.as_bool())
668 .unwrap_or(true);
669
670 let ctx = ProjectContext::load()
671 .await
672 .map_err(|e| format!("Failed to load project context: {}", e))?;
673
674 let report_mgr = ReportManager::new(&ctx.pool);
675 let report = report_mgr
676 .generate_report(since, status, filter_name, filter_spec, summary_only)
677 .await
678 .map_err(|e| format!("Failed to generate report: {}", e))?;
679
680 serde_json::to_value(&report).map_err(|e| format!("Serialization error: {}", e))
681}
682
683#[cfg(test)]
684#[path = "server_tests.rs"]
685mod tests;