Reference implementation of the Agent Directory (AD) from the IETF Internet-Draft draft-jimenez-agent-directory, currently at revision 01.
An AD is a registry where AI agents announce themselves and clients find them. An agent registers its base URI, the protocols it speaks (A2A, MCP, gRPC, plain HTTP), and a list of capabilities. A client queries the directory by agent name, capability, type, tag, or protocol, gets back the matching agents, and talks to them directly. The directory never sits in the data path. Registrations expire unless the agent refreshes them, the same soft-state model as the CoRE Resource Directory (RFC 9176).
Registration Lookup
Interface Interface
+-------+ | |
| Agent |--- | |
+-------+ --- | |
--|- +------+ |
+-------+ | ----| | | +--------+
| Agent |-------|-----| AD |----|-----| Client |
+-------+ | ----| | | +--------+
--|- +------+ |
+-------+ --- | |
| CT |--- | |
+-------+ | |
A CT is a commissioning tool that registers agents on their behalf.
You need Python 3.11 or newer and uv.
uv sync
uv run agent-directory serve # http://127.0.0.1:8787, state in ./agent-directory.sqliteOr with Docker:
docker build -t agent-directory .
docker run -p 8787:8787 -v ad-data:/data agent-directoryThe landing page at / lists the current registrations and shows curl
commands you can paste. /SKILL.md explains the API to an LLM agent, and
/openapi.json is the OpenAPI description.
AD=http://127.0.0.1:8787
# 1. Find the interfaces
curl $AD/.well-known/ad
# 2. Register an agent. The Bearer token can be any string; it becomes the owner.
curl -i -X POST "$AD/ad/r?agent=weather<=3600" \
-H "Authorization: Bearer my-secret" -H "Content-Type: application/json" \
-d '{"base":"https://api.open-meteo.com","protocols":["http"],
"description":"Open-Meteo weather API",
"capabilities":[{"name":"forecast","type":"tool","tags":["weather"]}]}'
# HTTP/1.1 201 Created
# Location: /ad/r/1
# 3. Look it up, then follow href for the full registration
curl "$AD/ad/l?tag=weather"
curl $AD/ad/r/1
# 4. Refresh before the hour is up, or delete
curl -X POST $AD/ad/r/1 -H "Authorization: Bearer my-secret"
curl -X DELETE $AD/ad/r/1 -H "Authorization: Bearer my-secret"| Method | Path | Auth | What it does |
|---|---|---|---|
GET |
/.well-known/ad |
no | Entry point. Returns the registration path, the lookup URI Template, and max_count |
POST |
/ad/r?agent=<= |
Bearer | Register. 201 for a new name, 200 when the owner replaces its own registration. Location header, empty body |
GET |
/ad/r/{id} |
no | Full registration, with lt and the remaining seconds |
POST |
/ad/r/{id}?lt= |
Bearer | Refresh the lifetime, replace the fields in the body, or both. 204 |
DELETE |
/ad/r/{id} |
Bearer | Remove. 204 |
GET |
/ad/l |
no | Lookup with the filters agent, protocol, cap_name, cap_type, tag, page, count |
Lookup filters are ANDed. agent and cap_name take one trailing * for a
prefix match. When you combine cap_name, cap_type, and tag, one single
capability has to satisfy all of them. Results are summaries with
capabilities reduced to {name, type}; follow href for descriptions, tags,
and schemas. If there are more results, the response carries a
Link: <...>; rel="next" header. Unknown query parameters are ignored.
The lifetime lt is in seconds. Values below the minimum get a 400, values
above the maximum are clamped. Errors use RFC 9457 Problem Details
(application/problem+json) with status 400, 401, 403, 404, 405, 409, 413,
or 500.
Write requests need Authorization: Bearer <token>. The token is opaque to
the directory. It stores the SHA-256 digest as the owner of the registration
and answers 403 to any later write that presents a different token. Registering
a name that someone else holds returns 409. When a registration expires, the
name is free again.
Published revision 01 allows OAuth 2.0 Bearer tokens or mutual TLS here, and the
editor's working copy moves to WIMSE workload identity tokens. This
implementation uses an opaque Bearer token as a stand-in for a verified
identity, and auth.py is the one file to change when a verifier is added.
The comparison is against the editor's working copy, in which the directory
fills the identity field from a verified WIMSE token.
- Authentication uses an opaque Bearer token instead of a verified WIMSE token
(WIT and WPT). Because no token is verified, the directory never fills the
identityfield and rejects registrations that try to set it (400). - Agent names are 1 to 63 characters from
[A-Za-z0-9._-]and start with a letter or digit. The draft allows any Unicode string. The restriction keeps names safe in URLs and shells. - No rate limiting. Put a reverse proxy in front if you need 429s.
- Extra fields inside a capability object, for example an
invoketemplate, are stored and returned as sent. The draft permits this.
Environment variables, all optional:
| Variable | Default | Meaning |
|---|---|---|
AD_HOST, AD_PORT |
127.0.0.1, 8787 |
Listen address. --host and --port override them |
AD_DB_PATH |
agent-directory.sqlite |
SQLite file. --db overrides it |
AD_DEFAULT_LT |
86400 |
Lifetime in seconds when lt is omitted |
AD_MIN_LT, AD_MAX_LT |
60, 604800 |
Accepted lifetime range |
AD_MAX_COUNT |
100 |
Largest page size, advertised in /.well-known/ad |
AD_MAX_BODY_BYTES |
65536 |
Request body limit, 413 above it |
AD_MAX_CAPABILITIES |
100 |
Most capabilities per registration, 400 above it |
AD_CORS_ORIGINS |
* |
Comma-separated allowed origins, or * for any |
AD_LOG_LEVEL |
INFO |
DEBUG, INFO, WARNING, ERROR, or CRITICAL |
uv sync
uv run pytest # runs against an in-memory SQLite store with a fake clock
uv run ruff check . && uv run ruff format --check .
uv run mypyCI runs the same commands on Python 3.11, 3.12, and 3.13, builds the Docker image, and runs the demo's tests.
CONTRIBUTING.md has the conventions for changes and SECURITY.md explains how to report a vulnerability.
src/agent_directory/
app.py HTTP routes (FastAPI)
models.py request bodies (pydantic), stored Registration, response views
store.py SQLite persistence, expiry on read
lookup.py filters and pagination
auth.py bearer token to owner digest
problem.py RFC 9457 responses
config.py AD_* settings
cli.py the agent-directory command
static/ landing page, served at / and /static, and SKILL.md
tests/ pytest suite
demo/ two-agent demo on Cloudflare Workers, see demo/README.md
demo/ has a two-agent demo. Alice finds tools through the directory
at runtime and calls them without any code written for a specific agent. Bob
is a tool agent that registers itself. A browser page shows every HTTP request
between them. The demo runs on Cloudflare Workers and needs a directory
instance it can reach. See demo/README.md.
MIT.