Skip to main content

systemprompt_models/bridge/
manifest.rs

1//! Signed manifest wire format.
2//!
3//! [`SignedManifest`] is the JSON document the gateway returns from
4//! `GET /v1/bridge/manifest` and the bridge consumes to drive its
5//! plugin / skill / agent / managed-MCP sync. Every public type in
6//! this module is part of that wire contract — the gateway server
7//! (in `crates/entry/api`) emits these structs and the bridge
8//! deserialises them, so any change here is a wire-format change.
9//!
10//! Signing, signature verification, and manifest construction live in
11//! the bridge crate (`bin/bridge/src/gateway/manifest.rs`) alongside
12//! the gateway client. Those layers pull in `ed25519-dalek` and
13//! `serde_jcs` which are not appropriate dependencies for this
14//! foundation crate.
15//!
16//! Copyright (c) systemprompt.io — Business Source License 1.1.
17//! See <https://systemprompt.io> for licensing details.
18
19use std::collections::BTreeMap;
20
21use serde::{Deserialize, Serialize};
22
23pub use crate::bridge::ids::ManifestSignature;
24use crate::bridge::ids::{
25    LibraryArtifactId, ManagedMcpServerName, PluginId, Sha256Digest, SkillId, SkillName, ToolName,
26    ToolPolicy,
27};
28use crate::bridge::manifest_version::ManifestVersion;
29use crate::services::hooks::{HookCategory, HookEvent};
30use crate::services::plugin::{PluginComponentRef, PluginHooksRef};
31use systemprompt_identifiers::{AgentId, AgentName, HookId, TenantId, UserId, ValidatedUrl};
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct SignedManifest {
35    pub manifest_version: ManifestVersion,
36    pub issued_at: String,
37    pub not_before: String,
38    pub user_id: UserId,
39    pub tenant_id: Option<TenantId>,
40    #[serde(default)]
41    pub user: Option<UserInfo>,
42    pub plugins: Vec<PluginEntry>,
43    #[serde(default)]
44    pub skills: Vec<SkillEntry>,
45    #[serde(default)]
46    pub agents: Vec<AgentEntry>,
47    #[serde(default)]
48    pub hooks: Vec<HookEntry>,
49    pub managed_mcp_servers: Vec<ManagedMcpServer>,
50    pub revocations: Vec<String>,
51    #[serde(default)]
52    pub enabled_hosts: Vec<String>,
53    /// Optional per-host wire-protocol filter, keyed by host id. A present
54    /// entry overrides the host's built-in default `accepted_protocols`; an
55    /// empty value means "all models" (no restriction). An absent entry leaves
56    /// the host on its default.
57    #[serde(default)]
58    pub host_model_protocols: BTreeMap<String, Vec<String>>,
59    /// Cowork global-library HTML documents — distinct from the in-chat MCP
60    /// artifacts in [`crate::artifacts`].
61    #[serde(default)]
62    pub artifacts: Vec<ArtifactEntry>,
63    /// Instructs the bridge's Claude Code managed-MCP policy to emit
64    /// `allowAllClaudeAiMcps`, re-allowing claude.ai first-party connectors
65    /// that `managed-mcp.json` would otherwise suppress.
66    #[serde(default)]
67    pub allow_claude_ai_connectors: bool,
68    /// Detached ed25519 signature of the canonicalised payload (every
69    /// field above this one). Always present on the wire even for
70    /// unsigned manifests, where it is the empty string.
71    pub signature: ManifestSignature,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct UserInfo {
76    pub id: UserId,
77    pub name: String,
78    pub email: String,
79    #[serde(default)]
80    pub display_name: Option<String>,
81    #[serde(default)]
82    pub roles: Vec<String>,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct PluginEntry {
87    pub id: PluginId,
88    pub version: String,
89    pub sha256: Sha256Digest,
90    pub files: Vec<PluginFile>,
91    #[serde(default)]
92    pub hooks: PluginHooksRef,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct PluginFile {
97    pub path: String,
98    pub sha256: Sha256Digest,
99    pub size: u64,
100}
101
102/// A Cowork-native library document (raw HTML in the desktop app's Artifacts
103/// library) — not one of the in-chat MCP artifacts in [`crate::artifacts`].
104#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct ArtifactEntry {
106    pub id: LibraryArtifactId,
107    pub name: String,
108    pub description: String,
109    pub version: String,
110    pub mcp_tools: Vec<String>,
111    pub content: String,
112    pub starred: bool,
113    pub sha256: Sha256Digest,
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct SkillEntry {
118    pub id: SkillId,
119    pub name: SkillName,
120    pub description: String,
121    pub file_path: String,
122    #[serde(default)]
123    pub tags: Vec<String>,
124    pub sha256: Sha256Digest,
125    pub instructions: String,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct AgentEntry {
130    pub id: AgentId,
131    pub name: AgentName,
132    pub display_name: String,
133    pub description: String,
134    pub version: String,
135    pub endpoint: String,
136    pub enabled: bool,
137    pub is_default: bool,
138    pub is_primary: bool,
139    #[serde(default)]
140    pub provider: Option<String>,
141    #[serde(default)]
142    pub model: Option<String>,
143    #[serde(default)]
144    pub mcp_servers: PluginComponentRef,
145    #[serde(default)]
146    pub skills: PluginComponentRef,
147    #[serde(default)]
148    pub tags: Vec<String>,
149    #[serde(default)]
150    pub system_prompt: Option<String>,
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize)]
154pub struct HookEntry {
155    pub id: HookId,
156    pub name: String,
157    pub description: String,
158    pub version: String,
159    pub event: HookEvent,
160    pub matcher: String,
161    pub command: String,
162    #[serde(default)]
163    pub is_async: bool,
164    pub category: HookCategory,
165    #[serde(default)]
166    pub tags: Vec<String>,
167    pub sha256: Sha256Digest,
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize)]
171pub struct ManagedMcpServer {
172    pub name: ManagedMcpServerName,
173    pub url: ValidatedUrl,
174    #[serde(skip_serializing_if = "Option::is_none")]
175    pub transport: Option<String>,
176    #[serde(skip_serializing_if = "Option::is_none")]
177    pub headers: Option<BTreeMap<String, String>>,
178    #[serde(skip_serializing_if = "Option::is_none")]
179    pub oauth: Option<bool>,
180    #[serde(skip_serializing_if = "Option::is_none")]
181    pub tool_policy: Option<BTreeMap<ToolName, ToolPolicy>>,
182}