Skip to main content

task_graph_mcp/resources/
workflows.rs

1//! Workflow resources - expose available workflows and their metadata via MCP resources.
2//!
3//! These resources allow agents to discover available workflows at runtime,
4//! including descriptions of what each workflow is designed for.
5
6use crate::config::workflows::WorkflowsConfig;
7use anyhow::Result;
8use serde_json::{Value, json};
9
10/// List all available workflows with their metadata.
11pub fn list_workflows(workflows: &WorkflowsConfig) -> Result<Value> {
12    let mut workflow_list: Vec<Value> = Vec::new();
13
14    for (name, config) in &workflows.named_workflows {
15        let source = config.source_file.as_ref().map(|p| p.display().to_string());
16
17        workflow_list.push(json!({
18            "name": name,
19            "description": config.description,
20            "source_file": source,
21            "states": config.states.keys().collect::<Vec<_>>(),
22            "phases": config.phases.keys().collect::<Vec<_>>(),
23        }));
24    }
25
26    // Sort by name for consistent output
27    workflow_list.sort_by(|a, b| {
28        a.get("name")
29            .and_then(|v| v.as_str())
30            .unwrap_or("")
31            .cmp(b.get("name").and_then(|v| v.as_str()).unwrap_or(""))
32    });
33
34    // Include info about default workflow if configured
35    let default_workflow = workflows.default_workflow_key.as_ref();
36
37    Ok(json!({
38        "workflows": workflow_list,
39        "default_workflow": default_workflow,
40        "count": workflows.named_workflows.len(),
41    }))
42}
43
44/// Get detailed information about a specific workflow.
45pub fn get_workflow(workflows: &WorkflowsConfig, name: &str) -> Result<Value> {
46    let config = workflows
47        .named_workflows
48        .get(name)
49        .ok_or_else(|| anyhow::anyhow!("Workflow '{}' not found", name))?;
50
51    let source = config.source_file.as_ref().map(|p| p.display().to_string());
52
53    // Build state details
54    let states: Vec<Value> = config
55        .states
56        .iter()
57        .map(|(state_name, state)| {
58            json!({
59                "name": state_name,
60                "exits": state.exits,
61                "timed": state.timed,
62                "has_enter_prompt": state.prompts.enter.is_some(),
63                "has_exit_prompt": state.prompts.exit.is_some(),
64            })
65        })
66        .collect();
67
68    // Build phase details
69    let phases: Vec<Value> = config
70        .phases
71        .iter()
72        .map(|(phase_name, phase)| {
73            json!({
74                "name": phase_name,
75                "has_enter_prompt": phase.prompts.enter.is_some(),
76                "has_exit_prompt": phase.prompts.exit.is_some(),
77            })
78        })
79        .collect();
80
81    Ok(json!({
82        "name": name,
83        "description": config.description,
84        "source_file": source,
85        "settings": {
86            "initial_state": config.settings.initial_state,
87            "disconnect_state": config.settings.disconnect_state,
88            "blocking_states": config.settings.blocking_states,
89        },
90        "states": states,
91        "phases": phases,
92        "combo_count": config.combos.len(),
93    }))
94}