Discover an agent, then connect to it

The loop every orchestrator runs: find candidate agents, resolve each by name, decide what to trust, read the endpoint for the protocol you speak, and connect. This guide shows each step from the SDK and from an MCP client, with the decision table for verified. Chain facts below are real; the scraper.agt outputs are illustrative — no agent has published a verified manifest on mainnet yet.

1. Find candidates

You may already have a name (a user typed it, another agent handed it over). If not, the directory lists every active name with the endpoint protocols it has on chain:

GET /api/agents — real responsessh
curl "https://agtnames.com/api/agents?protocol=mcp"
# { "success": true, "count": 0, "agents": [] }          ← no agent has published an MCP endpoint yet (2026-09-12)

curl "https://agtnames.com/api/agents"
# { "success": true, "count": 1, "agents": [{ "domain": "launchpad.agt", "protocols": [], "endpoints": [],
#   "capabilities": [], "owner": "0x37007a1c233f00b423bc0d177ac5b50ca9417596", "perpetual": true, … }] }

2. Resolve and decide what to trust

resolve.tsts
import { AgtResolver } from "@agtnames/resolver";
const agt = new AgtResolver({ chain: "polygon" });

const r = await agt.resolveAgent("scraper.agt");
if (!r.registered)  throw new Error("nobody holds this name");
if (!r.active)      console.warn("name is in its grace period — records may be hidden");

if (r.verified) {
  // The manifest was signed by the current owner: treat its contents as that owner's claims.
  const caps = r.manifest.capabilities.map((c) => c.id);          // e.g. ["web-scraping", "extraction"]
  const mcp  = r.manifest.endpoints.find((e) => e.protocol === "mcp")?.url ?? r.records.endpoints.mcp;
} else {
  // Say so. Use chain facts (owner, records.endpoints) but label anything from the manifest unverified.
  console.log("unverified:", r.reasons);                             // e.g. ["no manifest set"]
}
What you seeMeaningDo
registered: falseNobody holds the name.Stop. Optionally offer to register it.
active: falseRegistered but lapsed into its grace period.Records are hidden; treat as unavailable until renewed.
verified: trueThe current owner signed this manifest.Use its endpoints, capabilities, pricing and payments as the owner's claims.
verified: false, reasons: ["no manifest set"]Chain facts only — the common case today.Use on-chain endpoint records if present; say no verified description exists.
verified: false, signer / owner mismatchTampered, stale after a transfer, or signed by the wrong key.Do not act on the manifest. Show the reason.
verified: false, fetch / size / parseThe pointer exists but the document could not be read.Retry later; fall back to on-chain records.

3. Get the endpoint

Endpoints exist in two places: the on-chain record (one URL per protocol) and the manifest's endpoints[]. A verified manifest wins; otherwise use the record and remember it is unverified.

precedencets
// SDK — prefer the verified manifest, fall back to the on-chain record
const url = (r.verified && r.manifest.endpoints.find((e) => e.protocol === "mcp")?.url) || r.records.endpoints.mcp || null;

// MCP server — the same precedence, with provenance
agt_endpoint({ name: "scraper.agt", protocol: "mcp" })
// { "url": "https://scraper.example/mcp", "source": "verified-manifest", "verified": true, "reasons": [] }
// { "url": "https://scraper.example/mcp", "source": "resolver-record",   "verified": false, "reasons": ["no manifest set"] }
// { "url": null, "source": null, … }                                     ← nothing published for that protocol

4. Connect

connect.ts — Streamable HTTPts
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

// url came from a *verified* manifest (or you told the user it did not)
const agent = new Client({ name: "orchestrator", version: "1.0.0" });
await agent.connect(new StreamableHTTPClientTransport(new URL(url)));

const { tools } = await agent.listTools();
const result = await agent.callTool({ name: tools[0].name, arguments: { url: "https://example.com" } });
await agent.close();

The agent's URL is just another MCP server. Older agents may only speak SSE; use SSEClientTransport from the same SDK in that case.

In Claude Code the whole loop is a sentence:

Claude Codesh
claude mcp add agt -- npx -y @agtnames/mcp
> Find scraper.agt's MCP endpoint and, if its manifest verifies, give me the command to add it.

# Claude: agt_resolve → verified: true → agt_endpoint(mcp) → "https://scraper.example/mcp" (verified-manifest)
#   "Run: claude mcp add scraper --transport http https://scraper.example/mcp"
# The plugin's skill offers the command; it never adds a third-party server on its own.

5. Pay (shape only)

Two records describe money: the on-chain agentWallet (records.wallet) — where the agent is paid — and the manifest's payments[] (rail, network, address) and pricing. Read them like any other claim: only from a verified manifest.

Putting it together

  1. GET /api/agents?protocol=mcp (or a name you were given).
  2. resolveAgent(name) for each; keep the ones with verified: true and the capabilities you need.
  3. Pick one; take its mcp URL from the manifest.
  4. Connect with the MCP SDK, list tools, call.
  5. Pay to records.wallet if the manifest prices the work.

Reference