use sim_kernel::{CapabilityName, Expr, Result, ShapeRef, Symbol};
use crate::surface::{public_annotations, stable_mcp_name_text};
use crate::{
McpAnnotation, McpStreamPolicy, McpSurfaceCard, McpSurfaceRole, McpSurfaceSource,
stable_mcp_name,
};
#[derive(Clone)]
pub struct McpNativeCard {
pub subject: Symbol,
pub description: String,
pub input_shape: Option<ShapeRef>,
pub output_shape: Option<ShapeRef>,
pub capabilities: Vec<CapabilityName>,
pub facets: Vec<NativeFacet>,
}
impl McpNativeCard {
pub fn new(subject: Symbol, description: impl Into<String>) -> Self {
Self {
subject,
description: description.into(),
input_shape: None,
output_shape: None,
capabilities: Vec::new(),
facets: Vec::new(),
}
}
pub fn with_shapes(mut self, input: ShapeRef, output: ShapeRef) -> Self {
self.input_shape = Some(input);
self.output_shape = Some(output);
self
}
pub fn with_capability(mut self, capability: CapabilityName) -> Self {
self.capabilities.push(capability);
self
}
pub fn with_facet(mut self, facet: NativeFacet) -> Self {
self.facets.push(facet);
self
}
pub fn exported(mut self, export: McpExportFacet) -> Self {
self.facets.push(NativeFacet::McpExport(export));
self
}
}
#[derive(Clone)]
pub enum NativeFacet {
McpExport(McpExportFacet),
Other {
name: Symbol,
value: Expr,
private: bool,
},
}
#[derive(Clone)]
pub struct McpExportFacet {
pub role: McpSurfaceRole,
pub name: Option<String>,
pub symbol: Option<Symbol>,
pub uri: Option<String>,
pub description: Option<String>,
pub input_shape: Option<ShapeRef>,
pub output_shape: Option<ShapeRef>,
pub annotations: Vec<McpAnnotation>,
pub capabilities: Vec<CapabilityName>,
pub stream_policy: McpStreamPolicy,
}
impl McpExportFacet {
pub fn tool() -> Self {
Self::new(McpSurfaceRole::Tool)
}
pub fn resource() -> Self {
Self::new(McpSurfaceRole::Resource)
}
pub fn prompt() -> Self {
Self::new(McpSurfaceRole::Prompt)
}
pub fn model() -> Self {
Self::new(McpSurfaceRole::Model)
}
pub fn new(role: McpSurfaceRole) -> Self {
Self {
role,
name: None,
symbol: None,
uri: None,
description: None,
input_shape: None,
output_shape: None,
annotations: Vec::new(),
capabilities: Vec::new(),
stream_policy: McpStreamPolicy::None,
}
}
pub fn with_name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn with_symbol(mut self, symbol: Symbol) -> Self {
self.symbol = Some(symbol);
self
}
pub fn with_uri(mut self, uri: impl Into<String>) -> Self {
self.uri = Some(uri.into());
self
}
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
pub fn with_annotation(mut self, annotation: McpAnnotation) -> Self {
self.annotations.push(annotation);
self
}
pub fn with_capability(mut self, capability: CapabilityName) -> Self {
self.capabilities.push(capability);
self
}
pub fn with_stream_policy(mut self, policy: McpStreamPolicy) -> Self {
self.stream_policy = policy;
self
}
}
pub fn mcp_export_facet_name() -> Symbol {
Symbol::new("mcp-export")
}
pub fn mcp_export_operation_symbol() -> Symbol {
Symbol::qualified("mcp", "export")
}
pub fn native_surface_rows(cards: &[McpNativeCard]) -> Result<Vec<McpSurfaceCard>> {
let mut rows = Vec::new();
for card in cards {
for facet in &card.facets {
if let NativeFacet::McpExport(export) = facet {
rows.push(row_from_export(card, export)?);
}
}
}
Ok(rows)
}
fn row_from_export(card: &McpNativeCard, export: &McpExportFacet) -> Result<McpSurfaceCard> {
let symbol = export
.symbol
.clone()
.unwrap_or_else(|| card.subject.clone());
let name = match &export.name {
Some(name) => stable_mcp_name_text(name)?,
None => stable_mcp_name(&symbol)?,
};
let mut capabilities = card.capabilities.clone();
capabilities.extend(export.capabilities.clone());
capabilities.sort();
capabilities.dedup();
Ok(McpSurfaceCard {
id: format!("native:{}:{}", export.role.as_symbol(), card.subject),
source: McpSurfaceSource::NativeCard,
role: export.role.clone(),
name,
symbol: Some(symbol),
uri: Some(
export
.uri
.clone()
.unwrap_or_else(|| format!("sim://{}", card.subject)),
),
description: export
.description
.clone()
.unwrap_or_else(|| card.description.clone()),
input_shape: export
.input_shape
.clone()
.or_else(|| card.input_shape.clone()),
output_shape: export
.output_shape
.clone()
.or_else(|| card.output_shape.clone()),
annotations: public_annotations(&export.annotations),
capabilities,
stream_policy: export.stream_policy.clone(),
})
}