cd /news/ai-agents/implement-agent-discovery-in-10-minu… · home topics ai-agents article
[ARTICLE · art-88172] src=dev.to ↗ pub= topic=ai-agents verified=true sentiment=· neutral

Implement agent discovery in 10 minutes

Cheela Labs released version 0.3.1 of the Agent Discovery Specification, an open standard that lets agents advertise their capabilities via a simple JSON manifest served at a well-known path. The spec requires only five fields in the manifest and mandates HTTPS except for local development, and the project provides a minimal Python validation script using jsonschema to check manifests against the normative schema.

read6 min views1 publishedAug 5, 2026

By the end of this page you will have served a valid ADS manifest, validated it

against the normative schema, and made one capability callable from an LLM

tool-calling API. Ten minutes, a text editor, and Python — which you almost

certainly already have.

This is a quickstart for the Agent Discovery Specification, an open,

Current spec version: 0.3.0 (released as v0.3.1

). If you have seen an ADS

example declaring 0.1.0

, it predates two releases. Copy from here instead.

Five fields. That is the entire requirement.

mkdir -p ads-demo/.well-known && cd ads-demo

Create .well-known/agent-discovery.json

:

{
  "specVersion": "0.3.0",
  "id": "com.example.bookshop",
  "name": "Example Bookshop",
  "provider": { "name": "Example Inc." },
  "capabilities": []
}

An empty capabilities

array is valid. "I speak ADS and I currently expose

nothing" is a real, useful answer — it is how a client tells the difference

between a system that has no capabilities and a system that has never heard of

discovery.

Serve it:

python3 -m http.server 8000

In a second terminal, fetch it the way a client would:

curl -i http://localhost:8000/.well-known/agent-discovery.json

You should see Content-type: application/json

in the response headers. The spec

requires it, and Python's dev server gets it right for free.

WhyThe spec says a clienthttp://

and nothttps://

?MUST NOTfetch

a manifest over plaintext HTTP — a manifest names endpoints an agent will

subsequently call and the auth it will present, so over a rewritable channel it

is a redirection primitive. The single exception is loopback (localhost

,

127.0.0.0/8

,[::1]

) during local development, where there is no network

attacker. You are inside that exception. Everywhere else, use TLS.

You have just performed agent discovery. A GET to a predictable path. That is

genuinely the whole mechanism.

This is where you find out whether your file is correct instead of hoping.

pip install jsonschema requests

Save as validate.py

:

import json, requests
from jsonschema import Draft202012Validator

SCHEMA_URL = (
    "https://raw.githubusercontent.com/Cheela-Labs/"
    "agent-discovery-spec/v0.3.1/spec/schema/manifest.schema.json"
)

schema = requests.get(SCHEMA_URL).json()
manifest = json.load(open(".well-known/agent-discovery.json"))

errors = sorted(Draft202012Validator(schema).iter_errors(manifest),
                key=lambda e: list(e.path))

if not errors:
    print("✅ Valid manifest")
else:
    for e in errors:
        location = " → ".join(str(p) for p in e.path) or "(root)"
        print(f"❌ {location}: {e.message}")
python3 validate.py

That URL is pinned to a tag on purpose. Pointing a validator at main

means the

thing you validate against can change under you between two runs; pin the

version you are targeting and upgrade deliberately.

There is no official ads-validate

CLI yet. Thirty lines of jsonschema

is the

whole tool, which is roughly the point of keeping the schema small.

An empty manifest is legal but boring. A capability needs three things: name

,

version

, endpoint

.

Replace the file:

{
  "specVersion": "0.3.0",
  "id": "com.example.bookshop",
  "name": "Example Bookshop",
  "description": "Search the catalogue and check stock.",
  "provider": { "name": "Example Inc.", "url": "https://example.com" },
  "capabilities": [
    {
      "name": "com.example.searchBooks",
      "invocationName": "search_books",
      "version": "1.0.0",
      "description": "Search the catalogue by title or author.",
      "inputSchema": {
        "type": "object",
        "properties": { "query": { "type": "string" } },
        "required": ["query"]
      },
      "endpoint": {
        "transport": "http",
        "address": "https://api.example.com/v1/books/search",
        "auth": "none"
      }
    }
  ],
  "discovery": { "cacheTtlSeconds": 3600 }
}
python3 validate.py

✅ Valid manifest

.

Three fields there are worth understanding, because they are the ones people get

wrong.

** name must contain a dot.** It is a reverse-DNS identifier, and the schema

❌ capabilities → 0 → name: 'searchBooks' does not match
   '^[A-Za-z][A-Za-z0-9-]{0,63}(\.[A-Za-z][A-Za-z0-9-]{0,63})+$'

The namespace is what stops your searchBooks

and someone else's searchBooks

from colliding the moment two manifests are merged into one agent's tool list.

** invocationName must not contain a dot.** This is the newest part of the

^[a-zA-Z0-9_-]{1,64}$

. A dot is rejected outright. So a conformant ADS name

invocationName

is the identifier to use where name

cannot be. It is

presentation only — name

remains the sole identity. If you omit it, a client

that needs a constrained identifier must derive one by replacing dots with hyphens, and is now forbidden from truncating to a subset of segments. That

** endpoint.auth is required**, even when it is

"none"

. Delete the line and

❌ capabilities → 0 → endpoint: 'auth' is a required property

Making "no auth" an explicit statement rather than an omission is deliberate — a

missing field is indistinguishable from a forgotten one, and an agent should

never have to guess whether it needs a credential.

Break each of those three on purpose and run the validator. Ninety seconds,

and you will remember the rules for good. Then put them back.

Here is the payoff, and the reason invocationName

exists. This turns a manifest

into a tool list an LLM API will actually accept:

import json

manifest = json.load(open(".well-known/agent-discovery.json"))

def tool_name(cap):
    return cap.get("invocationName") or cap["name"].replace(".", "-")

tools = [
    {
        "name": tool_name(cap),
        "description": cap.get("description", ""),
        "input_schema": cap.get("inputSchema", {"type": "object"}),
    }
    for cap in manifest["capabilities"]
    if cap["endpoint"]["transport"] == "http"      # skip what you can't speak
    and not cap.get("deprecated")
]

print(json.dumps(tools, indent=2))
[
  {
    "name": "search_books",
    "description": "Search the catalogue by title or author.",
    "input_schema": {
      "type": "object",
      "properties": { "query": { "type": "string" } },
      "required": ["query"]
    }
  }
]

That array can be passed straight to a tool-calling API. You went from a URL to a

usable tool list without knowing anything in advance about the system behind it.

Note the transport

filter. The rule is skip what you do not understand, never reject the whole manifest. A client that raises on an unrecognised transport

You have a valid, current, tool-callable manifest. To put it in production:

serve the same document at /.well-known/agent-discovery.json

over TLS, send

Access-Control-Allow-Origin: *

if browser clients should see it, and keep

specVersion

honest when you upgrade.

The most useful thing you can do next is disagree with something here.

ADS is a 0.x draft. It is small, young, MIT-licensed with no CLA, and governed

through a public proposal process modelled on Ethereum's EIPs — which makes it an

unusually good first standards contribution. invocationName

exists because

someone hit the dot problem and wrote it up. Ambiguity in the spec is a

spec-bug issue.

A change to how it works is a

proposal.

Both doors are open, and the second one is less intimidating than it sounds.

If you serve a manifest anywhere public, open an issue and say so. A spec with

one implementer is a design document; the implementations are what make it a

standard.

Cheela is the first production implementer of ADS —

its runtime registry publishes conformant manifests for every registered

runtime. The spec does not depend on it, and nothing in this quickstart used it.

── more in #ai-agents 4 stories · sorted by recency
── more on @cheela labs 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/implement-agent-disc…] indexed:0 read:6min 2026-08-05 ·