Skip to content

Repository files navigation

Agent Directory

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.

Run it

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.sqlite

Or with Docker:

docker build -t agent-directory .
docker run -p 8787:8787 -v ad-data:/data agent-directory

The 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.

Try it

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&lt=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"

API

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=&lt= 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.

Ownership

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.

Where this implementation departs from the draft

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 identity field 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 invoke template, are stored and returned as sent. The draft permits this.

Configuration

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

Development

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 mypy

CI 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

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.

License

MIT.

About

Agent Directory is a lightweight registry function where AI agents publish and discover their capabilities.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages