Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ readme = "README.md"
requires-python = ">=3.12"

dependencies = [
"adcp==2.12.0", # Official AdCP Python client for external agent communication and adagents.json validation
"adcp==2.12.1", # Official AdCP Python client for external agent communication and adagents.json validation
"fastmcp>=2.13.0", # Required for context.get_http_request() support
"google-generativeai>=0.5.4",
"google-cloud-iam>=2.19.1",
Expand Down
25 changes: 25 additions & 0 deletions src/a2a_server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,31 @@ Automatically fetches and parses Agent Cards to:
- Get correct RPC endpoint URLs
- Display agent metadata and descriptions

### AdCP 2.5 Extension Support

The agent card includes the AdCP extension (per AdCP 2.5 spec) in `capabilities.extensions`:

```json
{
"capabilities": {
"extensions": [
{
"uri": "https://adcontextprotocol.org/schemas/2.5.0/protocols/adcp-extension.json",
"description": "AdCP protocol version and supported domains",
"params": {
"adcp_version": "2.5.0",
"protocols_supported": ["media_buy"]
}
}
]
}
}
```

This extension declares:
- **adcp_version**: The AdCP specification version implemented by this agent (currently "2.5.0")
- **protocols_supported**: Which AdCP protocol domains are supported (currently only "media_buy")

### Skill Invocation Patterns (AdCP PR #48)

The server supports two invocation patterns for maximum flexibility:
Expand Down
20 changes: 19 additions & 1 deletion src/a2a_server/adcp_a2a_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from a2a.server.request_handlers.request_handler import RequestHandler
from a2a.types import (
AgentCard,
AgentExtension,
Artifact,
DataPart,
InternalError,
Expand Down Expand Up @@ -2200,14 +2201,31 @@ def create_agent_card() -> AgentCard:
server_url = get_a2a_server_url()

from a2a.types import AgentCapabilities, AgentSkill
from adcp import get_adcp_version

# Create AdCP extension (AdCP 2.5 spec)
# As of adcp 2.12.1, get_adcp_version() returns the protocol version (e.g., "2.5.0")
# Previously it returned the schema version (e.g., "v1"), but this was fixed upstream
protocol_version = get_adcp_version()
adcp_extension = AgentExtension(
uri=f"https://adcontextprotocol.org/schemas/{protocol_version}/protocols/adcp-extension.json",
description="AdCP protocol version and supported domains",
params={
"adcp_version": protocol_version,
"protocols_supported": ["media_buy"], # Only media_buy protocol is currently supported
},
)

# Create the agent card with minimal required fields
agent_card = AgentCard(
name="AdCP Sales Agent",
description="AI agent for programmatic advertising campaigns via AdCP protocol",
version="1.0.0",
protocol_version="1.0",
capabilities=AgentCapabilities(push_notifications=True),
capabilities=AgentCapabilities(
push_notifications=True,
extensions=[adcp_extension],
),
default_input_modes=["message"],
default_output_modes=["message"],
skills=[
Expand Down
55 changes: 55 additions & 0 deletions tests/e2e/test_a2a_endpoints_working.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,24 @@ def test_well_known_agent_json_endpoint_live(self):
# Note: A2A spec uses security/securitySchemes instead of simple authentication field
assert "security" in data or "securitySchemes" in data

# AdCP 2.5: Should have AdCP extension in capabilities
assert "capabilities" in data
assert "extensions" in data["capabilities"]
extensions = data["capabilities"]["extensions"]
assert isinstance(extensions, list)
assert len(extensions) > 0

# Find AdCP extension
adcp_ext = None
for ext in extensions:
if "adcp-extension" in ext.get("uri", ""):
adcp_ext = ext
break

assert adcp_ext is not None, "AdCP extension not found in live agent card"
assert adcp_ext["params"]["adcp_version"] == "2.5.0"
assert "media_buy" in adcp_ext["params"]["protocols_supported"]

except (requests.ConnectionError, requests.Timeout):
pytest.skip("A2A server not running on localhost:8091")

Expand Down Expand Up @@ -157,6 +175,43 @@ def test_create_agent_card_function(self):
assert hasattr(skill, "name")
assert hasattr(skill, "description")

def test_agent_card_adcp_extension(self):
"""Test that agent card includes AdCP 2.5 extension."""
from src.a2a_server.adcp_a2a_server import create_agent_card

agent_card = create_agent_card()

# Check capabilities has extensions
assert hasattr(agent_card, "capabilities")
assert agent_card.capabilities is not None
assert hasattr(agent_card.capabilities, "extensions")
assert agent_card.capabilities.extensions is not None
assert len(agent_card.capabilities.extensions) > 0

# Find AdCP extension
adcp_ext = None
for ext in agent_card.capabilities.extensions:
if "adcp-extension" in ext.uri:
adcp_ext = ext
break

assert adcp_ext is not None, "AdCP extension not found in capabilities.extensions"

# Validate AdCP extension structure
assert adcp_ext.uri == "https://adcontextprotocol.org/schemas/2.5.0/protocols/adcp-extension.json"
assert adcp_ext.params is not None
assert "adcp_version" in adcp_ext.params
assert "protocols_supported" in adcp_ext.params

# Validate AdCP extension values
assert adcp_ext.params["adcp_version"] == "2.5.0"
protocols = adcp_ext.params["protocols_supported"]
assert isinstance(protocols, list)
assert len(protocols) >= 1
# Currently only media_buy protocol is supported
assert "media_buy" in protocols
assert set(protocols) == {"media_buy"}, "Only media_buy protocol is currently supported"

def test_agent_card_skills_coverage(self):
"""Test that agent card includes expected AdCP skills."""
from src.a2a_server.adcp_a2a_server import create_agent_card
Expand Down
8 changes: 4 additions & 4 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.