# One name, five protocols: endpoints in a .agt manifest

> Source: <https://agtnames.substack.com/p/one-name-five-protocols>
> Published: 2026-09-26 14:42:47+00:00

An agent that speaks MCP and an agent that speaks A2A can both be reached through the same .agt name. The name does not pick a transport. It lists the endpoints the agent exposes, one per protocol, and the client takes the one it understands. This article is about that `endpoints[]` field, how it relates to the endpoint records stored on-chain, and what a client does with both.

## The shape

Each entry has a `protocol`, a `url` and an optional `version`:

```
"endpoints": [
  { "protocol": "mcp",  "url": "https://exampleagent.example/mcp", "version": "2025-11-05" },
  { "protocol": "a2a",  "url": "https://exampleagent.example/.well-known/agent.json" },
  { "protocol": "http", "url": "https://exampleagent.example/api/v1" },
  { "protocol": "ws",   "url": "wss://exampleagent.example/stream" },
  { "protocol": "grpc", "url": "https://grpc.exampleagent.example:443" }
]
```

The `protocol` value is one of five reference ids or a custom string:

- `mcp` : Model Context Protocol. The URL is the server endpoint an MCP client connects to.
- `a2a` : Agent-to-Agent Protocol. The URL points at the agent card, conventionally`/.well-known/agent.json` . The manifest does not embed the card; it points to it.
- `http` : a REST or RPC API over HTTPS. The URL is the base of the API.
- `ws` : WebSocket. The URL should be`wss://` .
- `grpc` : gRPC. The URL is the host and port the channel dials.

Custom protocol ids are permitted. Lowercase, hyphenated, the same rule as capability ids. A reader that does not recognize the id ignores the entry and moves on.

One entry per protocol. The [manifest editor](https://agtnames.com/manifest) enforces this, along with `https://` for web URLs and `wss://` for WebSocket. If you have two MCP servers, put the one you want strangers to use in the manifest and document the other elsewhere.

Here is the live example. `launchpad.agt` exposes a single HTTP endpoint today:

```
"endpoints": [
  { "url": "https://agtnames.com/api/v2/manifest", "protocol": "http" }
]
```

## Two places an endpoint can live

Endpoints exist in two layers, and it is worth being precise about which is which.

The manifest is the signed document. Its `endpoints[]` is covered by the owner's EIP-191 signature. A client that verifies the manifest knows the owner published those URLs.

The resolver contract holds `agentEndpoint(protocol)` records, one URL per protocol, keyed to the name's node. These are plain on-chain writes from the owner wallet. They are fast to read with a single `eth_call`, they need no fetch of a document, and they are what a wallet, an indexer or a DNS projection reads when it does not want to parse JSON. The [records reference](https://agtnames.com/docs/records) lists them next to `addr`, `text`, `agentManifest`, `agentWallet` and `agentKey`.

The editor writes both in one transaction. When you sign and publish, the manifest goes to its host and a `multicall` on the resolver sets the manifest pointer and one `setAgentEndpoint` per protocol. Removing a protocol in the editor clears the on-chain record in the same call. So for a name published through the site, the two layers agree.

They can drift when someone writes one layer from code and forgets the other. That is fine, as long as readers know which one wins.

## Which one wins

The `agt_endpoint` tool in the `@agtnames/mcp` server answers exactly this question. Ask it for a name and a protocol:

```
{ "name": "exampleagent.agt", "protocol": "mcp" }
```

It resolves the name, then applies a two-step rule. If the manifest verifies, it takes the URL from the manifest's `endpoints[]`. Otherwise it falls back to the on-chain `agentEndpoint` record for that protocol. The response says which one it used:

```
{
  "name": "exampleagent.agt",
  "protocol": "mcp",
  "url": "https://exampleagent.example/mcp",
  "source": "verified-manifest",
  "verified": true,
  "pricing": "freemium",
  "reasons": [],
  "notice": "..."
}
```

`source` is `verified-manifest` or `resolver-record`. `verified` is `true` only when the URL came from a manifest that passed the three-way check (signer equals `manifest.owner` equals on-chain owner). A URL from the resolver record is still published by the owner, since only the owner wallet can write it, but the tool marks it `verified: false` so that an agent treats it as unverified third-party data rather than a signed statement. `pricing` comes from the verified manifest only and is `null` otherwise.

The tool's `protocol` input accepts `mcp`, `a2a`, `http` and `ws`. For `grpc` or a custom protocol, read the full manifest with `agt_manifest` or the SDK and pick the entry yourself.

The `@agtnames/resolver` package exposes both layers side by side, so you can apply the same rule or your own:

``` js
import { AgtResolver } from "@agtnames/resolver";

const agt = new AgtResolver({ chain: "polygon" });
const r = await agt.resolveAgent("exampleagent.agt");

r.records.endpoints.mcp;                      // on-chain agentEndpoint("mcp")
r.verified && r.manifest?.endpoints;          // the signed list, only when verified
```

The rule of thumb: prefer the signed list when it verifies, use the on-chain record when it does not, and tell the caller which one you used.

## Dispatching on the protocol you speak

A client does not iterate `endpoints[]` and try each one. It looks for the protocol it implements and connects to that URL. Everything else is ignored.

An MCP client, for instance a coding assistant with the `agt` server installed, asks `agt_endpoint` for `mcp` and registers the returned URL as a remote MCP server. It never looks at the A2A card.

An A2A client asks for `a2a`, fetches the agent card at that URL, and proceeds with the A2A handshake. It never opens an MCP session.

A plain script that wants a REST call asks for `http`, appends its path to the base URL, and sends the request. If `pricing` came back as `paid` or `freemium`, it knows to look at the manifest's `payments[]` before calling.

A streaming consumer asks for `ws` and opens the socket. A service mesh asks for `grpc` and dials the channel.

The point is that the manifest is protocol-neutral and the dispatch is protocol-specific. The agent publishes once. Each client does one lookup and one connect. Adding a sixth protocol later is one more entry in the list and one more resolver record, and clients that do not speak it are unaffected.

## Examples per protocol

Hypothetical manifests, one per protocol, showing what a well-formed entry looks like:

```
{ "protocol": "mcp", "url": "https://research.example/mcp", "version": "2025-11-05" }
```

The `version` here is the MCP protocol revision the server implements. An MCP client can check compatibility before connecting.

```
{ "protocol": "a2a", "url": "https://research.example/.well-known/agent.json" }
```

The agent card carries its own version and skills. The manifest only needs to say where it is.

```
{ "protocol": "http", "url": "https://research.example/api/v2", "version": "2" }
```

A base URL and the API major version. Paths and methods belong in your API docs, or in the `input` and `output` schemas on a capability.

```
{ "protocol": "ws", "url": "wss://research.example/stream" }
```

Secure WebSocket. The editor rejects `ws://`.

```
{ "protocol": "grpc", "url": "https://grpc.research.example:443", "version": "v1" }
```

Host, port and the service version. The `.proto` contract is published wherever you publish it; the manifest points at the channel.

```
{ "protocol": "acp", "url": "https://research.example/acp" }
```

A custom protocol id. Readers that know `acp` will use it. Readers that do not will skip it.

## Versioning advice

`version` is optional and free-form, so use it for the thing a client would check before connecting. For `mcp` that is the protocol revision date. For `http` and `grpc` it is your API's major version. For `a2a` leave it out; the card carries its own.

When you ship a breaking change, publish a new manifest with the new `version`. Since the manifest carries an `updated` timestamp and the whole body is signed, a client that cached the old one can see the difference and re-verify. If the manifest is hosted at `https://agts.dev/<label>.json`, the republish keeps the same URL and costs nothing on-chain. Update the on-chain `agentEndpoint` record too if the URL changed, so the fallback path stays correct.

Run old and new side by side during a migration if you can, and change `version` only when the URL or the contract actually changed. A client that compares `version` should never see a change that means nothing.

## Where this leaves you

A .agt name is a single lookup that returns every transport your agent supports, signed by the wallet that owns the name. Publish the protocols you actually serve, keep the on-chain records in step, and let each client pick its own.

The [records reference](https://agtnames.com/docs/records) documents the on-chain `agentEndpoint` record alongside the rest of the resolver model, with the contract address and the write calls. Start there if you are publishing from code, or open the [manifest editor](https://agtnames.com/manifest) if you would rather sign in your wallet.
