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
34 changes: 21 additions & 13 deletions src/a2a_server/adcp_a2a_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
from src.core.audit_logger import get_audit_logger
from src.core.auth_utils import get_principal_from_token
from src.core.config_loader import get_current_tenant
from src.core.database.models import PushNotificationConfig as DBPushNotificationConfig
from src.core.domain_config import get_a2a_server_url, get_sales_agent_domain
from src.core.schemas import (
GetSignalsRequest,
Expand Down Expand Up @@ -99,8 +100,6 @@
)
from src.services.protocol_webhook_service import get_protocol_webhook_service

from src.core.database.models import PushNotificationConfig as DBPushNotificationConfig

# Configure logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -939,7 +938,7 @@ async def on_get_task_push_notification_config(
from a2a.types import InvalidParamsError, TaskNotFoundError

from src.core.database.database_session import get_db_session

try:
# Get authentication token
auth_token = self._get_auth_token()
Expand Down Expand Up @@ -1864,7 +1863,15 @@ async def _handle_update_media_buy_skill(self, parameters: dict, auth_token: str
async def _handle_get_media_buy_delivery_skill(self, parameters: dict, auth_token: str) -> dict:
"""Handle explicit get_media_buy_delivery skill invocation (CRITICAL for monitoring).

Accepts media_buy_ids (plural, per AdCP v1.6.0 spec) or media_buy_id (singular, legacy).
Per AdCP spec, all parameters are optional:
- media_buy_ids (plural, per AdCP v1.6.0 spec) or media_buy_id (singular, legacy)
- buyer_refs: Filter by buyer reference IDs
- status_filter: Filter by status (active, pending, paused, completed, failed, all)
- start_date: Start date for reporting period (YYYY-MM-DD)
- end_date: End date for reporting period (YYYY-MM-DD)

When no media_buy_ids are provided, returns delivery data for all media buys
the requester has access to, filtered by the provided criteria.
"""
try:
# Create ToolContext from A2A auth info
Expand All @@ -1881,18 +1888,19 @@ async def _handle_get_media_buy_delivery_skill(self, parameters: dict, auth_toke
if media_buy_id:
media_buy_ids = [media_buy_id]

# Validate that we have at least one ID
if not media_buy_ids:
return {
"success": False,
"message": "Missing required parameter: 'media_buy_ids' (or 'media_buy_id' for single buy)",
"required_parameters": ["media_buy_ids"],
"received_parameters": list(parameters.keys()),
}
# Extract other optional parameters
buyer_refs = parameters.get("buyer_refs")
status_filter = parameters.get("status_filter")
start_date = parameters.get("start_date")
end_date = parameters.get("end_date")

# Call core function directly with spec-compliant plural parameter
# Call core function with all parameters (all are optional per AdCP spec)
response = core_get_media_buy_delivery_tool(
media_buy_ids=media_buy_ids,
buyer_refs=buyer_refs,
status_filter=status_filter,
start_date=start_date,
end_date=end_date,
context=self._tool_context_to_mcp_context(tool_context),
)

Expand Down
44 changes: 44 additions & 0 deletions tests/unit/test_a2a_parameter_mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,50 @@ def test_get_media_buy_delivery_uses_plural_media_buy_ids(self):
assert "media_buy_ids" in call_kwargs, "Should pass 'media_buy_ids' (plural) per AdCP spec"
assert call_kwargs["media_buy_ids"] == parameters["media_buy_ids"]

def test_get_media_buy_delivery_optional_media_buy_ids(self):
"""
Test that get_media_buy_delivery works without media_buy_ids.

Per AdCP spec, all parameters are optional. When media_buy_ids is omitted,
the server should return delivery data for all media buys the requester
has access to, filtered by the provided criteria (status_filter, dates, etc).
"""
from src.a2a_server.adcp_a2a_server import AdCPRequestHandler

handler = AdCPRequestHandler()

with (
patch("src.a2a_server.adcp_a2a_server.get_principal_from_token") as mock_principal,
patch("src.a2a_server.adcp_a2a_server.get_current_tenant") as mock_tenant,
patch("src.a2a_server.adcp_a2a_server.core_get_media_buy_delivery_tool") as mock_delivery,
):
mock_principal.return_value = "principal_123"
mock_tenant.return_value = {"tenant_id": "tenant_123"}
mock_delivery.return_value = {"media_buys": []}

# AdCP request with filters but no media_buy_ids
parameters = {
"status_filter": "active",
"start_date": "2025-01-01",
"end_date": "2025-01-31",
}

import asyncio

result = asyncio.run(
handler._handle_get_media_buy_delivery_skill(parameters=parameters, auth_token="test_token")
)

# Verify core function was called with filters
mock_delivery.assert_called_once()
call_kwargs = mock_delivery.call_args.kwargs

# Should pass None for media_buy_ids and include filters
assert call_kwargs["media_buy_ids"] is None, "media_buy_ids should be None when omitted"
assert call_kwargs["status_filter"] == "active", "Should pass status_filter"
assert call_kwargs["start_date"] == "2025-01-01", "Should pass start_date"
assert call_kwargs["end_date"] == "2025-01-31", "Should pass end_date"

def test_create_media_buy_validates_required_adcp_parameters(self):
"""
Test that create_media_buy validates required AdCP parameters.
Expand Down