From e6c07f2479054ff29857e2a8db1f0c8ac2f11dc7 Mon Sep 17 00:00:00 2001 From: Marcelo Trylesinski Date: Sat, 7 Mar 2026 17:20:50 +0100 Subject: [PATCH] Update `AgentCard` schema to match A2A v1.0 spec - Remove fields dropped in v1.0: `url`, `version`, `protocol_version`, `documentation_url`, `icon_url`, `preferred_transport`, `default_input_modes`, `default_output_modes` - Rename `additional_interfaces` to `interfaces` - Make `description`, `capabilities`, `skills` optional - Add `extensions`, `signature`, and `AgentCardSignature` type - Update `FastA2A` constructor and agent card endpoint accordingly Co-Authored-By: Claude Opus 4.6 --- fasta2a/applications.py | 15 ++--------- fasta2a/schema.py | 52 ++++++++++++++++++-------------------- tests/test_applications.py | 6 ----- 3 files changed, 26 insertions(+), 47 deletions(-) diff --git a/fasta2a/applications.py b/fasta2a/applications.py index 958530d..2c8bcf8 100644 --- a/fasta2a/applications.py +++ b/fasta2a/applications.py @@ -36,8 +36,6 @@ def __init__( broker: Broker, # Agent card name: str | None = None, - url: str = 'http://localhost:8000', - version: str = '1.0.0', description: str | None = None, provider: AgentProvider | None = None, skills: list[Skill] | None = None, @@ -61,15 +59,10 @@ def __init__( ) self.name = name or 'My Agent' - self.url = url - self.version = version self.description = description self.provider = provider self.skills = skills or [] self.docs_url = docs_url - # NOTE: For now, I don't think there's any reason to support any other input/output modes. - self.default_input_modes = ['application/json'] - self.default_output_modes = ['application/json'] self.task_manager = TaskManager(broker=broker, storage=storage) @@ -92,17 +85,13 @@ async def _agent_card_endpoint(self, request: Request) -> Response: if self._agent_card_json_schema is None: agent_card = AgentCard( name=self.name, - description=self.description or 'An AI agent exposed as an A2A agent.', - url=self.url, - version=self.version, - protocol_version='0.3.0', skills=self.skills, - default_input_modes=self.default_input_modes, - default_output_modes=self.default_output_modes, capabilities=AgentCapabilities( streaming=False, push_notifications=False, state_transition_history=False ), ) + if self.description is not None: + agent_card['description'] = self.description if self.provider is not None: agent_card['provider'] = self.provider self._agent_card_json_schema = agent_card_ta.dump_json(agent_card, by_alias=True) diff --git a/fasta2a/schema.py b/fasta2a/schema.py index 37ffb86..e77d2d4 100644 --- a/fasta2a/schema.py +++ b/fasta2a/schema.py @@ -17,38 +17,20 @@ class AgentCard(TypedDict): name: str """Human readable name of the agent e.g. "Recipe Agent".""" - description: str + description: NotRequired[str] """A human-readable description of the agent. Used to assist users and other agents in understanding what the agent can do. (e.g. "Agent that helps users with recipes and cooking.") """ - url: str - """A URL to the address the agent is hosted at.""" - - version: str - """The version of the agent - format is up to the provider. (e.g. "1.0.0")""" - - protocol_version: str - """The version of the A2A protocol this agent supports.""" - provider: NotRequired[AgentProvider] """The service provider of the agent.""" - documentation_url: NotRequired[str] - """A URL to documentation for the agent.""" - - icon_url: NotRequired[str] - """A URL to an icon for the agent.""" - - preferred_transport: NotRequired[str] - """The transport of the preferred endpoint. If empty, defaults to JSONRPC.""" + interfaces: NotRequired[list[AgentInterface]] + """Supported interfaces/transports for the agent.""" - additional_interfaces: NotRequired[list[AgentInterface]] - """Announcement of additional supported transports.""" - - capabilities: AgentCapabilities + capabilities: NotRequired[AgentCapabilities] """The capabilities of the agent.""" security: NotRequired[list[dict[str, list[str]]]] @@ -57,14 +39,14 @@ class AgentCard(TypedDict): security_schemes: NotRequired[dict[str, SecurityScheme]] """Security scheme definitions.""" - default_input_modes: list[str] - """Supported mime types for input data.""" + skills: NotRequired[list[Skill]] + """The set of skills, or distinct capabilities, that the agent can perform.""" - default_output_modes: list[str] - """Supported mime types for output data.""" + extensions: NotRequired[list[AgentExtension]] + """Extensions supported by the agent.""" - skills: list[Skill] - """The set of skills, or distinct capabilities, that the agent can perform.""" + signature: NotRequired[AgentCardSignature] + """Signature for the agent card.""" agent_card_ta = pydantic.TypeAdapter(AgentCard) @@ -194,6 +176,20 @@ class AgentExtension(TypedDict): """Optional configuration for the extension.""" +@pydantic.with_config({'alias_generator': to_camel}) +class AgentCardSignature(TypedDict): + """Signature for the agent card.""" + + algorithm: str + """The algorithm used to sign the agent card.""" + + key_id: str + """The key identifier.""" + + value: str + """The signature value.""" + + @pydantic.with_config({'alias_generator': to_camel}) class Skill(TypedDict): """Skills are a unit of capability that an agent can perform.""" diff --git a/tests/test_applications.py b/tests/test_applications.py index 21692e0..26fe2ca 100644 --- a/tests/test_applications.py +++ b/tests/test_applications.py @@ -30,13 +30,7 @@ async def test_agent_card(): assert response.json() == snapshot( { 'name': 'My Agent', - 'description': 'An AI agent exposed as an A2A agent.', - 'url': 'http://localhost:8000', - 'version': '1.0.0', - 'protocolVersion': '0.3.0', 'skills': [], - 'defaultInputModes': ['application/json'], - 'defaultOutputModes': ['application/json'], 'capabilities': { 'streaming': False, 'pushNotifications': False,