Skip to main content

systemprompt_models/a2a/agent_card/
mod.rs

1//! A2A protocol agent card — the JSON document an agent publishes to
2//! describe its identity, capabilities, transports, and skills.
3//!
4//! Copyright (c) systemprompt.io — Business Source License 1.1.
5//! See <https://systemprompt.io> for licensing details.
6
7mod extension;
8mod skill;
9
10pub use extension::{ARTIFACT_RENDERING_URI, AgentCapabilities, AgentExtension};
11pub use skill::{AgentCardSignature, AgentInterface, AgentProvider, AgentSkill};
12
13use serde::{Deserialize, Serialize};
14use std::collections::HashMap;
15
16use super::security::SecurityScheme;
17use super::transport::ProtocolBinding;
18
19#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
20#[serde(rename_all = "camelCase")]
21pub struct AgentCard {
22    pub name: String,
23    pub description: String,
24    pub supported_interfaces: Vec<AgentInterface>,
25    pub version: String,
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub icon_url: Option<String>,
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub provider: Option<AgentProvider>,
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub documentation_url: Option<String>,
32    pub capabilities: AgentCapabilities,
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub security_schemes: Option<HashMap<String, SecurityScheme>>,
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub security: Option<Vec<HashMap<String, Vec<String>>>>,
37    pub default_input_modes: Vec<String>,
38    pub default_output_modes: Vec<String>,
39    #[serde(default)]
40    pub skills: Vec<AgentSkill>,
41    #[serde(default, skip_serializing_if = "Option::is_none")]
42    pub supports_authenticated_extended_card: Option<bool>,
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub signatures: Option<Vec<AgentCardSignature>>,
45}
46
47impl AgentCard {
48    #[must_use]
49    pub fn builder(
50        name: String,
51        description: String,
52        url: String,
53        version: String,
54    ) -> AgentCardBuilder {
55        AgentCardBuilder::new(name, description, url, version)
56    }
57
58    #[must_use]
59    pub fn url(&self) -> Option<&str> {
60        self.supported_interfaces.first().map(|i| i.url.as_str())
61    }
62
63    #[must_use]
64    pub fn has_mcp_extension(&self) -> bool {
65        self.capabilities
66            .extensions
67            .as_ref()
68            .is_some_and(|exts| exts.iter().any(|ext| ext.uri == "systemprompt:mcp-tools"))
69    }
70
71    pub fn ensure_mcp_extension(&mut self) {
72        if self.has_mcp_extension() {
73            return;
74        }
75
76        self.capabilities
77            .extensions
78            .get_or_insert_with(Vec::new)
79            .push(AgentExtension::mcp_tools_extension());
80    }
81}
82
83#[derive(Debug)]
84pub struct AgentCardBuilder {
85    agent_card: AgentCard,
86}
87
88impl AgentCardBuilder {
89    #[must_use]
90    pub fn new(name: String, description: String, url: String, version: String) -> Self {
91        Self {
92            agent_card: AgentCard {
93                name,
94                description,
95                supported_interfaces: vec![AgentInterface {
96                    url,
97                    protocol_binding: ProtocolBinding::JsonRpc,
98                    protocol_version: "1.0.0".to_owned(),
99                }],
100                version,
101                icon_url: None,
102                provider: None,
103                documentation_url: None,
104                capabilities: AgentCapabilities::default(),
105                security_schemes: None,
106                security: None,
107                default_input_modes: vec!["text/plain".to_owned()],
108                default_output_modes: vec!["text/plain".to_owned()],
109                skills: Vec::new(),
110                supports_authenticated_extended_card: Some(false),
111                signatures: None,
112            },
113        }
114    }
115
116    #[must_use]
117    pub const fn with_streaming(mut self) -> Self {
118        self.agent_card.capabilities.streaming = Some(true);
119        self
120    }
121
122    #[must_use]
123    pub const fn with_push_notifications(mut self) -> Self {
124        self.agent_card.capabilities.push_notifications = Some(true);
125        self
126    }
127
128    #[must_use]
129    pub fn with_provider(mut self, organization: String, url: String) -> Self {
130        self.agent_card.provider = Some(AgentProvider { organization, url });
131        self
132    }
133
134    #[must_use]
135    pub fn build(self) -> AgentCard {
136        self.agent_card
137    }
138}