1use std::collections::BTreeMap;
24
25use serde::{Deserialize, Serialize};
26
27pub use crate::bridge::ids::ManifestSignature;
28use crate::bridge::ids::{
29 LibraryArtifactId, ManagedMcpServerName, PluginId, Sha256Digest, SkillId, SkillName, ToolName,
30 ToolPolicy,
31};
32use crate::bridge::manifest_version::ManifestVersion;
33use crate::services::hooks::{HookCategory, HookEvent};
34use crate::services::plugin::{PluginComponentRef, PluginHooksRef};
35use systemprompt_identifiers::{
36 AgentId, AgentName, HookId, MarketplaceId, McpServerId, TenantId, UserId, ValidatedUrl,
37};
38
39pub const MANIFEST_SCHEMA_VERSION: u32 = 1;
40
41pub const MIN_BRIDGE_VERSION: &str = "0.28.0";
42
43#[must_use]
44pub fn bridge_version_is_supported(reported: &str, floor: &str) -> bool {
45 match (
46 semver::Version::parse(reported),
47 semver::Version::parse(floor),
48 ) {
49 (Ok(reported), Ok(floor)) => reported >= floor,
50 _ => false,
54 }
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct SignedManifestEnvelope {
59 pub payload: String,
60 pub signature: ManifestSignature,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct SignedManifest {
65 #[serde(default)]
66 pub min_schema_version: u32,
67 #[serde(default)]
68 pub min_bridge_version: Option<String>,
69 pub manifest_version: ManifestVersion,
70 pub issued_at: String,
71 pub not_before: String,
72 pub user_id: UserId,
73 pub tenant_id: Option<TenantId>,
74 #[serde(default)]
75 pub user: Option<UserInfo>,
76 pub plugins: Vec<PluginEntry>,
77 #[serde(default)]
78 pub skills: Vec<SkillEntry>,
79 #[serde(default)]
80 pub agents: Vec<AgentEntry>,
81 #[serde(default)]
82 pub hooks: Vec<HookEntry>,
83 pub managed_mcp_servers: Vec<ManagedMcpServer>,
84 pub revocations: Vec<String>,
85 #[serde(default)]
86 pub enabled_hosts: Vec<String>,
87 #[serde(default)]
88 pub host_model_protocols: BTreeMap<String, Vec<String>>,
89 #[serde(default)]
90 pub artifacts: Vec<ArtifactEntry>,
91 #[serde(default)]
92 pub allow_claude_ai_connectors: bool,
93 #[serde(default, skip_serializing_if = "Vec::is_empty")]
94 pub diagnostics: Vec<String>,
95 #[serde(default, skip_serializing_if = "Vec::is_empty")]
96 pub marketplaces: Vec<ManifestMarketplace>,
97}
98
99#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
102pub struct ManifestMarketplace {
103 pub id: MarketplaceId,
104 pub name: String,
105 pub plugin_ids: Vec<PluginId>,
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct UserInfo {
110 pub id: UserId,
111 pub name: String,
112 pub email: String,
113 #[serde(default)]
114 pub display_name: Option<String>,
115 #[serde(default)]
116 pub roles: Vec<String>,
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct PluginEntry {
121 pub id: PluginId,
122 pub version: String,
123 pub sha256: Sha256Digest,
124 pub files: Vec<PluginFile>,
125 #[serde(default)]
126 pub hooks: PluginHooksRef,
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct PluginFile {
131 pub path: String,
132 pub sha256: Sha256Digest,
133 pub size: u64,
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct ArtifactEntry {
140 pub id: LibraryArtifactId,
141 pub name: String,
142 pub description: String,
143 pub version: String,
144 pub mcp_tools: Vec<String>,
145 pub content: String,
146 pub starred: bool,
147 pub sha256: Sha256Digest,
148 #[serde(default, skip_serializing_if = "Vec::is_empty")]
149 pub plugins: Vec<PluginId>,
150}
151
152#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct SkillEntry {
154 pub id: SkillId,
155 pub name: SkillName,
156 pub description: String,
157 pub file_path: String,
158 #[serde(default)]
159 pub tags: Vec<String>,
160 pub sha256: Sha256Digest,
161 pub instructions: String,
162 #[serde(default, skip_serializing_if = "Vec::is_empty")]
163 pub hosts: Vec<String>,
164 #[serde(default, skip_serializing_if = "Vec::is_empty")]
165 pub plugins: Vec<PluginId>,
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize)]
169pub struct AgentEntry {
170 pub id: AgentId,
171 pub name: AgentName,
172 pub display_name: String,
173 pub description: String,
174 pub version: String,
175 pub endpoint: String,
176 pub enabled: bool,
177 pub is_default: bool,
178 pub is_primary: bool,
179 #[serde(default)]
180 pub provider: Option<String>,
181 #[serde(default)]
182 pub model: Option<String>,
183 #[serde(default)]
184 pub mcp_servers: PluginComponentRef,
185 #[serde(default)]
186 pub skills: PluginComponentRef,
187 #[serde(default)]
188 pub tags: Vec<String>,
189 #[serde(default)]
190 pub system_prompt: Option<String>,
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct HookEntry {
195 pub id: HookId,
196 pub name: String,
197 pub description: String,
198 pub version: String,
199 pub event: HookEvent,
200 pub matcher: String,
201 pub command: String,
202 #[serde(default)]
203 pub is_async: bool,
204 pub category: HookCategory,
205 #[serde(default)]
206 pub tags: Vec<String>,
207 pub sha256: Sha256Digest,
208}
209
210#[derive(Debug, Clone, Serialize, Deserialize)]
211#[serde(from = "ManagedMcpServerWire")]
212pub struct ManagedMcpServer {
213 pub id: McpServerId,
214 pub name: ManagedMcpServerName,
215 pub url: ValidatedUrl,
216 #[serde(skip_serializing_if = "Option::is_none")]
217 pub transport: Option<String>,
218 #[serde(skip_serializing_if = "Option::is_none")]
219 pub headers: Option<BTreeMap<String, String>>,
220 #[serde(skip_serializing_if = "Option::is_none")]
221 pub oauth: Option<bool>,
222 #[serde(skip_serializing_if = "Option::is_none")]
223 pub tool_policy: Option<BTreeMap<ToolName, ToolPolicy>>,
224}
225
226#[derive(Deserialize)]
227struct ManagedMcpServerWire {
228 #[serde(default)]
229 id: Option<McpServerId>,
230 name: ManagedMcpServerName,
231 url: ValidatedUrl,
232 #[serde(default)]
233 transport: Option<String>,
234 #[serde(default)]
235 headers: Option<BTreeMap<String, String>>,
236 #[serde(default)]
237 oauth: Option<bool>,
238 #[serde(default)]
239 tool_policy: Option<BTreeMap<ToolName, ToolPolicy>>,
240}
241
242impl From<ManagedMcpServerWire> for ManagedMcpServer {
243 fn from(wire: ManagedMcpServerWire) -> Self {
244 let id = wire
245 .id
246 .unwrap_or_else(|| McpServerId::new(wire.name.as_str()));
247 Self {
248 id,
249 name: wire.name,
250 url: wire.url,
251 transport: wire.transport,
252 headers: wire.headers,
253 oauth: wire.oauth,
254 tool_policy: wire.tool_policy,
255 }
256 }
257}