Skip to main content

mcp_skill_framework/
describe.rs

1//! Human-readable rendering of skills and families — "the description" layer.
2//!
3//! [`render_skill`] turns one [`Skill`] into a plain-text block: its
4//! description, use cases, worked examples, validation rules, and pretty-
5//! printed argument schema. [`render_family`] does the same for a
6//! [`FamilyMeta`]. These power an on-demand introspection tool (commonly named
7//! `describe_skill` / `describe_family`) so a model can look up one tool's
8//! exact shape after the initial `tools/list` — handy when a host truncates
9//! the catalog, or when the model wants to double-check arguments before a
10//! call.
11//!
12//! Both renderers accept an optional `gating` line so the host can splice in
13//! application-specific state ("`[filesystem].enabled = true`") without this
14//! crate needing to know anything about configuration.
15
16use std::fmt::Write;
17
18use crate::capability::SkillCapability;
19use crate::family::FamilyMeta;
20use crate::skill::Skill;
21use crate::validation::rules_to_json;
22
23/// Render one skill as a plain-text description block.
24///
25/// `family` is the family name this tool belongs to, if known. `gating` is an
26/// optional application-specific line (e.g. config state) spliced in near the
27/// top. Pass `None` for either to omit it.
28pub fn render_skill<S: 'static>(
29    skill: &dyn Skill<S>,
30    family: Option<&str>,
31    gating: Option<&str>,
32) -> String {
33    let mut out = String::new();
34    let _ = writeln!(out, "Tool: {}", skill.name());
35    if let Some(f) = family {
36        let _ = writeln!(out, "Family: {f}");
37    }
38    if let Some(g) = gating {
39        let _ = writeln!(out, "{g}");
40    }
41    let _ = writeln!(out);
42    let _ = writeln!(out, "Description:");
43    let _ = writeln!(out, "  {}", skill.description());
44
45    let use_cases = skill.use_cases();
46    if !use_cases.is_empty() {
47        let _ = writeln!(out);
48        let _ = writeln!(out, "Use cases:");
49        for uc in use_cases {
50            let _ = writeln!(out, "  - {uc}");
51        }
52    }
53
54    let examples = skill.examples();
55    if !examples.is_empty() {
56        let _ = writeln!(out);
57        let _ = writeln!(out, "Examples:");
58        for (i, ex) in examples.iter().enumerate() {
59            let _ = writeln!(out, "  {}. {}", i + 1, ex.title);
60            let _ = writeln!(out, "     args: {}", ex.args);
61            if let Some(note) = ex.note {
62                let _ = writeln!(out, "     note: {note}");
63            }
64        }
65    }
66
67    let rules = skill.validation_rules();
68    if !rules.is_empty() {
69        let _ = writeln!(out);
70        let _ = writeln!(out, "Validation rules:");
71        let rules_json = rules_to_json(rules);
72        let rules_pretty = serde_json::to_string_pretty(&rules_json)
73            .unwrap_or_else(|_| "<could not serialize rules>".into());
74        let _ = writeln!(out, "{rules_pretty}");
75    }
76
77    let schema_json = serde_json::to_value(skill.schema().as_ref())
78        .ok()
79        .and_then(|v| serde_json::to_string_pretty(&v).ok())
80        .unwrap_or_else(|| "<could not serialize schema>".into());
81    let _ = writeln!(out);
82    let _ = writeln!(out, "Argument schema (JSON):");
83    let _ = writeln!(out, "{schema_json}");
84
85    out
86}
87
88/// Render one family as a plain-text description block: its summary,
89/// capability state, the tools it contributes, and a worked flow if it has
90/// one. `gating` is an optional application-specific line spliced in after
91/// the capability line.
92pub fn render_family(fam: &dyn FamilyMeta, gating: Option<&str>) -> String {
93    let mut out = String::new();
94    let _ = writeln!(out, "Family: {}", fam.family());
95    let _ = writeln!(out, "Description:");
96    let _ = writeln!(out, "  {}", fam.description());
97
98    let cap_line = match fam.check_capability() {
99        SkillCapability::Ready => "Capability: Ready".to_string(),
100        SkillCapability::Unavailable { reason, hint } => {
101            let mut s = format!("Capability: Unavailable — {reason}");
102            if let Some(h) = hint {
103                s.push_str(&format!("\n  Hint: {h}"));
104            }
105            s
106        }
107    };
108    let _ = writeln!(out, "{cap_line}");
109
110    if let Some(g) = gating {
111        let _ = writeln!(out, "{g}");
112    }
113
114    let tools = fam.tools();
115    let _ = writeln!(out);
116    let _ = writeln!(out, "Tools ({}):", tools.len());
117    for t in &tools {
118        let _ = writeln!(out, "  - {t}");
119    }
120
121    if let Some(flow) = fam.example_flow() {
122        let _ = writeln!(out);
123        let _ = writeln!(out, "Example flow:");
124        for line in flow.lines() {
125            let _ = writeln!(out, "  {line}");
126        }
127    }
128
129    out
130}