ENSIP-27: Agent Card Schema (/.well-known/agent.json)

PR: https://github.com/ensdomains/ensips/pull/75

ENSIP-26 standardised how to find an agent via ENS text records (agent-endpoint[mcp], agent-endpoint[a2a]). It intentionally left the format of the document at the discovered endpoint open for a future ENSIP.

This is that future.

ENSIP-27 defines the schema for the agent card, the JSON resource served at /.well-known/agent.json that a client fetches after resolving the ENS text record. Without a shared schema, every gateway invents its own format and clients can’t parse agent cards interoperably.


The discovery chain it completes:

ENS name
  → agent-endpoint[mcp] text record     (ENSIP-26)
  → /.well-known/agent.json              (ENSIP-27)
  → mcp endpoint + capabilities + on-chain identity



What the schema defines:

Required: schema_version, name, url (MCP endpoint), provider, version, capabilities, authentication, skills.

Key optional fields:

Field Purpose
erc8004 On-chain identity anchor — { registry, agentId }
trustScope A2A depth limiting — { transitive, maxDepth, capabilities }
sanitizationSpec IPFS URI of input sanitization pipeline (addresses the prompt injection concern raised in the ENSIP-26 thread)
inputSources Declared input origin scope

Companion PR:

PR #76 contributes erc8004 to ENSIP-26’s agent-context and registers agent-endpoint[ens-acs] (ENS Agent Card Schema) as the dedicated protocol value pointing to the card, namespaced per reviewer feedback to keep the keyspace unambiguous.


Reference implementation - live today:

GET https://gateway.ensub.org/agent/0xe61f5a6783ae09949b9a1b6821b68f89c0d7bb2d/5/.well-known/agent.json


Three ERC-8004 collection registries on Ethereum mainnet. Agent cards served via CCIP-Read for *.dinamic.eth subnames, no on-chain transaction per agent registration.


Open questions for discussion:

  1. Should /.well-known/agent.json be the only valid path, or should agent-endpoint[ens-acs] allow any URL? Multi-agent gateways may want per-agent paths rather than a root-level card.

  2. Should erc8004 be required rather than optional, should every ENSIP-27-compliant card declare an on-chain identity?

  3. Is sanitizationSpec too implementation-specific for a general schema, or does input provenance belong at the agent card level?

2 Likes

If you want to join the ENSxAI call, it would be great to have you present this ENSIP.

Here is a link to the telegram group.

Weekly calls are on Wed at 10 am ET.
The link is shared in the telegram group.

1 Like

Will be there @Premm.eth , just joined Telegram! Glad to participate.Thank you

1 Like

On Github, you wrote:

Thanks @jmacwhyte — this is directly relevant and I should have caught #64 earlier.

Reading through the Agent use case in 0xLighthouse/ens-metadata#46, there’s significant overlap in intent and some differences in approach worth aligning on.

Where we converge:

  • Both use parameterized text record keys for service endpoints
  • Both reference ERC-8004 as the on-chain agent identity anchor
  • Both treat the ENS name as the canonical discovery point for the agent

Where we differ:

Concern ENSIP-64 / ens-metadata ENSIP-27
Service endpoint keys services[mcp], services[a2a] agent-endpoint[mcp], agent-endpoint[a2a]
Node classification class = "Agent" + schema = "ipfs://..." implicit via /.well-known/agent.json
Cross-registry refs registrations[0] (CAIP-19) not specified
Resolution Static text records CCIP-Read (EIP-3668) — dynamic, off-chain computed

The key naming divergence (services[*] vs agent-endpoint[*]) is the most important one to resolve — clients can’t support both without knowing which to look for.

On CCIP-Read: ENSIP-27’s primary use case is CCIP-Read resolution — the agent’s ENS name resolves dynamically via an off-chain gateway, which enables private endpoints, runtime-computed metadata, and records that don’t require on-chain writes. This is complementary to the static text record path rather than competing with it. An agent could publish stable fields as static records and serve dynamic ones via CCIP-Read.

The registrations[*] CAIP-19 pattern is a genuine gap in ENSIP-27 — cross-chain registry references should be in scope. Already shipped this in the reference implementation: pixel-goblins.dinamic.eth now returns registrations[0] = eip155:1/erc721:0xe61f.../5 via CCIP-Read, verifiable with any ENS resolver.

The key naming (services[*] vs agent-endpoint[*]) and node classification (class = "Agent") are the remaining open questions — happy to align once we agree on the right convention. No point shipping a rename twice.

Would it make sense to treat ENSIP-64 as the classification and metadata layer, and ENSIP-27 as the agent-card content spec that sits on top of it? The /.well-known/agent.json format and the CCIP-Read serving mechanism feel like a different layer from node classification.

Open to that conversation here or wherever makes sense to the #64 working group.

Our reference agent schema uses services[*] to match the formatting of an ERC-8004 manifest. This allows users to publish their agent metadata using a combination of records stored directly on their ENS name and additional off-chain data available via the ERC-8004 agent uri. Consumers can query the ENS metadata and receive an object that follows the same structure as an 8004 manifest file, and if they want to check for additional data they can also follow the agent uri if one is provided.

The schema record also explains to users exactly what data they are expected to find attached to the ENS name, so users who are not familiar with any of these standards can easily understand what they are looking at. The agent-endpoint[*] naming is unrelated to 8004 formatting, and would require users to be familiar with ENSIP-27 in order to understand how to make use of it.

As to your comments about CCIP-Read, I am not sure how that is related. From what I can see, CCIP is not mentioned by your draft ENSIP or ENSIP-27. What is the intended use case? Do you plan to develop smart contracts that modify their state based on data that is hosted off-chain? A much easier solution would be to publish that same data to an ENS name using ENSIP-64 (temp.number) which would make it directly accessible to smart contracts with no off-chain read necessary.

1 Like

Thanks for the detailed response @jkm.eth , a few pushbacks worth working through before we align on naming.


On services[*] vs agent-endpoint[*]

agent-endpoint[*] is already defined in ENSIP-26, which is an existing draft in this same repository. Renaming it in ENSIP-27 would require amending ENSIP-26 - that’s more disruptive than ENSIP-64 recognising agent-specific keys as a known convention.

More importantly, the names serve different semantic purposes:

  • services[*] is deliberately generic - ENSIP-64 needs it to be, because it covers organisations, delegates, contracts, and agents under the same classification scheme.

  • agent-endpoint[*] is self-describing - it tells a resolver exactly what it is looking at without first checking class = "Agent".

This matters for tooling. A client discovering MCP agents should be able to query agent-endpoint[mcp] directly without first fetching node classification metadata and filtering on class = "Agent". One lookup, unambiguous intent.

There is also a collision risk: services[mcp] will not always mean an AI agent. MCP is not exclusively an AI protocol, and ENS names used by non-agent entities could legitimately carry a services[mcp] record with a completely different meaning.

The proposal: rather than ENSIP-27 adopting services[*], ENSIP-64 could recognise agent-endpoint[*] as the canonical convention for nodes classified as Agent. The classification layer (ENSIP-64) and the endpoint convention (ENSIP-26/27) operate at different levels — they do not need to share key names.


On CCIP-Read

Publishing to an ENS name is the right approach for stable, rarely-changing fields. It is not equivalent to CCIP-Read for agent use cases because:

  • Attestations include per-execution hashes and signatures that are stale the moment they are written on-chain. A gas write per agent action is not a viable model.

  • The gateway signs each /.well-known/agent.json response at fetch time. A static record cannot carry a live signature over its own content.

  • Some agent endpoints are authenticated or private. Publishing them as public calldata is a security concern, not a simplification.

CCIP-Read and static ENS records are complementary. An agent that never needs dynamic content can skip CCIP-Read and serve a static JSON file — the schema is the same either way. ENSIP-27 does not mandate CCIP-Read; it is just the natural fit for agents that need it.


On input security

One concern with open agent endpoints is prompt injection and unverified inputs reaching the model. ENSIP-27 addresses this at the schema level via sanitizationSpec — a pointer (IPFS or URL) to the exact sanitization pipeline the agent applies before execution. Every attestation produced includes a sanitization_pipeline_hash that commits to the pipeline used, so any consumer can verify the input was processed as declared. This is the WYRIWE (What You Read Is What Executed) guarantee — the agent card advertises its sanitization contract, and attestations prove it was honoured. Neither static text records nor the ENSIP-64 metadata layer currently have a mechanism for this.


Reference implementation is live and open for inspection:
GET https://gateway.ensub.org/agent/0xe61f5a6783ae09949b9a1b6821b68f89c0d7bb2d/5/.well-known/agent.json

CCIP-Read resolver on dinamic.eth. The registrations[*] CAIP-19 pattern from ENSIP-64 is now incorporated - the live endpoint returns:

"registrations": [
  {
    "agentId": "5",
    "agentRegistry": "eip155:1/erc8004:0xe61f5a6783ae09949b9a1b6821b68f89c0d7bb2d/5",
    "agentURI": "https://gateway.ensub.org/agent/0xe61f5a6783ae09949b9a1b6821b68f89c0d7bb2d/5"
  }
]


Happy to align further once the naming question is settled.

One development since my last post that’s directly relevant to this discussion.

ERC-8126 (AI Agent Verification) has independently converged on the same schema gap.

In March, Cybercentry proposed a major architectural shift for ERC-8126, dropping its own on-chain agent registry entirely and adopting ERC-8004 as a hard dependency. The proposal is explicit: ERC-8126 verification providers must now call tokenURI(agentId) and extract walletAddress, endpoints, contractAddress, stakingContractAddress, chainId to run their verification stack.

Those fields are not defined anywhere in ERC-8126. They are not defined in ERC-8004 either — ERC-8004 specifies the registry interface, not what tokenURI must return. Two standards now share a dependency on a tokenURI metadata schema that doesn’t formally exist yet.

That is exactly what ENSIP-27 proposes to define.

For ERC-8126 specifically:

  • walletAddress → registry.getAgentWallet(agentId) — already the canonical resolution path in ERC-8004, ENSIP-27 makes it explicit in the agent card

  • endpoints → agent-endpoint[*] ENS text records, surfaced via /.well-known/agent.json

  • chainId → carried in the CAIP-19 registrations[*] field now live in the reference implementation

This is a second external standards track arriving at the same need independently. The naming question (services[*] vs agent-endpoint[*]) remains open, but the schema question is becoming more urgent, ERC-8126 implementers will start filling the gap on their own if a standard isn’t referenced.

ERC-8126 thread for context:
https://ethereum-magicians.org/t/erc-8126-ai-agent-verification/27445/13

Live tokenURI if useful as a reference for what the schema looks like in practice:

GET https://gateway.ensub.org/agent/0xe61f5a6783ae09949b9a1b6821b68f89c0d7bb2d/5/.well-known/agent.json


TMerlini / dinamic.eth

A few more comments, based on what you’ve asked and in general:

  1. Your proposal states: “an agent card MUST be served at […] where base-url is the value of the agent-endpoint[mcp] or agent-endpoint[a2a]text record, or the root of any agent gateway domain.”

This seems somewhat unrelated to ENS. There are many agent-specific standards that could specify that an agent card must be found at the advertised MCP or A2A URL, so why does this need to be an ENS standard?

  1. CCIP-Read: I was suggesting the URL to the offline resource could be stored as an ENS text record, instead of requiring consumers to request it from a different smart contract as part of a CCIP-Read flow. My understanding is that CCIP-Read is intended to allow off-chain data to be injected into smart contract operations; if you are simply doing a read-only operation to retrieve an off-chain url to some agent metadata, why is CCIP-Read needed? I still don’t understand the use case.

  2. services[*] vs agent-endpoint[*]: ENSIP-64 allows you to use whatever name you want. It is perfectly fine to create your own schema that aligns with ENSIP-26 and not ERC-8004. However, as stated above, I don’t quite understand the need for the proposed ENSIP. It seems to boil down to “if an agent endpoint is advertised using any of the existing standards, an agent card should be discoverable at that location” which doesn’t seem to be an ENS-specific issue.

If you want to attach an agent card to an ENS name, I would recommend simply using an ENSIP-64 schema that has an “agent-card” property that points directly to the off-chain URL for the agent card. Then instead of telling clients to look for a specific text record, you would just tell them to look for an ENSIP-64 record that uses the schema you have created.

1 Like

@jkm.eth fair points, appreciate the direct feedback.

The ENSIP-64 suggestion is the valid one. The schema and discovery convention is finding a better home inside ERC-8263 v0.2 (which is absorbing the triple-hash input scheme) and ERC-8004’s tokenURI definition - both of which are ERC-track, not ENSIP-track. The ENS-specific part of what we were proposing is thinner than we made it look.

We’ll close this out and implement the convention directly. No hard feelings - you’re right that this doesn’t need to be an ENS standard.

dinamic.eth / TMerlini

1 Like

I often find that less is more when it comes to ENSIPs.

If you have an idea for a key/value pair that should be added to ENSIP-26, I would be very interested to know. Some candidates might be agent-version, agent-runtime, and agent-tool[<toolname>].

1 Like

Sounds good! Thanks for coming to chat about it, that’s the whole point of these forums :+1:

2 Likes

Agreed “less is more” good call, and I think jkm feedback pushed things in the right direction.

agent-tool[] is interesting, that’s essentially the pattern the reference implementation already uses (agent-endpoint[mcp], agent-endpoint[a2a]). Worth picking up if there’s appetite to add it to ENSIP-26.

Appreciate the invite and the constructive framing throughout. Will keep an eye on where this lands.

jus my few cents..
as per specs /.well-known dir should be at root domain.tld/.well-known/.., no point in adding that in deep sub dir.. in past we’ve used reverse domain.eth.limo/.well-known/eth/domain/...json for ccip data storage, so same domain can hold all of wildcard/alt signed records.

The RFC 5785 point is valid but the solution doesn’t require adopting the .eth.limo path convention.

domain.eth.limo/.well-known/eth/domain/...json is ENS’s own HTTPS gateway structure. If ENSIP-27 mandates that pattern, agent card resolution becomes dependent on ENS’s limo infrastructure — an independent gateway serving the same agent can’t be compliant on its own terms.

RFC 5785 only requires /.well-known/ at the authority root. It says nothing about which authority. Any CCIP-Read gateway can satisfy this:

gateway.ensub.org/.well-known/agent/{registry}/{agentId}.json


This is RFC-compliant, runs on fully independent infrastructure, and serves multiple agents from a single gateway without touching .eth.limo. The CCIP-Read resolver points to the operator’s own gateway - the .well-known path is at the root of that authority, not ENS’s.

ENSIP-27 should specify the path relative to the resolved authority , whatever gateway the CCIP-Read resolver returns. Tying the standard to .eth.limo conventions would make independent gateway operators second-class citizens and centralise agent card resolution on ENS infrastructure.

The reference implementation at dinamic.eth runs on an independent gateway today. That independence is the point.