Skip to main content

qualia_client_core/
qapp_mcp.rs

1use crate::api::{
2    inspect_installed_qapp_readiness, list_installed_qapps, list_qapp_update_offers,
3    load_installed_qapp_package,
4};
5use crate::qapp_registry::QappPackageManifest;
6use serde::Serialize;
7
8#[derive(Debug, Clone, Serialize)]
9struct QappCatalogueEntry {
10    name: String,
11    version: String,
12    did: String,
13    display_name: String,
14    category: String,
15    preferred_launch_mode: String,
16    launch_modes: Vec<String>,
17    supports_offline: bool,
18    required_shapes: Vec<String>,
19    required_ontologies: Vec<String>,
20    required_models: Vec<String>,
21    ui_surfaces: Vec<String>,
22    entrypoints: Vec<String>,
23    notes: Vec<String>,
24}
25
26#[derive(Debug, Clone, Serialize)]
27struct QappHostSurfaceSchema {
28    host_shell: String,
29    package_manifest: String,
30    layout_strategies: Vec<&'static str>,
31    presentation_modes: Vec<&'static str>,
32    coordinate_spaces: Vec<&'static str>,
33    layer_behaviors: Vec<&'static str>,
34    theme_scopes: Vec<&'static str>,
35    manifest_surfaces: Vec<&'static str>,
36    mcp_tools: Vec<&'static str>,
37}
38
39fn package_did(manifest: &QappPackageManifest) -> String {
40    manifest
41        .x_qualia
42        .as_ref()
43        .and_then(|extension| {
44            (!extension.app_id.trim().is_empty()).then_some(extension.app_id.clone())
45        })
46        .unwrap_or_else(|| {
47            format!(
48                "did:qualia:qapp:{}",
49                manifest.name.to_lowercase().replace(' ', "-")
50            )
51        })
52}
53
54fn catalogue_entry(manifest: &QappPackageManifest) -> QappCatalogueEntry {
55    let extension = manifest.x_qualia.clone().unwrap_or_default();
56    let mut entrypoints = extension.entrypoints.keys().cloned().collect::<Vec<_>>();
57    entrypoints.sort();
58
59    QappCatalogueEntry {
60        name: manifest.name.clone(),
61        version: manifest.version.clone(),
62        did: package_did(manifest),
63        display_name: if extension.display_name.is_empty() {
64            manifest.name.clone()
65        } else {
66            extension.display_name
67        },
68        category: extension.category,
69        preferred_launch_mode: extension.preferred_launch_mode,
70        launch_modes: extension.launch_modes,
71        supports_offline: extension.supports_offline,
72        required_shapes: manifest.required_shapes.clone(),
73        required_ontologies: extension.required_ontologies,
74        required_models: extension.required_models,
75        ui_surfaces: extension.ui_surfaces,
76        entrypoints,
77        notes: extension.notes,
78    }
79}
80
81pub fn list_qapp_catalogue_json() -> Result<String, String> {
82    let mut installed = list_installed_qapps();
83    installed.sort();
84
85    let mut entries = Vec::new();
86    for qapp_name in installed {
87        let manifest = load_installed_qapp_package(&qapp_name)?;
88        entries.push(catalogue_entry(&manifest));
89    }
90
91    serde_json::to_string(&entries).map_err(|e| e.to_string())
92}
93
94pub fn get_qapp_manifest_json(qapp_name: &str) -> Result<String, String> {
95    let manifest = load_installed_qapp_package(qapp_name)?;
96    serde_json::to_string(&manifest).map_err(|e| e.to_string())
97}
98
99pub fn inspect_qapp_readiness_json(qapp_name: &str) -> Result<String, String> {
100    inspect_installed_qapp_readiness(qapp_name.to_string())
101}
102
103pub fn list_qapp_updates_json() -> Result<String, String> {
104    list_qapp_update_offers()
105}
106
107pub fn describe_qapp_surface_schema_json() -> Result<String, String> {
108    let schema = QappHostSurfaceSchema {
109        host_shell: "webizen-studio".to_string(),
110        package_manifest: "qapp.json".to_string(),
111        layout_strategies: vec!["PointGrid", "CssGrid", "FlexBox", "Masonry"],
112        presentation_modes: vec!["GridBound", "NodeRelational", "Spatial"],
113        coordinate_spaces: vec!["GlobalCartesian", "RelativeAnchored"],
114        layer_behaviors: vec!["Docked", "FloatingOverlay", "ModalOverlay", "FullCanvas"],
115        theme_scopes: vec!["environment", "app", "page", "module"],
116        manifest_surfaces: vec![
117            "static-web",
118            "wasm-local",
119            "online-daemon-aware",
120            "native-dioxus-pane",
121        ],
122        mcp_tools: vec![
123            "list_qapps",
124            "get_qapp_manifest",
125            "inspect_qapp_readiness",
126            "list_qapp_updates",
127            "describe_qapp_surface_schema",
128            "query_sparql",
129            "computational_geometry",
130        ],
131    };
132    serde_json::to_string(&schema).map_err(|e| e.to_string())
133}