-
-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathserver_feature.rs
More file actions
184 lines (163 loc) · 5.66 KB
/
Copy pathserver_feature.rs
File metadata and controls
184 lines (163 loc) · 5.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
//! Server feature discovery commands
//!
//! IPC commands for querying discovered MCP features (tools, prompts, resources).
use mcpmux_storage::{FeatureType, ServerFeature, ServerFeatureRepository};
use serde::Serialize;
use tauri::State;
use crate::state::AppState;
/// Response for server feature listing
#[derive(Debug, Serialize)]
pub struct ServerFeatureResponse {
pub id: String,
pub space_id: String,
pub server_id: String,
pub feature_type: String,
pub feature_name: String,
pub display_name: Option<String>,
pub description: Option<String>,
pub input_schema: Option<serde_json::Value>,
pub discovered_at: String,
pub last_seen_at: String,
pub is_available: bool,
}
impl From<ServerFeature> for ServerFeatureResponse {
fn from(f: ServerFeature) -> Self {
Self {
id: f.id,
space_id: f.space_id,
server_id: f.server_id,
feature_type: f.feature_type.as_str().to_string(),
feature_name: f.feature_name,
display_name: f.display_name,
description: f.description,
input_schema: f.raw_json, // Use raw_json now
discovered_at: f.discovered_at.to_rfc3339(),
last_seen_at: f.last_seen_at.to_rfc3339(),
is_available: f.is_available,
}
}
}
/// List all features for a space (only available features by default).
#[tauri::command]
pub async fn list_server_features(
space_id: String,
include_unavailable: Option<bool>,
state: State<'_, AppState>,
) -> Result<Vec<ServerFeatureResponse>, String> {
let features = state
.server_feature_repository
.list_by_space(&space_id)
.await
.map_err(|e| e.to_string())?;
// Filter to only available features unless explicitly requested
let filtered = if include_unavailable.unwrap_or(false) {
features
} else {
features.into_iter().filter(|f| f.is_available).collect()
};
Ok(filtered.into_iter().map(Into::into).collect())
}
/// List features for a specific server in a space (only available by default).
#[tauri::command]
pub async fn list_server_features_by_server(
space_id: String,
server_id: String,
include_unavailable: Option<bool>,
state: State<'_, AppState>,
) -> Result<Vec<ServerFeatureResponse>, String> {
let features = state
.server_feature_repository
.list_by_server(&space_id, &server_id)
.await
.map_err(|e| e.to_string())?;
let filtered = if include_unavailable.unwrap_or(false) {
features
} else {
features.into_iter().filter(|f| f.is_available).collect()
};
Ok(filtered.into_iter().map(Into::into).collect())
}
/// List features by type for a server (only available by default).
#[tauri::command]
pub async fn list_server_features_by_type(
space_id: String,
server_id: String,
feature_type: String,
include_unavailable: Option<bool>,
state: State<'_, AppState>,
) -> Result<Vec<ServerFeatureResponse>, String> {
let ft = FeatureType::parse(&feature_type)
.ok_or_else(|| format!("Invalid feature type: {}", feature_type))?;
let features = state
.server_feature_repository
.list_by_type(&space_id, &server_id, ft)
.await
.map_err(|e| e.to_string())?;
let filtered = if include_unavailable.unwrap_or(false) {
features
} else {
features.into_iter().filter(|f| f.is_available).collect()
};
Ok(filtered.into_iter().map(Into::into).collect())
}
/// Get a specific feature by ID.
#[tauri::command]
pub async fn get_server_feature(
id: String,
state: State<'_, AppState>,
) -> Result<Option<ServerFeatureResponse>, String> {
let feature = state
.server_feature_repository
.get(&id)
.await
.map_err(|e| e.to_string())?;
Ok(feature.map(Into::into))
}
/// Seed server features for E2E testing.
///
/// Accepts an array of feature definitions and upserts them into the database.
/// This is intended for screenshot capture and E2E tests where servers aren't
/// actually connected but we need realistic feature data in the UI.
#[tauri::command]
pub async fn seed_server_features(
features: Vec<SeedFeatureInput>,
state: State<'_, AppState>,
) -> Result<Vec<String>, String> {
let storage_features: Vec<ServerFeature> = features
.into_iter()
.map(|f| {
let mut sf = match f.feature_type.as_str() {
"tool" => ServerFeature::new_tool(&f.space_id, &f.server_id, &f.feature_name),
"prompt" => ServerFeature::new_prompt(&f.space_id, &f.server_id, &f.feature_name),
"resource" => {
ServerFeature::new_resource(&f.space_id, &f.server_id, &f.feature_name)
}
_ => ServerFeature::new_tool(&f.space_id, &f.server_id, &f.feature_name),
};
if let Some(dn) = f.display_name {
sf = sf.with_display_name(dn);
}
if let Some(desc) = f.description {
sf = sf.with_description(desc);
}
sf
})
.collect();
let ids: Vec<String> = storage_features.iter().map(|f| f.id.clone()).collect();
state
.server_feature_repository
.upsert_many(&storage_features)
.await
.map_err(|e| format!("Failed to seed features: {e}"))?;
Ok(ids)
}
/// Input for seeding a server feature (E2E testing).
#[derive(Debug, serde::Deserialize)]
pub struct SeedFeatureInput {
pub space_id: String,
pub server_id: String,
pub feature_type: String,
pub feature_name: String,
pub display_name: Option<String>,
pub description: Option<String>,
}