systemprompt-mcp 0.24.0

Native Model Context Protocol (MCP) implementation for systemprompt.io. Orchestration, per-server OAuth2, RBAC middleware, and tool-call governance — the core of the AI governance pipeline.
Documentation
//! Registry of `UiRenderer` implementations keyed by artifact type.
//!
//! Copyright (c) systemprompt.io — Business Source License 1.1.
//! See <https://systemprompt.io> for licensing details.

use super::{UiRenderer, UiResource};
use crate::error::{McpDomainError, McpDomainResult};
use std::collections::HashMap;
use std::sync::Arc;
use systemprompt_models::a2a::{Artifact, Part};
use systemprompt_models::artifacts::CliArtifact;

pub struct UiRendererRegistry {
    renderers: HashMap<String, Arc<dyn UiRenderer>>,
}

impl Default for UiRendererRegistry {
    fn default() -> Self {
        Self::new()
    }
}

impl UiRendererRegistry {
    pub fn new() -> Self {
        Self {
            renderers: HashMap::new(),
        }
    }

    pub fn register<R: UiRenderer + 'static>(&mut self, renderer: R) {
        let artifact_type = renderer.artifact_type().to_string();
        self.renderers.insert(artifact_type, Arc::new(renderer));
    }

    pub fn get(&self, artifact_type: &str) -> Option<Arc<dyn UiRenderer>> {
        self.renderers.get(artifact_type).cloned()
    }

    pub fn supports(&self, artifact_type: &str) -> bool {
        self.renderers.contains_key(artifact_type)
    }

    pub fn supported_types(&self) -> Vec<&str> {
        self.renderers.keys().map(String::as_str).collect()
    }

    pub async fn render(&self, artifact: &Artifact) -> McpDomainResult<UiResource> {
        let artifact_type = resolve_artifact_type(artifact);

        let renderer = self.get(artifact_type).ok_or_else(|| {
            McpDomainError::Internal(format!(
                "No renderer registered for artifact type: {artifact_type}"
            ))
        })?;

        renderer.render(artifact).await
    }
}

pub fn resolve_artifact_type(artifact: &Artifact) -> &str {
    let declared = artifact.metadata.artifact_type.as_str();
    if declared != CliArtifact::ENVELOPE_TYPE_STR {
        return declared;
    }

    artifact
        .parts
        .iter()
        .find_map(|part| match part {
            Part::Data(data) => data
                .data
                .get("artifact_type")
                .or_else(|| data.data.get("x-artifact-type"))
                .and_then(serde_json::Value::as_str),
            Part::Text(_) | Part::File(_) => None,
        })
        .unwrap_or(declared)
}

impl std::fmt::Debug for UiRendererRegistry {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("UiRendererRegistry")
            .field("registered_types", &self.supported_types())
            .finish()
    }
}

pub fn create_default_registry() -> UiRendererRegistry {
    let mut registry = UiRendererRegistry::new();

    registry.register(super::templates::TableRenderer::new());
    registry.register(super::templates::ChartRenderer::new());
    registry.register(super::templates::TextRenderer::new());
    registry.register(super::templates::CopyPasteTextRenderer::new());
    registry.register(super::templates::FormRenderer::new());
    registry.register(super::templates::ListRenderer::new());
    registry.register(super::templates::ImageRenderer::new());
    registry.register(super::templates::AudioRenderer::new());
    registry.register(super::templates::VideoRenderer::new());
    registry.register(super::templates::DashboardRenderer::new());
    registry.register(super::templates::PresentationCardRenderer::new());
    registry.register(super::templates::MessageRenderer::new());

    registry
}