1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4use crate::ProviderId;
5
6pub type ProviderCatalog = ProviderManifest;
7
8#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
9#[serde(deny_unknown_fields)]
10pub struct ProviderManifest {
11 pub schema_version: u32,
12 pub provider: ProviderIdentity,
13 #[serde(default)]
14 pub tools: Vec<ProviderTool>,
15 #[serde(default)]
16 pub prompts: Vec<ProviderPrompt>,
17 #[serde(default)]
18 pub resources: Vec<ProviderResource>,
19 #[serde(default)]
20 pub tasks: Vec<ProviderTask>,
21 #[serde(default)]
22 pub elicitation: Vec<ProviderElicitation>,
23 #[serde(default)]
24 pub env: Vec<EnvRequirement>,
25 #[serde(default)]
26 pub capabilities: HostCapabilities,
27 #[serde(default)]
28 pub docs: Option<DocsOverlay>,
29 #[serde(default)]
30 pub plugin: Option<PluginOverlay>,
31 #[serde(default)]
32 pub ui: Option<UiOverlay>,
33 #[serde(default)]
34 pub meta: Value,
35}
36
37impl ProviderManifest {
38 pub fn new(id: ProviderId, title: impl Into<String>, version: impl Into<String>) -> Self {
39 Self {
40 schema_version: 1,
41 provider: ProviderIdentity {
42 name: id.into_string(),
43 kind: ProviderKind::StaticRust,
44 title: Some(title.into()),
45 description: None,
46 homepage: None,
47 source: None,
48 version: Some(version.into()),
49 enabled: None,
50 },
51 tools: Vec::new(),
52 prompts: Vec::new(),
53 resources: Vec::new(),
54 tasks: Vec::new(),
55 elicitation: Vec::new(),
56 env: Vec::new(),
57 capabilities: HostCapabilities::default(),
58 docs: None,
59 plugin: None,
60 ui: None,
61 meta: Value::Null,
62 }
63 }
64
65 #[must_use]
66 pub fn with_tool(mut self, tool: ToolSpec) -> Self {
67 self.tools.push(tool);
68 self
69 }
70}
71
72#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
73#[serde(deny_unknown_fields)]
74pub struct ProviderIdentity {
75 pub name: String,
76 pub kind: ProviderKind,
77 #[serde(default)]
78 pub title: Option<String>,
79 #[serde(default)]
80 pub description: Option<String>,
81 #[serde(default)]
82 pub homepage: Option<String>,
83 #[serde(default)]
84 pub source: Option<String>,
85 #[serde(default)]
86 pub version: Option<String>,
87 #[serde(default)]
88 pub enabled: Option<bool>,
89}
90
91#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
92#[serde(rename_all = "kebab-case")]
93pub enum ProviderKind {
94 StaticRust,
95 Openapi,
96 AiSdk,
97 Wasm,
98 Mcp,
99 Python,
100 Langchain,
101 Llamaindex,
102}
103
104impl ProviderKind {
105 pub fn as_str(self) -> &'static str {
106 match self {
107 Self::StaticRust => "static-rust",
108 Self::Openapi => "openapi",
109 Self::AiSdk => "ai-sdk",
110 Self::Wasm => "wasm",
111 Self::Mcp => "mcp",
112 Self::Python => "python",
113 Self::Langchain => "langchain",
114 Self::Llamaindex => "llamaindex",
115 }
116 }
117}
118
119#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
120#[serde(deny_unknown_fields)]
121pub struct ToolSpec {
122 pub name: String,
123 pub description: String,
124 #[serde(default)]
125 pub title: Option<String>,
126 pub input_schema: Value,
127 #[serde(default)]
128 pub output_schema: Option<Value>,
129 #[serde(default)]
130 pub scope: Option<String>,
131 #[serde(default)]
132 pub destructive: bool,
133 #[serde(default)]
134 pub requires_admin: bool,
135 #[serde(default)]
136 pub cost: Option<String>,
137 #[serde(default)]
138 pub env: Vec<EnvRequirement>,
139 #[serde(default)]
140 pub limits: Option<ProviderLimits>,
141 #[serde(default)]
142 pub mcp: Option<McpOverlay>,
143 #[serde(default)]
144 pub rest: Option<RestOverlay>,
145 #[serde(default)]
146 pub cli: Option<CliOverlay>,
147 #[serde(default)]
148 pub palette: Option<PaletteOverlay>,
149 #[serde(default)]
150 pub ui: Option<UiOverlay>,
151 #[serde(default)]
152 pub examples: Vec<ProviderExample>,
153 #[serde(default)]
154 pub meta: Value,
155}
156
157impl ToolSpec {
158 pub fn new(
159 name: impl Into<String>,
160 description: impl Into<String>,
161 input_schema: Value,
162 ) -> Self {
163 Self {
164 name: name.into(),
165 description: description.into(),
166 title: None,
167 input_schema,
168 output_schema: None,
169 scope: None,
170 destructive: false,
171 requires_admin: false,
172 cost: None,
173 env: Vec::new(),
174 limits: None,
175 mcp: None,
176 rest: None,
177 cli: None,
178 palette: None,
179 ui: None,
180 examples: Vec::new(),
181 meta: Value::Null,
182 }
183 }
184
185 pub fn exposed_on(&self, surface: crate::ProviderSurface) -> bool {
186 match surface {
187 crate::ProviderSurface::Internal => true,
188 crate::ProviderSurface::Mcp => self
189 .mcp
190 .as_ref()
191 .map(|overlay| overlay.enabled)
192 .unwrap_or(true),
193 crate::ProviderSurface::Rest => self
194 .rest
195 .as_ref()
196 .map(|overlay| overlay.enabled)
197 .unwrap_or(true),
198 crate::ProviderSurface::Cli => self
199 .cli
200 .as_ref()
201 .map(|overlay| overlay.enabled)
202 .unwrap_or(false),
203 crate::ProviderSurface::Palette => self
204 .palette
205 .as_ref()
206 .map(|overlay| overlay.enabled)
207 .unwrap_or(true),
208 crate::ProviderSurface::Ui => self
209 .ui
210 .as_ref()
211 .map(|overlay| overlay.enabled)
212 .unwrap_or(true),
213 }
214 }
215}
216
217pub type ProviderTool = ToolSpec;
218
219#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
220#[serde(deny_unknown_fields)]
221pub struct ProviderPrompt {
222 pub name: String,
223 pub description: String,
224 #[serde(default)]
225 pub template: Option<String>,
226 #[serde(default)]
227 pub arguments_schema: Option<Value>,
228 #[serde(default)]
229 pub scope: Option<String>,
230 #[serde(default)]
231 pub mcp: Option<McpOverlay>,
232 #[serde(default)]
233 pub examples: Vec<ProviderExample>,
234}
235
236#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
237#[serde(deny_unknown_fields)]
238pub struct ProviderResource {
239 pub uri_template: String,
240 pub name: String,
241 pub description: String,
242 #[serde(default)]
243 pub mime_type: Option<String>,
244 #[serde(default)]
245 pub scope: Option<String>,
246 #[serde(default)]
247 pub mcp: Option<McpOverlay>,
248 #[serde(default)]
249 pub annotations: Value,
250}
251
252#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
253#[serde(deny_unknown_fields)]
254pub struct ProviderTask {
255 pub name: String,
256 pub description: String,
257 pub input_schema: Value,
258 #[serde(default)]
259 pub output_schema: Option<Value>,
260 #[serde(default)]
261 pub scope: Option<String>,
262 #[serde(default)]
263 pub mcp: Option<McpOverlay>,
264 #[serde(default)]
265 pub limits: Option<ProviderLimits>,
266}
267
268#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
269#[serde(deny_unknown_fields)]
270pub struct ProviderElicitation {
271 pub name: String,
272 pub description: String,
273 pub schema: Value,
274 #[serde(default)]
275 pub scope: Option<String>,
276 #[serde(default)]
277 pub mcp: Option<McpOverlay>,
278}
279
280#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
281#[serde(deny_unknown_fields)]
282pub struct EnvRequirement {
283 pub name: String,
284 #[serde(default)]
285 pub description: Option<String>,
286 #[serde(default = "default_true")]
287 pub required: bool,
288 #[serde(default = "default_true")]
289 pub sensitive: bool,
290 #[serde(default = "default_true")]
291 pub server_prefixed: bool,
292 #[serde(default)]
293 pub allow_unprefixed: bool,
294 #[serde(default)]
295 pub default: Option<Value>,
296}
297
298impl EnvRequirement {
299 pub fn runtime_name(&self, server_prefix: &str) -> String {
300 if self.server_prefixed {
301 format!(
302 "{}_{}",
303 server_prefix.trim_matches('_').to_ascii_uppercase(),
304 self.name
305 )
306 } else {
307 self.name.clone()
308 }
309 }
310}
311
312#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq, Eq)]
313#[serde(deny_unknown_fields)]
314pub struct HostCapabilities {
315 #[serde(default)]
316 pub filesystem: Option<FilesystemCapability>,
317 #[serde(default)]
318 pub network: Option<NetworkCapability>,
319 #[serde(default)]
320 pub env: Option<EnvCapability>,
321 #[serde(default)]
322 pub terminal: Option<TerminalCapability>,
323 #[serde(default)]
324 pub browser: Option<BrowserCapability>,
325 #[serde(default)]
326 pub github: Option<GitHubCapability>,
327}
328
329#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
330#[serde(tag = "kind", rename_all = "kebab-case")]
331pub enum CapabilityGrant {
332 Filesystem {
333 read_roots: Vec<String>,
334 write_roots: Vec<String>,
335 },
336 Network {
337 allowed_hosts: Vec<String>,
338 },
339 Env {
340 allowed: Vec<String>,
341 },
342 Terminal {
343 working_dir: Option<String>,
344 allowlist: Vec<String>,
345 },
346 Browser {
347 allowed_origins: Vec<String>,
348 },
349 Github {
350 allowed_repos: Vec<String>,
351 read_only: bool,
352 },
353}
354
355#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq, Eq)]
356#[serde(deny_unknown_fields)]
357pub struct FilesystemCapability {
358 #[serde(default)]
359 pub enabled: bool,
360 #[serde(default)]
361 pub read_roots: Vec<String>,
362 #[serde(default)]
363 pub write_roots: Vec<String>,
364}
365
366#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq, Eq)]
367#[serde(deny_unknown_fields)]
368pub struct NetworkCapability {
369 #[serde(default)]
370 pub enabled: bool,
371 #[serde(default)]
372 pub allowed_hosts: Vec<String>,
373}
374
375#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq, Eq)]
376#[serde(deny_unknown_fields)]
377pub struct EnvCapability {
378 #[serde(default)]
379 pub enabled: bool,
380 #[serde(default)]
381 pub allowed: Vec<String>,
382}
383
384#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq, Eq)]
385#[serde(deny_unknown_fields)]
386pub struct TerminalCapability {
387 #[serde(default)]
388 pub enabled: bool,
389 #[serde(default)]
390 pub working_dir: Option<String>,
391 #[serde(default)]
392 pub allowlist: Vec<String>,
393}
394
395#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq, Eq)]
396#[serde(deny_unknown_fields)]
397pub struct BrowserCapability {
398 #[serde(default)]
399 pub enabled: bool,
400 #[serde(default)]
401 pub allowed_origins: Vec<String>,
402}
403
404#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq, Eq)]
405#[serde(deny_unknown_fields)]
406pub struct GitHubCapability {
407 #[serde(default)]
408 pub enabled: bool,
409 #[serde(default)]
410 pub allowed_repos: Vec<String>,
411 #[serde(default = "default_true")]
412 pub read_only: bool,
413}
414
415#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
416#[serde(deny_unknown_fields)]
417pub struct McpOverlay {
418 #[serde(default = "default_true")]
419 pub enabled: bool,
420 #[serde(default)]
421 pub title: Option<String>,
422 #[serde(default)]
423 pub annotations: Value,
424}
425
426#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
427#[serde(deny_unknown_fields)]
428pub struct RestOverlay {
429 #[serde(default = "default_true")]
430 pub enabled: bool,
431 #[serde(default)]
432 pub method: Option<String>,
433 #[serde(default)]
434 pub path: Option<String>,
435 #[serde(default)]
436 pub tags: Vec<String>,
437 #[serde(default)]
438 pub summary: Option<String>,
439 #[serde(default)]
440 pub description: Option<String>,
441 #[serde(default)]
442 pub deprecated: bool,
443 #[serde(default)]
444 pub path_params: Value,
445 #[serde(default)]
446 pub query_params: Value,
447 #[serde(default)]
448 pub request_body_schema: Option<Value>,
449}
450
451#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
452#[serde(deny_unknown_fields)]
453pub struct CliOverlay {
454 #[serde(default = "default_true")]
455 pub enabled: bool,
456 #[serde(default)]
457 pub command: Option<String>,
458 #[serde(default)]
459 pub aliases: Vec<String>,
460 #[serde(default)]
461 pub about: Option<String>,
462 #[serde(default)]
463 pub long_about: Option<String>,
464 #[serde(default)]
465 pub hidden: bool,
466 #[serde(default)]
467 pub flags: Vec<Value>,
468 #[serde(default)]
469 pub default_output: Option<String>,
470 #[serde(default)]
471 pub interactive: bool,
472}
473
474#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
475#[serde(deny_unknown_fields)]
476pub struct PaletteOverlay {
477 #[serde(default = "default_true")]
478 pub enabled: bool,
479 #[serde(default)]
480 pub category: Option<String>,
481 #[serde(default)]
482 pub icon: Option<String>,
483 #[serde(default)]
484 pub tone: Option<String>,
485 #[serde(default)]
486 pub arg_mode: Option<String>,
487 #[serde(default)]
488 pub result_view: Option<String>,
489 #[serde(default)]
490 pub aurora_blocks: Vec<String>,
491}
492
493#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
494#[serde(deny_unknown_fields)]
495pub struct UiOverlay {
496 #[serde(default = "default_true")]
497 pub enabled: bool,
498 #[serde(default)]
499 pub aurora_registry_dependencies: Vec<String>,
500 #[serde(default)]
501 pub shadcn_items: Vec<String>,
502 #[serde(default)]
503 pub categories: Vec<String>,
504 #[serde(default)]
505 pub meta: Value,
506}
507
508#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
509#[serde(deny_unknown_fields)]
510pub struct DocsOverlay {
511 #[serde(default)]
512 pub when_to_use: Option<String>,
513 #[serde(default)]
514 pub examples: Vec<ProviderExample>,
515 #[serde(default)]
516 pub troubleshooting: Vec<String>,
517}
518
519#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
520#[serde(deny_unknown_fields)]
521pub struct PluginOverlay {
522 #[serde(default = "default_true")]
523 pub generate_skill: bool,
524 #[serde(default = "default_true")]
525 pub generate_claude: bool,
526 #[serde(default = "default_true")]
527 pub generate_codex: bool,
528 #[serde(default = "default_true")]
529 pub generate_gemini: bool,
530 #[serde(default = "default_true")]
531 pub generate_marketplace: bool,
532 #[serde(default)]
533 pub mcp_registration: Option<String>,
534}
535
536#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
537#[serde(deny_unknown_fields)]
538pub struct ProviderLimits {
539 #[serde(default)]
540 pub timeout_ms: Option<u64>,
541 #[serde(default)]
542 pub max_response_bytes: Option<usize>,
543 #[serde(default)]
544 pub max_input_bytes: Option<usize>,
545}
546
547#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
548#[serde(deny_unknown_fields)]
549pub struct ProviderExample {
550 #[serde(default)]
551 pub title: Option<String>,
552 #[serde(default)]
553 pub description: Option<String>,
554 #[serde(default)]
555 pub input: Option<Value>,
556 #[serde(default)]
557 pub output: Option<Value>,
558 #[serde(default)]
559 pub cli: Option<String>,
560 #[serde(default)]
561 pub rest: Option<Value>,
562 #[serde(default)]
563 pub mcp: Option<Value>,
564}
565
566fn default_true() -> bool {
567 true
568}