Skip to content

Commit 2efd3d9

Browse files
committed
feat(a2a): Add A2A protocol discovery endpoint (#6996)
Implements Phase 1 of the A2A Agent Card support: Discovery Endpoint. This enables A2A protocol discovery of Apicurio Registry as an AI agent. Changes: - Add /.well-known/agent.json endpoint for registry agent card - Add /.well-known/agents/{groupId}/{artifactId} endpoint to serve registered agent cards stored in the registry - Create A2AConfig for configuration properties - Create RegistryAgentCardBuilder to construct the registry's agent card - Add Agent Card DTOs (AgentCard, AgentSkill, AgentCapabilities, etc.) - Add WellKnownResource interface and implementation - Add configuration properties (apicurio.a2a.enabled, agent name, etc.) - Add unit tests for both endpoints Part of #6996
1 parent 5df3e2f commit 2efd3d9

File tree

13 files changed

+1115
-67
lines changed

13 files changed

+1115
-67
lines changed
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package io.apicurio.registry.a2a;
2+
3+
import io.apicurio.common.apps.config.Info;
4+
import jakarta.inject.Singleton;
5+
import org.eclipse.microprofile.config.inject.ConfigProperty;
6+
7+
import java.util.Optional;
8+
9+
import static io.apicurio.common.apps.config.ConfigPropertyCategory.CATEGORY_A2A;
10+
11+
/**
12+
* Configuration properties for A2A (Agent2Agent) protocol support.
13+
*/
14+
@Singleton
15+
public class A2AConfig {
16+
17+
@ConfigProperty(name = "apicurio.a2a.enabled", defaultValue = "true")
18+
@Info(category = CATEGORY_A2A, description = "Enable A2A protocol support", availableSince = "3.0.0")
19+
boolean enabled;
20+
21+
@ConfigProperty(name = "apicurio.a2a.agent.name", defaultValue = "Apicurio Registry")
22+
@Info(category = CATEGORY_A2A, description = "Name of the registry agent for A2A discovery", availableSince = "3.0.0")
23+
String agentName;
24+
25+
@ConfigProperty(name = "apicurio.a2a.agent.description", defaultValue = "API and Schema Registry with A2A Agent support")
26+
@Info(category = CATEGORY_A2A, description = "Description of the registry agent", availableSince = "3.0.0")
27+
String agentDescription;
28+
29+
@ConfigProperty(name = "apicurio.a2a.agent.version")
30+
@Info(category = CATEGORY_A2A, description = "Version of the registry agent (defaults to app version)", availableSince = "3.0.0")
31+
Optional<String> agentVersion;
32+
33+
@ConfigProperty(name = "apicurio.a2a.agent.url")
34+
@Info(category = CATEGORY_A2A, description = "Base URL for the registry agent's A2A endpoint", availableSince = "3.0.0")
35+
Optional<String> agentUrl;
36+
37+
@ConfigProperty(name = "apicurio.a2a.agent.provider.organization", defaultValue = "Apicurio")
38+
@Info(category = CATEGORY_A2A, description = "Organization name for the agent provider", availableSince = "3.0.0")
39+
String providerOrganization;
40+
41+
@ConfigProperty(name = "apicurio.a2a.agent.provider.url", defaultValue = "https://www.apicur.io")
42+
@Info(category = CATEGORY_A2A, description = "URL for the agent provider", availableSince = "3.0.0")
43+
String providerUrl;
44+
45+
@ConfigProperty(name = "apicurio.a2a.agent.capabilities.streaming", defaultValue = "false")
46+
@Info(category = CATEGORY_A2A, description = "Whether the agent supports streaming", availableSince = "3.0.0")
47+
boolean capabilitiesStreaming;
48+
49+
@ConfigProperty(name = "apicurio.a2a.agent.capabilities.push-notifications", defaultValue = "false")
50+
@Info(category = CATEGORY_A2A, description = "Whether the agent supports push notifications", availableSince = "3.0.0")
51+
boolean capabilitiesPushNotifications;
52+
53+
public boolean isEnabled() {
54+
return enabled;
55+
}
56+
57+
public String getAgentName() {
58+
return agentName;
59+
}
60+
61+
public String getAgentDescription() {
62+
return agentDescription;
63+
}
64+
65+
public Optional<String> getAgentVersion() {
66+
return agentVersion;
67+
}
68+
69+
public Optional<String> getAgentUrl() {
70+
return agentUrl;
71+
}
72+
73+
public String getProviderOrganization() {
74+
return providerOrganization;
75+
}
76+
77+
public String getProviderUrl() {
78+
return providerUrl;
79+
}
80+
81+
public boolean isCapabilitiesStreaming() {
82+
return capabilitiesStreaming;
83+
}
84+
85+
public boolean isCapabilitiesPushNotifications() {
86+
return capabilitiesPushNotifications;
87+
}
88+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package io.apicurio.registry.a2a;
2+
3+
import io.apicurio.registry.a2a.rest.beans.AgentAuthentication;
4+
import io.apicurio.registry.a2a.rest.beans.AgentCapabilities;
5+
import io.apicurio.registry.a2a.rest.beans.AgentCard;
6+
import io.apicurio.registry.a2a.rest.beans.AgentProvider;
7+
import io.apicurio.registry.a2a.rest.beans.AgentSkill;
8+
import io.apicurio.registry.auth.AuthConfig;
9+
import jakarta.enterprise.context.ApplicationScoped;
10+
import jakarta.inject.Inject;
11+
12+
import java.util.ArrayList;
13+
import java.util.List;
14+
15+
/**
16+
* Builds the Agent Card that represents Apicurio Registry as an A2A agent.
17+
* This agent card is served at /.well-known/agent.json per the A2A protocol.
18+
*/
19+
@ApplicationScoped
20+
public class RegistryAgentCardBuilder {
21+
22+
@Inject
23+
A2AConfig a2aConfig;
24+
25+
@Inject
26+
io.apicurio.registry.core.System system;
27+
28+
@Inject
29+
AuthConfig authConfig;
30+
31+
/**
32+
* Builds and returns the Agent Card for this Apicurio Registry instance.
33+
*
34+
* @param baseUrl the base URL of the registry (used if not configured)
35+
* @return the Agent Card
36+
*/
37+
public AgentCard build(String baseUrl) {
38+
return AgentCard.builder()
39+
.name(a2aConfig.getAgentName())
40+
.description(a2aConfig.getAgentDescription())
41+
.version(a2aConfig.getAgentVersion().orElse(system.getVersion()))
42+
.url(a2aConfig.getAgentUrl().orElse(baseUrl))
43+
.provider(buildProvider())
44+
.capabilities(buildCapabilities())
45+
.skills(buildSkills())
46+
.defaultInputModes(List.of("text"))
47+
.defaultOutputModes(List.of("text"))
48+
.authentication(buildAuthentication())
49+
.supportsExtendedAgentCard(false)
50+
.build();
51+
}
52+
53+
private AgentProvider buildProvider() {
54+
return AgentProvider.builder()
55+
.organization(a2aConfig.getProviderOrganization())
56+
.url(a2aConfig.getProviderUrl())
57+
.build();
58+
}
59+
60+
private AgentCapabilities buildCapabilities() {
61+
return AgentCapabilities.builder()
62+
.streaming(a2aConfig.isCapabilitiesStreaming())
63+
.pushNotifications(a2aConfig.isCapabilitiesPushNotifications())
64+
.build();
65+
}
66+
67+
private List<AgentSkill> buildSkills() {
68+
return List.of(
69+
AgentSkill.builder()
70+
.id("schema-validation")
71+
.name("Schema Validation")
72+
.description("Validate schemas against format specifications (Avro, JSON Schema, Protobuf, OpenAPI, etc.)")
73+
.tags(List.of("schema", "validation", "avro", "json-schema", "protobuf", "openapi"))
74+
.build(),
75+
AgentSkill.builder()
76+
.id("schema-search")
77+
.name("Schema Search")
78+
.description("Search for schemas and APIs in the registry by name, description, labels, or content")
79+
.tags(List.of("schema", "search", "discovery"))
80+
.build(),
81+
AgentSkill.builder()
82+
.id("artifact-management")
83+
.name("Artifact Management")
84+
.description("Create, update, and manage schema and API artifacts with full version history")
85+
.tags(List.of("artifact", "crud", "versioning"))
86+
.build(),
87+
AgentSkill.builder()
88+
.id("compatibility-check")
89+
.name("Compatibility Check")
90+
.description("Check schema compatibility between versions using configurable compatibility rules")
91+
.tags(List.of("compatibility", "evolution", "breaking-changes"))
92+
.build(),
93+
AgentSkill.builder()
94+
.id("agent-discovery")
95+
.name("Agent Discovery")
96+
.description("Discover and manage A2A agent cards registered in the registry")
97+
.tags(List.of("a2a", "agent", "discovery"))
98+
.build()
99+
);
100+
}
101+
102+
private AgentAuthentication buildAuthentication() {
103+
List<String> schemes = new ArrayList<>();
104+
105+
if (authConfig.isOidcAuthEnabled()) {
106+
schemes.add("bearer");
107+
}
108+
if (authConfig.isBasicAuthEnabled()) {
109+
schemes.add("basic");
110+
}
111+
112+
if (schemes.isEmpty()) {
113+
schemes.add("none");
114+
}
115+
116+
return AgentAuthentication.builder()
117+
.schemes(schemes)
118+
.build();
119+
}
120+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package io.apicurio.registry.a2a.rest.beans;
2+
3+
import com.fasterxml.jackson.annotation.JsonInclude;
4+
import com.fasterxml.jackson.annotation.JsonProperty;
5+
6+
import java.util.List;
7+
8+
/**
9+
* Represents the authentication configuration of an A2A agent.
10+
*/
11+
@JsonInclude(JsonInclude.Include.NON_NULL)
12+
public class AgentAuthentication {
13+
14+
@JsonProperty("schemes")
15+
private List<String> schemes;
16+
17+
public AgentAuthentication() {
18+
}
19+
20+
public AgentAuthentication(List<String> schemes) {
21+
this.schemes = schemes;
22+
}
23+
24+
public List<String> getSchemes() {
25+
return schemes;
26+
}
27+
28+
public void setSchemes(List<String> schemes) {
29+
this.schemes = schemes;
30+
}
31+
32+
public static Builder builder() {
33+
return new Builder();
34+
}
35+
36+
public static class Builder {
37+
private final AgentAuthentication authentication = new AgentAuthentication();
38+
39+
public Builder schemes(List<String> schemes) {
40+
authentication.setSchemes(schemes);
41+
return this;
42+
}
43+
44+
public AgentAuthentication build() {
45+
return authentication;
46+
}
47+
}
48+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package io.apicurio.registry.a2a.rest.beans;
2+
3+
import com.fasterxml.jackson.annotation.JsonInclude;
4+
import com.fasterxml.jackson.annotation.JsonProperty;
5+
6+
/**
7+
* Represents the capabilities of an A2A agent.
8+
*/
9+
@JsonInclude(JsonInclude.Include.NON_NULL)
10+
public class AgentCapabilities {
11+
12+
@JsonProperty("streaming")
13+
private Boolean streaming;
14+
15+
@JsonProperty("pushNotifications")
16+
private Boolean pushNotifications;
17+
18+
public AgentCapabilities() {
19+
}
20+
21+
public AgentCapabilities(Boolean streaming, Boolean pushNotifications) {
22+
this.streaming = streaming;
23+
this.pushNotifications = pushNotifications;
24+
}
25+
26+
public Boolean getStreaming() {
27+
return streaming;
28+
}
29+
30+
public void setStreaming(Boolean streaming) {
31+
this.streaming = streaming;
32+
}
33+
34+
public Boolean getPushNotifications() {
35+
return pushNotifications;
36+
}
37+
38+
public void setPushNotifications(Boolean pushNotifications) {
39+
this.pushNotifications = pushNotifications;
40+
}
41+
42+
public static Builder builder() {
43+
return new Builder();
44+
}
45+
46+
public static class Builder {
47+
private final AgentCapabilities capabilities = new AgentCapabilities();
48+
49+
public Builder streaming(Boolean streaming) {
50+
capabilities.setStreaming(streaming);
51+
return this;
52+
}
53+
54+
public Builder pushNotifications(Boolean pushNotifications) {
55+
capabilities.setPushNotifications(pushNotifications);
56+
return this;
57+
}
58+
59+
public AgentCapabilities build() {
60+
return capabilities;
61+
}
62+
}
63+
}

0 commit comments

Comments
 (0)