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_search" => handle_task_search(params.arguments).await,
194 "task_get" => handle_task_get(params.arguments).await,
195 "task_context" => handle_task_context(params.arguments).await,
196 "task_delete" => handle_task_delete(params.arguments).await,
197 "event_add" => handle_event_add(params.arguments).await,
198 "event_list" => handle_event_list(params.arguments).await,
199 "current_task_get" => handle_current_task_get(params.arguments).await,
200 "report_generate" => handle_report_generate(params.arguments).await,
201 _ => Err(format!("Unknown tool: {}", params.name)),
202 }?;
203
204 Ok(json!({
205 "content": [{
206 "type": "text",
207 "text": serde_json::to_string_pretty(&result)
208 .unwrap_or_else(|_| "{}".to_string())
209 }]
210 }))
211}
212
213async fn handle_task_add(args: Value) -> Result<Value, String> {
216 let name = args
217 .get("name")
218 .and_then(|v| v.as_str())
219 .ok_or("Missing required parameter: name")?;
220
221 let spec = args.get("spec").and_then(|v| v.as_str());
222 let parent_id = args.get("parent_id").and_then(|v| v.as_i64());
223
224 let ctx = ProjectContext::load_or_init()
225 .await
226 .map_err(|e| format!("Failed to load project context: {}", e))?;
227
228 let task_mgr = TaskManager::new(&ctx.pool);
229 let task = task_mgr
230 .add_task(name, spec, parent_id)
231 .await
232 .map_err(|e| format!("Failed to add task: {}", e))?;
233
234 serde_json::to_value(&task).map_err(|e| format!("Serialization error: {}", e))
235}
236
237async fn handle_task_add_dependency(args: Value) -> Result<Value, String> {
238 let blocked_task_id = args
239 .get("blocked_task_id")
240 .and_then(|v| v.as_i64())
241 .ok_or("Missing required parameter: blocked_task_id")?;
242
243 let blocking_task_id = args
244 .get("blocking_task_id")
245 .and_then(|v| v.as_i64())
246 .ok_or("Missing required parameter: blocking_task_id")?;
247
248 let ctx = ProjectContext::load_or_init()
249 .await
250 .map_err(|e| format!("Failed to load project context: {}", e))?;
251
252 let dependency =
253 crate::dependencies::add_dependency(&ctx.pool, blocking_task_id, blocked_task_id)
254 .await
255 .map_err(|e| format!("Failed to add dependency: {}", e))?;
256
257 serde_json::to_value(&dependency).map_err(|e| format!("Serialization error: {}", e))
258}
259
260async fn handle_task_start(args: Value) -> Result<Value, String> {
261 let task_id = args
262 .get("task_id")
263 .and_then(|v| v.as_i64())
264 .ok_or("Missing required parameter: task_id")?;
265
266 let with_events = args
267 .get("with_events")
268 .and_then(|v| v.as_bool())
269 .unwrap_or(true);
270
271 let ctx = ProjectContext::load_or_init()
272 .await
273 .map_err(|e| format!("Failed to load project context: {}", e))?;
274
275 let task_mgr = TaskManager::new(&ctx.pool);
276 let task = task_mgr
277 .start_task(task_id, with_events)
278 .await
279 .map_err(|e| format!("Failed to start task: {}", e))?;
280
281 serde_json::to_value(&task).map_err(|e| format!("Serialization error: {}", e))
282}
283
284async fn handle_task_pick_next(args: Value) -> Result<Value, String> {
285 let _max_count = args.get("max_count").and_then(|v| v.as_i64());
286 let _capacity = args.get("capacity").and_then(|v| v.as_i64());
287
288 let ctx = ProjectContext::load_or_init()
289 .await
290 .map_err(|e| format!("Failed to load project context: {}", e))?;
291
292 let task_mgr = TaskManager::new(&ctx.pool);
293 let response = task_mgr
294 .pick_next()
295 .await
296 .map_err(|e| format!("Failed to pick next task: {}", e))?;
297
298 serde_json::to_value(&response).map_err(|e| format!("Serialization error: {}", e))
299}
300
301async fn handle_task_spawn_subtask(args: Value) -> Result<Value, String> {
302 let name = args
303 .get("name")
304 .and_then(|v| v.as_str())
305 .ok_or("Missing required parameter: name")?;
306
307 let spec = args.get("spec").and_then(|v| v.as_str());
308
309 let ctx = ProjectContext::load_or_init()
310 .await
311 .map_err(|e| format!("Failed to load project context: {}", e))?;
312
313 let task_mgr = TaskManager::new(&ctx.pool);
314 let subtask = task_mgr
315 .spawn_subtask(name, spec)
316 .await
317 .map_err(|e| format!("Failed to spawn subtask: {}", e))?;
318
319 serde_json::to_value(&subtask).map_err(|e| format!("Serialization error: {}", e))
320}
321
322async fn handle_task_switch(args: Value) -> Result<Value, String> {
323 let task_id = args
324 .get("task_id")
325 .and_then(|v| v.as_i64())
326 .ok_or("Missing required parameter: task_id")?;
327
328 let ctx = ProjectContext::load_or_init()
329 .await
330 .map_err(|e| format!("Failed to load project context: {}", e))?;
331
332 let task_mgr = TaskManager::new(&ctx.pool);
333 let task = task_mgr
334 .switch_to_task(task_id)
335 .await
336 .map_err(|e| format!("Failed to switch task: {}", e))?;
337
338 serde_json::to_value(&task).map_err(|e| format!("Serialization error: {}", e))
339}
340
341async fn handle_task_done(args: Value) -> Result<Value, String> {
342 let task_id = args.get("task_id").and_then(|v| v.as_i64());
343
344 let ctx = ProjectContext::load_or_init()
345 .await
346 .map_err(|e| format!("Failed to load project context: {}", e))?;
347
348 let task_mgr = TaskManager::new(&ctx.pool);
349
350 if let Some(id) = task_id {
352 let workspace_mgr = WorkspaceManager::new(&ctx.pool);
353 workspace_mgr
354 .set_current_task(id)
355 .await
356 .map_err(|e| format!("Failed to set current task: {}", e))?;
357 }
358
359 let task = task_mgr
360 .done_task()
361 .await
362 .map_err(|e| format!("Failed to mark task as done: {}", e))?;
363
364 serde_json::to_value(&task).map_err(|e| format!("Serialization error: {}", e))
365}
366
367async fn handle_task_update(args: Value) -> Result<Value, String> {
368 let task_id = args
369 .get("task_id")
370 .and_then(|v| v.as_i64())
371 .ok_or("Missing required parameter: task_id")?;
372
373 let name = args.get("name").and_then(|v| v.as_str());
374 let spec = args.get("spec").and_then(|v| v.as_str());
375 let status = args.get("status").and_then(|v| v.as_str());
376 let complexity = args
377 .get("complexity")
378 .and_then(|v| v.as_i64())
379 .map(|v| v as i32);
380 let priority = match args.get("priority").and_then(|v| v.as_str()) {
381 Some(p) => Some(
382 crate::priority::PriorityLevel::parse_to_int(p)
383 .map_err(|e| format!("Invalid priority: {}", e))?,
384 ),
385 None => None,
386 };
387 let parent_id = args.get("parent_id").and_then(|v| v.as_i64()).map(Some);
388
389 let ctx = ProjectContext::load_or_init()
390 .await
391 .map_err(|e| format!("Failed to load project context: {}", e))?;
392
393 let task_mgr = TaskManager::new(&ctx.pool);
394 let task = task_mgr
395 .update_task(task_id, name, spec, parent_id, status, complexity, priority)
396 .await
397 .map_err(|e| format!("Failed to update task: {}", e))?;
398
399 serde_json::to_value(&task).map_err(|e| format!("Serialization error: {}", e))
400}
401
402async fn handle_task_list(args: Value) -> Result<Value, String> {
403 let status = args.get("status").and_then(|v| v.as_str());
404 let parent = args.get("parent").and_then(|v| v.as_str());
405
406 let parent_opt = parent.map(|p| {
407 if p == "null" {
408 None
409 } else {
410 p.parse::<i64>().ok()
411 }
412 });
413
414 let ctx = ProjectContext::load()
415 .await
416 .map_err(|e| format!("Failed to load project context: {}", e))?;
417
418 let task_mgr = TaskManager::new(&ctx.pool);
419 let tasks = task_mgr
420 .find_tasks(status, parent_opt)
421 .await
422 .map_err(|e| format!("Failed to list tasks: {}", e))?;
423
424 serde_json::to_value(&tasks).map_err(|e| format!("Serialization error: {}", e))
425}
426
427async fn handle_task_search(args: Value) -> Result<Value, String> {
428 let query = args
429 .get("query")
430 .and_then(|v| v.as_str())
431 .ok_or("Missing required parameter: query")?;
432
433 let ctx = ProjectContext::load()
434 .await
435 .map_err(|e| format!("Failed to load project context: {}", e))?;
436
437 let task_mgr = TaskManager::new(&ctx.pool);
438 let results = task_mgr
439 .search_tasks(query)
440 .await
441 .map_err(|e| format!("Failed to search tasks: {}", e))?;
442
443 serde_json::to_value(&results).map_err(|e| format!("Serialization error: {}", e))
444}
445
446async fn handle_task_get(args: Value) -> Result<Value, String> {
447 let task_id = args
448 .get("task_id")
449 .and_then(|v| v.as_i64())
450 .ok_or("Missing required parameter: task_id")?;
451
452 let with_events = args
453 .get("with_events")
454 .and_then(|v| v.as_bool())
455 .unwrap_or(false);
456
457 let ctx = ProjectContext::load()
458 .await
459 .map_err(|e| format!("Failed to load project context: {}", e))?;
460
461 let task_mgr = TaskManager::new(&ctx.pool);
462
463 if with_events {
464 let task = task_mgr
465 .get_task_with_events(task_id)
466 .await
467 .map_err(|e| format!("Failed to get task: {}", e))?;
468 serde_json::to_value(&task).map_err(|e| format!("Serialization error: {}", e))
469 } else {
470 let task = task_mgr
471 .get_task(task_id)
472 .await
473 .map_err(|e| format!("Failed to get task: {}", e))?;
474 serde_json::to_value(&task).map_err(|e| format!("Serialization error: {}", e))
475 }
476}
477
478async fn handle_task_context(args: Value) -> Result<Value, String> {
479 let task_id = if let Some(id) = args.get("task_id").and_then(|v| v.as_i64()) {
481 id
482 } else {
483 let ctx = ProjectContext::load()
485 .await
486 .map_err(|e| format!("Failed to load project context: {}", e))?;
487
488 let current_task_id: Option<String> =
489 sqlx::query_scalar("SELECT value FROM workspace_state WHERE key = 'current_task_id'")
490 .fetch_optional(&ctx.pool)
491 .await
492 .map_err(|e| format!("Database error: {}", e))?;
493
494 current_task_id
495 .and_then(|s| s.parse::<i64>().ok())
496 .ok_or_else(|| {
497 "No current task is set and task_id was not provided. \
498 Use task_start or task_switch to set a task first, or provide task_id parameter."
499 .to_string()
500 })?
501 };
502
503 let ctx = ProjectContext::load()
504 .await
505 .map_err(|e| format!("Failed to load project context: {}", e))?;
506
507 let task_mgr = TaskManager::new(&ctx.pool);
508 let context = task_mgr
509 .get_task_context(task_id)
510 .await
511 .map_err(|e| format!("Failed to get task context: {}", e))?;
512
513 serde_json::to_value(&context).map_err(|e| format!("Serialization error: {}", e))
514}
515
516async fn handle_task_delete(args: Value) -> Result<Value, String> {
517 let task_id = args
518 .get("task_id")
519 .and_then(|v| v.as_i64())
520 .ok_or("Missing required parameter: task_id")?;
521
522 let ctx = ProjectContext::load()
523 .await
524 .map_err(|e| format!("Failed to load project context: {}", e))?;
525
526 let task_mgr = TaskManager::new(&ctx.pool);
527 task_mgr
528 .delete_task(task_id)
529 .await
530 .map_err(|e| format!("Failed to delete task: {}", e))?;
531
532 Ok(json!({"success": true, "deleted_task_id": task_id}))
533}
534
535async fn handle_event_add(args: Value) -> Result<Value, String> {
536 let task_id = args.get("task_id").and_then(|v| v.as_i64());
537
538 let event_type = args
539 .get("event_type")
540 .and_then(|v| v.as_str())
541 .ok_or("Missing required parameter: event_type")?;
542
543 let data = args
544 .get("data")
545 .and_then(|v| v.as_str())
546 .ok_or("Missing required parameter: data")?;
547
548 let ctx = ProjectContext::load_or_init()
549 .await
550 .map_err(|e| format!("Failed to load project context: {}", e))?;
551
552 let target_task_id = if let Some(id) = task_id {
554 id
555 } else {
556 let current_task_id: Option<String> =
558 sqlx::query_scalar("SELECT value FROM workspace_state WHERE key = 'current_task_id'")
559 .fetch_optional(&ctx.pool)
560 .await
561 .map_err(|e| format!("Database error: {}", e))?;
562
563 current_task_id
564 .and_then(|s| s.parse::<i64>().ok())
565 .ok_or_else(|| {
566 "No current task is set and task_id was not provided. \
567 Use task_start or task_switch to set a task first."
568 .to_string()
569 })?
570 };
571
572 let event_mgr = EventManager::new(&ctx.pool);
573 let event = event_mgr
574 .add_event(target_task_id, event_type, data)
575 .await
576 .map_err(|e| format!("Failed to add event: {}", e))?;
577
578 serde_json::to_value(&event).map_err(|e| format!("Serialization error: {}", e))
579}
580
581async fn handle_event_list(args: Value) -> Result<Value, String> {
582 let task_id = args
583 .get("task_id")
584 .and_then(|v| v.as_i64())
585 .ok_or("Missing required parameter: task_id")?;
586
587 let limit = args.get("limit").and_then(|v| v.as_i64());
588 let log_type = args
589 .get("type")
590 .and_then(|v| v.as_str())
591 .map(|s| s.to_string());
592 let since = args
593 .get("since")
594 .and_then(|v| v.as_str())
595 .map(|s| s.to_string());
596
597 let ctx = ProjectContext::load()
598 .await
599 .map_err(|e| format!("Failed to load project context: {}", e))?;
600
601 let event_mgr = EventManager::new(&ctx.pool);
602 let events = event_mgr
603 .list_events(task_id, limit, log_type, since)
604 .await
605 .map_err(|e| format!("Failed to list events: {}", e))?;
606
607 serde_json::to_value(&events).map_err(|e| format!("Serialization error: {}", e))
608}
609
610async fn handle_current_task_get(_args: Value) -> Result<Value, String> {
611 let ctx = ProjectContext::load()
612 .await
613 .map_err(|e| format!("Failed to load project context: {}", e))?;
614
615 let workspace_mgr = WorkspaceManager::new(&ctx.pool);
616 let response = workspace_mgr
617 .get_current_task()
618 .await
619 .map_err(|e| format!("Failed to get current task: {}", e))?;
620
621 serde_json::to_value(&response).map_err(|e| format!("Serialization error: {}", e))
622}
623
624async fn handle_report_generate(args: Value) -> Result<Value, String> {
625 let since = args.get("since").and_then(|v| v.as_str()).map(String::from);
626 let status = args
627 .get("status")
628 .and_then(|v| v.as_str())
629 .map(String::from);
630 let filter_name = args
631 .get("filter_name")
632 .and_then(|v| v.as_str())
633 .map(String::from);
634 let filter_spec = args
635 .get("filter_spec")
636 .and_then(|v| v.as_str())
637 .map(String::from);
638 let summary_only = args
639 .get("summary_only")
640 .and_then(|v| v.as_bool())
641 .unwrap_or(true);
642
643 let ctx = ProjectContext::load()
644 .await
645 .map_err(|e| format!("Failed to load project context: {}", e))?;
646
647 let report_mgr = ReportManager::new(&ctx.pool);
648 let report = report_mgr
649 .generate_report(since, status, filter_name, filter_spec, summary_only)
650 .await
651 .map_err(|e| format!("Failed to generate report: {}", e))?;
652
653 serde_json::to_value(&report).map_err(|e| format!("Serialization error: {}", e))
654}
655
656#[cfg(test)]
657#[path = "server_tests.rs"]
658mod tests;