Skip to content
Open
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
43 changes: 43 additions & 0 deletions src/google/adk/tools/dns_aid_a2a_bridge.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""Bridge between DNS-AID discovered agents and Google ADK A2A.

When DNS-AID discovers an agent published with protocol='a2a', the agent's
endpoint serves an A2A Agent Card at /.well-known/agent-card.json.
This module provides utilities to convert those records to ADK-compatible refs.
"""

from __future__ import annotations

from typing import Any

from dns_aid.core.models import AgentRecord, Protocol

# Well-known path for A2A agent cards per the A2A specification.
A2A_AGENT_CARD_PATH = "/.well-known/agent-card.json"


async def a2a_agent_from_record(agent_record: AgentRecord) -> dict[str, Any]:
"""Convert a DNS-AID AgentRecord with A2A protocol to an ADK-compatible reference.

Args:
agent_record: An AgentRecord from dns_aid.discover() with protocol=a2a.

Returns:
Dictionary with agent metadata suitable for ADK RemoteAgent construction.

Raises:
ValueError: If the agent record is not using A2A protocol.
"""
if agent_record.protocol != Protocol.A2A:
raise ValueError(
f"Agent {agent_record.name} uses protocol {agent_record.protocol}, not A2A"
)

base_url = agent_record.endpoint_url.rstrip("/")
return {
"name": agent_record.name,
"url": agent_record.endpoint_url,
"capabilities": agent_record.capabilities or [],
"description": agent_record.description,
"protocol": "a2a",
"a2a_card_url": f"{base_url}{A2A_AGENT_CARD_PATH}",
}
177 changes: 177 additions & 0 deletions src/google/adk/tools/dns_aid_tool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
"""DNS-AID tools for Google ADK.

Google ADK wraps plain async functions as FunctionTool objects.
Tool descriptions are derived from docstrings.
"""

from __future__ import annotations

import json
from typing import Any, Optional


def _resolve_backend(backend_name: str | None) -> Any:
"""Resolve a DNS backend by name, or return None."""
if not backend_name:
return None
from dns_aid.backends import create_backend

return create_backend(backend_name)


async def discover_agents(
domain: str,
protocol: Optional[str] = None,
name: Optional[str] = None,
require_dnssec: bool = False,
) -> str:
"""Discover AI agents at a domain via DNS-AID SVCB records.

Queries DNS to find published agents, optionally filtering by protocol or name.
Returns JSON with agent names, endpoints, capabilities, and protocols.

Args:
domain: Domain to search for agents (e.g. 'agents.example.com').
protocol: Filter by protocol: 'a2a', 'mcp', 'https', or None for all.
name: Filter by specific agent name.
require_dnssec: Require DNSSEC-validated responses.
"""
import dns_aid

result = await dns_aid.discover(
domain=domain, protocol=protocol, name=name, require_dnssec=require_dnssec
)
return json.dumps(result.model_dump(), default=str)


async def publish_agent(
agent_name: str,
domain: str,
protocol: str = "mcp",
endpoint: str = "",
port: int = 443,
capabilities: Optional[list[str]] = None,
version: str = "1.0.0",
description: Optional[str] = None,
ttl: int = 3600,
backend_name: Optional[str] = None,
) -> str:
"""Publish an AI agent to DNS using DNS-AID protocol.

Creates SVCB and TXT records so the agent becomes discoverable.

Args:
agent_name: Agent identifier in DNS label format.
domain: Domain to publish under.
protocol: Protocol ('a2a', 'mcp', 'https').
endpoint: Hostname where the agent is reachable.
port: Port number.
capabilities: List of agent capabilities.
version: Agent version.
description: Human-readable description.
ttl: DNS TTL in seconds.
backend_name: DNS backend name (e.g. 'route53', 'cloudflare').
"""
import dns_aid

result = await dns_aid.publish(
name=agent_name,
domain=domain,
protocol=protocol,
endpoint=endpoint,
port=port,
capabilities=capabilities,
version=version,
description=description,
ttl=ttl,
backend=_resolve_backend(backend_name),
)
return json.dumps(result.model_dump(), default=str)


async def unpublish_agent(
agent_name: str,
domain: str,
protocol: str = "mcp",
backend_name: Optional[str] = None,
) -> str:
"""Remove an AI agent's DNS-AID records.

Args:
agent_name: Agent identifier to remove.
domain: Domain the agent is published under.
protocol: Protocol.
backend_name: DNS backend name.
"""
import dns_aid

deleted = await dns_aid.unpublish(
name=agent_name, domain=domain, protocol=protocol, backend=_resolve_backend(backend_name)
)
if deleted:
return json.dumps(
{"success": True, "message": f"Agent '{agent_name}' unpublished from {domain}"}
)
return json.dumps(
{"success": False, "message": f"Agent '{agent_name}' not found at {domain}"}
)


def get_dns_aid_tools(backend_name: Optional[str] = None) -> list["FunctionTool"]:
"""Return DNS-AID tools wrapped as Google ADK FunctionTool objects.

Args:
backend_name: Optional DNS backend for publish/unpublish operations.
"""
from google.adk.tools import FunctionTool

tools = [FunctionTool(discover_agents)]
if backend_name:
# Create closures that bind the backend_name with explicit signatures
# so FunctionTool can introspect parameters for the LLM tool schema.
async def _publish(
agent_name: str,
domain: str,
protocol: str = "mcp",
endpoint: str = "",
port: int = 443,
capabilities: Optional[list[str]] = None,
version: str = "1.0.0",
description: Optional[str] = None,
ttl: int = 3600,
) -> str:
return await publish_agent(
backend_name=backend_name,
agent_name=agent_name,
domain=domain,
protocol=protocol,
endpoint=endpoint,
port=port,
capabilities=capabilities,
version=version,
description=description,
ttl=ttl,
)

async def _unpublish(
agent_name: str,
domain: str,
protocol: str = "mcp",
) -> str:
return await unpublish_agent(
backend_name=backend_name,
agent_name=agent_name,
domain=domain,
protocol=protocol,
)

_publish.__name__ = "publish_agent"
_publish.__doc__ = publish_agent.__doc__
_unpublish.__name__ = "unpublish_agent"
_unpublish.__doc__ = unpublish_agent.__doc__
tools.append(FunctionTool(_publish))
tools.append(FunctionTool(_unpublish))
else:
tools.append(FunctionTool(publish_agent))
tools.append(FunctionTool(unpublish_agent))
return tools
Loading