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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ All notable changes to DNS-AID will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.12.1] - 2026-03-12

### Added
- **MCP endpoint path resolution** — DNS SVCB records provide only host:port, but MCP agents serve their JSON-RPC handler at sub-paths (e.g., `/mcp`). New `resolve_mcp_endpoint()` discovers the correct path via `/.well-known/agent.json` with `/mcp` convention fallback. Applied automatically in `call_mcp_tool()` and `list_mcp_tools()`.

### Fixed
- **MCP tool invocations failing on DNS-discovered agents** — `call_mcp_tool` and `list_mcp_tools` posted to the root URL (`/`) instead of the MCP handler path (`/mcp`), causing 404 errors for agents discovered via DNS.
- **Default A2A timeout too short** — `send_a2a_message` MCP tool default timeout increased from 30s to 60s. Agents performing multi-step analysis (DNS lookups, DNSSEC checks, TLS probing) need more than 30s.
- **LLM tool selection confusion** — Improved `list_published_agents` and `discover_agents_via_dns` tool descriptions to clarify when each should be used (managed domains with credentials vs. any public domain).

## [0.12.0] - 2026-03-12

### Added
Expand Down
2 changes: 1 addition & 1 deletion CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ repository-code: "https://github.com/infobloxopen/dns-aid-core"
authors:
- name: "The DNS-AID Authors"

version: "0.12.0"
version: "0.12.1"
date-released: "2026-03-12"

keywords:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "dns-aid"
version = "0.12.0"
version = "0.12.1"
description = "DNS-based Agent Identification and Discovery - Reference Implementation"
readme = "README.md"
license = "Apache-2.0"
Expand Down
2 changes: 1 addition & 1 deletion src/dns_aid/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
# Alias for convenience
delete = unpublish

__version__ = "0.12.0"
__version__ = "0.12.1"
__all__ = [
# Core functions (Tier 0)
"publish",
Expand Down
68 changes: 68 additions & 0 deletions src/dns_aid/core/invoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,66 @@ def extract_mcp_content(result: dict) -> dict | str | list | None:
return rpc_result


# ---------------------------------------------------------------------------
# MCP endpoint path resolution
# ---------------------------------------------------------------------------

# DNS SVCB records give host:port but not the path where the MCP JSON-RPC
# handler lives (could be /mcp, /api/mcp, etc.). This helper discovers the
# correct path via /.well-known/agent.json or falls back to the /mcp convention.

_MCP_CONVENTIONAL_PATH = "/mcp"


async def resolve_mcp_endpoint(endpoint: str, *, timeout: float = 5.0) -> str:
"""Resolve the full MCP JSON-RPC endpoint URL including path.

DNS SVCB records provide only host:port. The actual MCP handler may live
at a sub-path (e.g. ``/mcp``). This function discovers the correct path:

1. If *endpoint* already contains a non-root path → return as-is.
2. Fetch ``/.well-known/agent.json`` → use ``endpoints.mcp`` if present.
3. Fallback → append ``/mcp`` (the emerging convention).

Args:
endpoint: Base endpoint URL, typically from DNS discovery.
timeout: HTTP timeout for the agent.json probe.

Returns:
Fully-qualified MCP endpoint URL with path.
"""
base = normalize_endpoint(endpoint)
parsed = urlparse(base)

# Already has a meaningful path — caller knows what they're doing
if parsed.path and parsed.path not in ("/", ""):
return base

# Try /.well-known/agent.json for the authoritative path
agent_json_url = f"{base}/.well-known/agent.json"
try:
async with httpx.AsyncClient(timeout=timeout, verify=True) as client:
resp = await client.get(agent_json_url)
if resp.status_code == 200:
data = resp.json()
endpoints = data.get("endpoints", {})
if isinstance(endpoints, dict):
mcp_path = endpoints.get("mcp")
if mcp_path and isinstance(mcp_path, str):
# Absolute path from agent.json
resolved = (
f"{base}{mcp_path}" if mcp_path.startswith("/") else f"{base}/{mcp_path}"
)
logger.debug("resolve_mcp.from_agent_json", path=mcp_path, url=resolved)
return resolved
except Exception:
pass # agent.json unavailable — fall through to convention

# Convention fallback
logger.debug("resolve_mcp.convention_fallback", path=_MCP_CONVENTIONAL_PATH)
return f"{base}{_MCP_CONVENTIONAL_PATH}"


# ---------------------------------------------------------------------------
# Shared helper: build AgentRecord from endpoint URL
# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -598,6 +658,9 @@ async def call_mcp_tool(
) -> InvokeResult:
"""Call a tool on a remote MCP agent.

Automatically resolves the MCP endpoint path if only host:port is given
(common with DNS-discovered agents whose SVCB records lack path info).

Args:
endpoint: MCP agent endpoint URL.
tool_name: Name of the tool to call.
Expand All @@ -608,6 +671,7 @@ async def call_mcp_tool(
Returns:
InvokeResult with the tool's response.
"""
endpoint = await resolve_mcp_endpoint(endpoint)
mcp_args = {"name": tool_name, "arguments": arguments or {}}

if _sdk_available:
Expand Down Expand Up @@ -636,6 +700,8 @@ async def list_mcp_tools(
) -> InvokeResult:
"""List available tools on a remote MCP agent.

Automatically resolves the MCP endpoint path if only host:port is given.

Args:
endpoint: MCP agent endpoint URL.
timeout: Request timeout in seconds.
Expand All @@ -644,6 +710,8 @@ async def list_mcp_tools(
Returns:
InvokeResult with ``data`` containing the tools list.
"""
endpoint = await resolve_mcp_endpoint(endpoint)

if _sdk_available:
result = await _invoke_via_sdk(
endpoint,
Expand Down
17 changes: 11 additions & 6 deletions src/dns_aid/mcp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ def discover_agents_via_dns(
use_http_index: bool = False,
) -> dict:
"""
Discover AI agents at a domain using the DNS-AID protocol (IETF draft-mozleywilliams-dnsop-dnsaid-01).
Discover AI agents at any public domain using the DNS-AID protocol (no credentials needed).

Discovery flow (DNS-only, default):
1. Query the TXT index record at _index._agents.{domain} to get the list of
Expand Down Expand Up @@ -650,13 +650,18 @@ def list_published_agents(
backend: Literal["route53", "cloudflare", "infoblox", "nios", "ddns", "mock"] = "route53",
) -> dict:
"""
List all agents published at a domain via DNS-AID.
List all agents published at a domain you manage via DNS-AID.

Queries the DNS backend for all _agents.* records in the specified zone.
This tool requires backend API credentials (e.g., AWS keys for Route53,
API key for Infoblox). Use this only for domains you own and have
configured backend access for.

To discover agents at any public domain (no credentials needed), use
discover_agents_via_dns instead.

Args:
domain: Domain to list agents from (e.g., "example.com").
backend: DNS backend to use - "route53" for AWS Route53 or "mock" for testing.
domain: Domain you manage (e.g., "highvelocitynetworking.com").
backend: DNS backend to use - requires matching API credentials configured.

Returns:
dict with:
Expand Down Expand Up @@ -999,7 +1004,7 @@ def send_a2a_message(
endpoint: str | None = None,
domain: str | None = None,
name: str | None = None,
timeout: float = 30.0,
timeout: float = 60.0,
) -> dict:
"""
Send a message to an A2A (Agent-to-Agent) agent and get its response.
Expand Down
Loading