|
| 1 | +# DNS-AID |
| 2 | + |
| 3 | +Decentralized agent discovery for Google ADK via DNS SVCB records — the |
| 4 | +counterpart to [`agent_registry`](../agent_registry/), which uses Google |
| 5 | +Cloud's centralized agent registry. |
| 6 | + |
| 7 | +## What is DNS-AID? |
| 8 | + |
| 9 | +DNS-AID encodes agent metadata into DNS SVCB records (RFC 9460) under the |
| 10 | +naming convention `_<name>._<protocol>._agents.<domain>` (for example |
| 11 | +`_chat._mcp._agents.example.com`). The |
| 12 | +[`dns-aid`](https://pypi.org/project/dns-aid/) Python SDK (`dns-aid-core`) |
| 13 | +handles SVCB encoding/decoding, backend zone updates, and discovery |
| 14 | +queries; this integration is a thin ADK-shaped wrapper over it. |
| 15 | + |
| 16 | +Discovery is decentralized by design: each operator publishes to whatever |
| 17 | +DNS provider they already own (Route 53, Cloud DNS, Cloudflare, NS1, |
| 18 | +Infoblox NIOS, on-prem BIND/Knot via RFC 2136). There is no single trust |
| 19 | +root and no shared registry — which makes DNS-AID a natural fit for |
| 20 | +multi-cloud, federated, and air-gapped deployments where a centralized |
| 21 | +service is undesirable or unavailable. |
| 22 | + |
| 23 | +## Install |
| 24 | + |
| 25 | +```bash |
| 26 | +pip install "google-adk[dns-aid]" |
| 27 | +``` |
| 28 | + |
| 29 | +This pulls in `dns_aid>=0.18,<1` and the `[a2a]` extra (needed for the |
| 30 | +A2A bridge). |
| 31 | + |
| 32 | +## Quickstart — discover agents |
| 33 | + |
| 34 | +```python |
| 35 | +import asyncio |
| 36 | +from google.adk.integrations.dns_aid import discover_agents |
| 37 | + |
| 38 | + |
| 39 | +async def main(): |
| 40 | + result = await discover_agents(domain='agents.example.com') |
| 41 | + for record in result['agents']: |
| 42 | + print(record['name'], record['protocol'], record['endpoint']) |
| 43 | + |
| 44 | + |
| 45 | +asyncio.run(main()) |
| 46 | +``` |
| 47 | + |
| 48 | +Filter by protocol or by name with `protocol='a2a'` / `name='chat'`. Pass |
| 49 | +`require_dnssec=True` to reject any result where end-to-end DNSSEC |
| 50 | +validation fails. |
| 51 | + |
| 52 | +## Quickstart — publish an agent |
| 53 | + |
| 54 | +```python |
| 55 | +from google.adk.integrations.dns_aid import publish_agent |
| 56 | + |
| 57 | +await publish_agent( |
| 58 | + agent_name='chat', |
| 59 | + domain='agents.example.com', |
| 60 | + protocol='mcp', |
| 61 | + endpoint='chat.internal.example.com', |
| 62 | + port=443, |
| 63 | + backend_name='route53', |
| 64 | +) |
| 65 | +``` |
| 66 | + |
| 67 | +Omit `backend_name` to use whatever default the host resolver / `dns_aid` |
| 68 | +configuration provides. See [Backend credentials](#backend-credentials) |
| 69 | +for the supported values. |
| 70 | + |
| 71 | +## Quickstart — bridge to RemoteA2aAgent |
| 72 | + |
| 73 | +```python |
| 74 | +from google.adk.integrations.dns_aid import discover_agents |
| 75 | +from google.adk.integrations.dns_aid import remote_a2a_agent_from_record |
| 76 | + |
| 77 | +result = await discover_agents(domain='agents.example.com', protocol='a2a') |
| 78 | +agents = [ |
| 79 | + remote_a2a_agent_from_record(record) |
| 80 | + for record in result['agents'] |
| 81 | +] |
| 82 | +``` |
| 83 | + |
| 84 | +`remote_a2a_agent_from_record` is synchronous — it does no I/O. |
| 85 | +`RemoteA2aAgent` fetches its agent card lazily on first invocation. |
| 86 | + |
| 87 | +## Quickstart — bridge to McpToolset |
| 88 | + |
| 89 | +```python |
| 90 | +from google.adk.integrations.dns_aid import discover_agents |
| 91 | +from google.adk.integrations.dns_aid import mcp_toolset_from_record |
| 92 | + |
| 93 | +result = await discover_agents(domain='agents.example.com', protocol='mcp') |
| 94 | +toolsets = [ |
| 95 | + mcp_toolset_from_record(record) |
| 96 | + for record in result['agents'] |
| 97 | +] |
| 98 | +``` |
| 99 | + |
| 100 | +Like its A2A counterpart, `mcp_toolset_from_record` is synchronous; the |
| 101 | +underlying `McpToolset` connects on first use. |
| 102 | + |
| 103 | +## Use as ADK FunctionTools |
| 104 | + |
| 105 | +```python |
| 106 | +from google.adk.agents import LlmAgent |
| 107 | +from google.adk.integrations.dns_aid import get_dns_aid_tools |
| 108 | + |
| 109 | +agent = LlmAgent( |
| 110 | + model='gemini-2.5-flash', |
| 111 | + tools=get_dns_aid_tools(backend_name='route53'), |
| 112 | +) |
| 113 | +``` |
| 114 | + |
| 115 | +`get_dns_aid_tools(backend_name=None)` returns FunctionTool wrappers for |
| 116 | +`discover_agents`, `publish_agent`, and `unpublish_agent` with |
| 117 | +`backend_name` pre-bound (so the LLM-facing schema does not include it). |
| 118 | +Omit `backend_name` to fall back to the default `dns_aid` configuration. |
| 119 | + |
| 120 | +## Backend credentials |
| 121 | + |
| 122 | +`backend_name` must match the exact spelling that `dns_aid` expects |
| 123 | +(hyphens, not underscores). Credentials live wherever the underlying |
| 124 | +provider SDK looks for them: |
| 125 | + |
| 126 | +| `backend_name` | Auth source | Required env vars / config | |
| 127 | +|---|---|---| |
| 128 | +| `route53` | AWS SDK default chain | `AWS_PROFILE` or `AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY` (or instance profile) | |
| 129 | +| `cloudflare` | API token | `CLOUDFLARE_API_TOKEN` | |
| 130 | +| `cloud-dns` | Google ADC | `GOOGLE_APPLICATION_CREDENTIALS` (or `gcloud auth application-default login`) | |
| 131 | +| `ns1` | API key | `NS1_API_KEY` | |
| 132 | +| `infoblox` / `nios` | NIOS API user | `INFOBLOX_HOST`, `INFOBLOX_USERNAME`, `INFOBLOX_PASSWORD` | |
| 133 | +| `ddns` | RFC 2136 TSIG | `DDNS_TSIG_KEYFILE`, `DDNS_SERVER` | |
| 134 | +| `mock` | n/a — in-memory only | (no creds; for tests) | |
| 135 | + |
| 136 | +> Credentials are resolved by `dns_aid` itself, not by ADK. ADK does not |
| 137 | +> read these env vars directly — it just hands the `backend_name` string |
| 138 | +> to `dns_aid.backends.create_backend(...)`. If a publish or unpublish |
| 139 | +> call fails with a backend-specific authentication error, check the |
| 140 | +> `dns-aid-core` docs for that provider. |
| 141 | +
|
| 142 | +## Programmatic API |
| 143 | + |
| 144 | +| Symbol | Description | |
| 145 | +|---|---| |
| 146 | +| `discover_agents` | Async; query DNS SVCB records under a domain. | |
| 147 | +| `publish_agent` | Async; create SVCB + TXT records via the named backend. | |
| 148 | +| `unpublish_agent` | Async; remove the records. Returns a structured status dict that distinguishes `not_found`, `permission_denied`, `backend_unavailable`, and `throttled`. | |
| 149 | +| `get_dns_aid_tools` | Build the FunctionTool list for an `LlmAgent`. | |
| 150 | +| `remote_a2a_agent_from_record` | Bridge to `RemoteA2aAgent` (protocol `a2a`). | |
| 151 | +| `mcp_toolset_from_record` | Bridge to `McpToolset` (protocol `mcp`). | |
| 152 | + |
| 153 | +## Future |
| 154 | + |
| 155 | +The current integration ships discovery, publish, unpublish, and the two |
| 156 | +bridges. Future revisions can layer: |
| 157 | + |
| 158 | +- **Cap-doc fetch and verify** — `dns-aid-core` exposes `cap_fetcher` for |
| 159 | + pulling the SVCB-referenced capability document. |
| 160 | +- **Trust verification** — DNSSEC, DANE, and `cap-sha256` checks via |
| 161 | + `dns_aid.core.validator`. |
| 162 | +- **Policy enforcement** — Phase 6 of `dns-aid-core` provides |
| 163 | + `PolicyEvaluator` for evaluating policies referenced by `policy_uri` |
| 164 | + in the SVCB record. |
| 165 | + |
| 166 | +## References |
| 167 | + |
| 168 | +- DNS-AID spec: `draft-mozleywilliams-dnsop-dnsaid` (IETF Internet-Draft) |
| 169 | +- `dns-aid-core`: the [`dns-aid`](https://pypi.org/project/dns-aid/) |
| 170 | + PyPI package |
| 171 | +- A2A spec: [Agent-to-Agent protocol](https://google.github.io/A2A/) |
| 172 | +- ADK FunctionTool: [function tools documentation](https://google.github.io/adk-docs/tools/function-tools/) |
0 commit comments