Skip to main content

sim_lib_mcp/
native.rs

1use sim_kernel::{CapabilityName, Expr, Result, ShapeRef, Symbol};
2
3use crate::surface::{public_annotations, stable_mcp_name_text};
4use crate::{
5    McpAnnotation, McpStreamPolicy, McpSurfaceCard, McpSurfaceRole, McpSurfaceSource,
6    stable_mcp_name,
7};
8
9/// Native browse card describing one runtime subject and its MCP facets.
10#[derive(Clone)]
11pub struct McpNativeCard {
12    /// Runtime symbol the card describes.
13    pub subject: Symbol,
14    /// Human-readable description.
15    pub description: String,
16    /// Default input shape applied to exported facets.
17    pub input_shape: Option<ShapeRef>,
18    /// Default output shape applied to exported facets.
19    pub output_shape: Option<ShapeRef>,
20    /// Capabilities required to invoke the subject.
21    pub capabilities: Vec<CapabilityName>,
22    /// Facets attached to the card, including MCP exports.
23    pub facets: Vec<NativeFacet>,
24}
25
26impl McpNativeCard {
27    /// Creates a card for `subject` with the given `description`.
28    pub fn new(subject: Symbol, description: impl Into<String>) -> Self {
29        Self {
30            subject,
31            description: description.into(),
32            input_shape: None,
33            output_shape: None,
34            capabilities: Vec::new(),
35            facets: Vec::new(),
36        }
37    }
38
39    /// Returns the card with default input and output shapes set.
40    pub fn with_shapes(mut self, input: ShapeRef, output: ShapeRef) -> Self {
41        self.input_shape = Some(input);
42        self.output_shape = Some(output);
43        self
44    }
45
46    /// Returns the card with `capability` added to its requirements.
47    pub fn with_capability(mut self, capability: CapabilityName) -> Self {
48        self.capabilities.push(capability);
49        self
50    }
51
52    /// Returns the card with `facet` appended.
53    pub fn with_facet(mut self, facet: NativeFacet) -> Self {
54        self.facets.push(facet);
55        self
56    }
57
58    /// Returns the card with `export` appended as an MCP export facet.
59    pub fn exported(mut self, export: McpExportFacet) -> Self {
60        self.facets.push(NativeFacet::McpExport(export));
61        self
62    }
63}
64
65/// A facet attached to an [`McpNativeCard`].
66#[derive(Clone)]
67pub enum NativeFacet {
68    /// An MCP export that projects onto the surface.
69    McpExport(McpExportFacet),
70    /// An arbitrary named facet carried on the card.
71    Other {
72        /// Facet name.
73        name: Symbol,
74        /// Facet value.
75        value: Expr,
76        /// Whether the facet is internal-only.
77        private: bool,
78    },
79}
80
81/// Describes how a native card subject is exported onto the MCP surface.
82#[derive(Clone)]
83pub struct McpExportFacet {
84    /// MCP role of the exported row.
85    pub role: McpSurfaceRole,
86    /// Explicit MCP name; defaults to the subject when absent.
87    pub name: Option<String>,
88    /// Backing symbol; defaults to the card subject when absent.
89    pub symbol: Option<Symbol>,
90    /// Resource URI override.
91    pub uri: Option<String>,
92    /// Description override.
93    pub description: Option<String>,
94    /// Input shape override.
95    pub input_shape: Option<ShapeRef>,
96    /// Output shape override.
97    pub output_shape: Option<ShapeRef>,
98    /// Annotations attached to the exported row.
99    pub annotations: Vec<McpAnnotation>,
100    /// Extra capabilities required by the exported row.
101    pub capabilities: Vec<CapabilityName>,
102    /// Streaming behavior advertised by the exported row.
103    pub stream_policy: McpStreamPolicy,
104}
105
106impl McpExportFacet {
107    /// Builds a tool export facet.
108    pub fn tool() -> Self {
109        Self::new(McpSurfaceRole::Tool)
110    }
111
112    /// Builds a resource export facet.
113    pub fn resource() -> Self {
114        Self::new(McpSurfaceRole::Resource)
115    }
116
117    /// Builds a prompt export facet.
118    pub fn prompt() -> Self {
119        Self::new(McpSurfaceRole::Prompt)
120    }
121
122    /// Builds a model export facet.
123    pub fn model() -> Self {
124        Self::new(McpSurfaceRole::Model)
125    }
126
127    /// Builds an export facet for `role` with all overrides unset.
128    pub fn new(role: McpSurfaceRole) -> Self {
129        Self {
130            role,
131            name: None,
132            symbol: None,
133            uri: None,
134            description: None,
135            input_shape: None,
136            output_shape: None,
137            annotations: Vec::new(),
138            capabilities: Vec::new(),
139            stream_policy: McpStreamPolicy::None,
140        }
141    }
142
143    /// Returns the facet with an explicit MCP `name`.
144    pub fn with_name(mut self, name: impl Into<String>) -> Self {
145        self.name = Some(name.into());
146        self
147    }
148
149    /// Returns the facet with an explicit backing `symbol`.
150    pub fn with_symbol(mut self, symbol: Symbol) -> Self {
151        self.symbol = Some(symbol);
152        self
153    }
154
155    /// Returns the facet with an explicit resource `uri`.
156    pub fn with_uri(mut self, uri: impl Into<String>) -> Self {
157        self.uri = Some(uri.into());
158        self
159    }
160
161    /// Returns the facet with an explicit `description`.
162    pub fn with_description(mut self, description: impl Into<String>) -> Self {
163        self.description = Some(description.into());
164        self
165    }
166
167    /// Returns the facet with `annotation` appended.
168    pub fn with_annotation(mut self, annotation: McpAnnotation) -> Self {
169        self.annotations.push(annotation);
170        self
171    }
172
173    /// Returns the facet with `capability` added to its requirements.
174    pub fn with_capability(mut self, capability: CapabilityName) -> Self {
175        self.capabilities.push(capability);
176        self
177    }
178
179    /// Returns the facet with its stream `policy` set.
180    pub fn with_stream_policy(mut self, policy: McpStreamPolicy) -> Self {
181        self.stream_policy = policy;
182        self
183    }
184}
185
186/// Returns the facet name used to mark MCP exports on native cards.
187pub fn mcp_export_facet_name() -> Symbol {
188    Symbol::new("mcp-export")
189}
190
191/// Returns the operation symbol identifying the MCP export operation.
192pub fn mcp_export_operation_symbol() -> Symbol {
193    Symbol::qualified("mcp", "export")
194}
195
196/// Projects the MCP export facets of `cards` into surface rows.
197pub fn native_surface_rows(cards: &[McpNativeCard]) -> Result<Vec<McpSurfaceCard>> {
198    let mut rows = Vec::new();
199    for card in cards {
200        for facet in &card.facets {
201            if let NativeFacet::McpExport(export) = facet {
202                rows.push(row_from_export(card, export)?);
203            }
204        }
205    }
206    Ok(rows)
207}
208
209fn row_from_export(card: &McpNativeCard, export: &McpExportFacet) -> Result<McpSurfaceCard> {
210    let symbol = export
211        .symbol
212        .clone()
213        .unwrap_or_else(|| card.subject.clone());
214    let name = match &export.name {
215        Some(name) => stable_mcp_name_text(name)?,
216        None => stable_mcp_name(&symbol)?,
217    };
218    let mut capabilities = card.capabilities.clone();
219    capabilities.extend(export.capabilities.clone());
220    capabilities.sort();
221    capabilities.dedup();
222
223    Ok(McpSurfaceCard {
224        id: format!("native:{}:{}", export.role.as_symbol(), card.subject),
225        source: McpSurfaceSource::NativeCard,
226        role: export.role.clone(),
227        name,
228        symbol: Some(symbol),
229        uri: Some(
230            export
231                .uri
232                .clone()
233                .unwrap_or_else(|| format!("sim://{}", card.subject)),
234        ),
235        description: export
236            .description
237            .clone()
238            .unwrap_or_else(|| card.description.clone()),
239        input_shape: export
240            .input_shape
241            .clone()
242            .or_else(|| card.input_shape.clone()),
243        output_shape: export
244            .output_shape
245            .clone()
246            .or_else(|| card.output_shape.clone()),
247        annotations: public_annotations(&export.annotations),
248        capabilities,
249        stream_policy: export.stream_policy.clone(),
250    })
251}