{"_id":"@a2a-labs/registry-server","_rev":"5-57d752859081620c17f21e65fbb77678","name":"@a2a-labs/registry-server","dist-tags":{"latest":"0.2.3-deprecated"},"versions":{"0.2.0":{"name":"@a2a-labs/registry-server","version":"0.2.0","keywords":["a2a","a2a-labs","a2a-registry-server","a2a-registry","agent2agent","agent-registry","service-discovery","etcd"],"license":"Apache-2.0","_id":"@a2a-labs/registry-server@0.2.0","maintainers":[{"name":"tsangwailam","email":"info@digicrafts.com.hk"}],"homepage":"https://github.com/a2a-labs/a2a-registry-server#readme","bugs":{"url":"https://github.com/a2a-labs/a2a-registry-server/issues"},"bin":{"a2a-registry":"dist/cli.js"},"dist":{"shasum":"43d374b75340b4b75c120ac3bf73ff0441fb3580","tarball":"https://registry.npmjs.org/@a2a-labs/registry-server/-/registry-server-0.2.0.tgz","fileCount":44,"integrity":"sha512-X+T9l+iqkS+4AuTn6EMazk8f3vcGGo+sHME+tko4hFN1lzGuZrjWKbmTvrp74O8lf3t4WiJxg0FBHPqAdEtxwQ==","signatures":[{"sig":"MEUCIQDGBqXHQDYXL5zPxaem0dHDvn4uumVrpWoBr4tKSYQhkwIgVJtxf/+zNmUlK3oCzeTjAohqcXW9Bb0ddtV2RHfAwWs=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":183636},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=22"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"40e45b912ec9d940094c146cda39c592c16b56e2","scripts":{"cli":"tsx src/cli.ts","dev":"tsx watch src/cli.ts","test":"node --import tsx --test test/*.test.ts","build":"tsc -p tsconfig.json && node -e \"require('node:fs').chmodSync('dist/cli.js', 0o755)\"","check":"tsc -p tsconfig.json --noEmit","start":"node dist/cli.js","test:watch":"node --import tsx --test --watch test/*.test.ts"},"_npmUser":{"name":"tsangwailam","email":"info@digicrafts.com.hk"},"repository":{"url":"git+https://github.com/a2a-labs/a2a-registry-server.git","type":"git"},"_npmVersion":"11.17.0","description":"A lease-based registry and discovery server for A2A agents","directories":{},"_nodeVersion":"26.5.0","dependencies":{"@a2a-js/sdk":"^1.0.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.20.3","typescript":"^5.9.2","@types/node":"^24.0.0"},"_npmOperationalInternal":{"tmp":"tmp/registry-server_0.2.0_1786796799278_0.686194584304554","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@a2a-labs/registry-server","version":"0.2.1","keywords":["a2a","a2a-labs","a2a-registry-server","a2a-registry","agent2agent","agent-registry","service-discovery","etcd"],"license":"Apache-2.0","_id":"@a2a-labs/registry-server@0.2.1","maintainers":[{"name":"tsangwailam","email":"info@digicrafts.com.hk"}],"homepage":"https://github.com/a2a-labs/a2a-registry-server#readme","bugs":{"url":"https://github.com/a2a-labs/a2a-registry-server/issues"},"bin":{"a2a-registry":"dist/cli.js"},"dist":{"shasum":"277d50d714fd5ca70b5320b9626f757abe7ccf98","tarball":"https://registry.npmjs.org/@a2a-labs/registry-server/-/registry-server-0.2.1.tgz","fileCount":44,"integrity":"sha512-GKhQc5lqksi75d/4kkTWAI/NlsQ+XPiA3nOvXdzvCCde3roqB9JfAzmwPrVWQv4ANsIktVeSkZMtEf23bdZJdg==","signatures":[{"sig":"MEUCIFHMxTeYGArLcitmqtpFqTCRkjDVmq5fjgdMIyk0Tu+AAiEAi2bxBrzYTSe0vvhR0mwHgxhznxzYljuZq9+TPVR08vY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":215625},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=22"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"a0fa89b84139eb6d28aa74a9a4334c0b7cab00cb","scripts":{"cli":"tsx src/cli.ts","dev":"tsx watch src/cli.ts","test":"node --import tsx --test test/*.test.ts","build":"tsc -p tsconfig.json && node -e \"require('node:fs').chmodSync('dist/cli.js', 0o755)\"","check":"tsc -p tsconfig.json --noEmit","start":"node dist/cli.js","test:watch":"node --import tsx --test --watch test/*.test.ts"},"_npmUser":{"name":"tsangwailam","email":"info@digicrafts.com.hk"},"repository":{"url":"git+https://github.com/a2a-labs/a2a-registry-server.git","type":"git"},"_npmVersion":"11.17.0","description":"A lease-based registry and discovery server for A2A agents","directories":{},"_nodeVersion":"26.5.0","dependencies":{"@a2a-js/sdk":"^1.0.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.20.3","typescript":"^5.9.2","@types/node":"^24.0.0"},"_npmOperationalInternal":{"tmp":"tmp/registry-server_0.2.1_1787152809487_0.4110243995477094","host":"s3://npm-registry-packages-npm-production"}},"0.2.2":{"name":"@a2a-labs/registry-server","version":"0.2.2","keywords":["a2a","a2a-labs","a2a-registry-server","a2a-registry","agent2agent","agent-registry","service-discovery","etcd"],"license":"Apache-2.0","_id":"@a2a-labs/registry-server@0.2.2","maintainers":[{"name":"tsangwailam","email":"info@digicrafts.com.hk"}],"homepage":"https://github.com/a2a-labs/a2a-registry-server#readme","bugs":{"url":"https://github.com/a2a-labs/a2a-registry-server/issues"},"bin":{"a2a-registry":"dist/cli.js"},"dist":{"shasum":"271af28fc49f78f60cb54d03196e3642176ef340","tarball":"https://registry.npmjs.org/@a2a-labs/registry-server/-/registry-server-0.2.2.tgz","fileCount":44,"integrity":"sha512-okaRd5jzWGlES2XANw5N4K9912943CMnrG8gf2GImAp4SryvKA0k0kQbDDouEcBAc38KgT7GVuQeaTEmXB/dfQ==","signatures":[{"sig":"MEUCIBprPSp04yO24qRbwm5wX6ARkx/gVoVv4VvDaDAl0IF7AiEA0ajspgMF/OhROpBmcbby5x/5OTENuK2SRw0UE3+3CQM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":216184},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=22"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"1e1f3635afc71fda3e3841d04bc21b1c972378ca","scripts":{"cli":"tsx src/cli.ts","dev":"tsx watch src/cli.ts","test":"node --import tsx --test test/*.test.ts","build":"tsc -p tsconfig.json && node -e \"require('node:fs').chmodSync('dist/cli.js', 0o755)\"","check":"tsc -p tsconfig.json --noEmit","start":"node dist/cli.js","test:watch":"node --import tsx --test --watch test/*.test.ts"},"_npmUser":{"name":"tsangwailam","email":"info@digicrafts.com.hk"},"repository":{"url":"git+https://github.com/a2a-labs/a2a-registry-server.git","type":"git"},"_npmVersion":"11.17.0","description":"A lease-based registry and discovery server for A2A agents","directories":{},"_nodeVersion":"26.5.0","dependencies":{"@a2a-js/sdk":"^1.0.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.20.3","typescript":"^5.9.2","@types/node":"^24.0.0"},"_npmOperationalInternal":{"tmp":"tmp/registry-server_0.2.2_1787154103463_0.1638529408094811","host":"s3://npm-registry-packages-npm-production"}},"0.2.3":{"name":"@a2a-labs/registry-server","version":"0.2.3","keywords":["a2a","a2a-labs","a2a-registry-server","a2a-registry","agent2agent","agent-registry","service-discovery","etcd"],"license":"Apache-2.0","_id":"@a2a-labs/registry-server@0.2.3","maintainers":[{"name":"tsangwailam","email":"info@digicrafts.com.hk"}],"homepage":"https://github.com/a2a-labs/a2a-registry-server#readme","bugs":{"url":"https://github.com/a2a-labs/a2a-registry-server/issues"},"bin":{"a2a-registry":"dist/cli.js"},"dist":{"shasum":"d421cce4442564c4bb0c59dc1d2eaeb3a090a081","tarball":"https://registry.npmjs.org/@a2a-labs/registry-server/-/registry-server-0.2.3.tgz","fileCount":48,"integrity":"sha512-CDDno87ZTdXSO+RJSv6Zhb8ck25u3OB5WHwxTPr0IOenInlTN1BD7FS2ld+ifR0pDZ0sqa48hkxVh9yxRrlp3g==","signatures":[{"sig":"MEUCIQCWwVzfIPH9xe0f3le3dxMi+OBfeFYG4h500h6/cAFcvAIgTpkJO/lrUqDmuuueA4aOOD4wgoxekFmLzkHJ3OjKfhI=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":457188},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=22"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"6e95ad9ed34e3b058f02d0bedc33dd9a3bbedeaf","scripts":{"cli":"tsx src/cli.ts","dev":"tsx watch src/cli.ts","test":"node --import tsx --test test/*.test.ts","build":"tsc -p tsconfig.json && node -e \"require('node:fs').chmodSync('dist/cli.js', 0o755)\"","check":"tsc -p tsconfig.json --noEmit","start":"node dist/cli.js","prepack":"npm run build:all && node scripts/prepare-ui-package.mjs","build:ui":"npm --prefix ui run build","postpack":"node scripts/cleanup-ui-package.mjs","build:all":"npm run build:ui && npm run build","test:watch":"node --import tsx --test --watch test/*.test.ts"},"_npmUser":{"name":"tsangwailam","email":"info@digicrafts.com.hk"},"repository":{"url":"git+https://github.com/a2a-labs/a2a-registry-server.git","type":"git"},"_npmVersion":"11.17.0","description":"A lease-based registry and discovery server for A2A agents","directories":{},"_nodeVersion":"26.5.0","dependencies":{"@a2a-js/sdk":"^1.0.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.20.3","typescript":"^5.9.2","@types/node":"^24.0.0"},"_npmOperationalInternal":{"tmp":"tmp/registry-server_0.2.3_1787162535345_0.31375972049030776","host":"s3://npm-registry-packages-npm-production"}},"0.2.3-deprecated":{"name":"@a2a-labs/registry-server","version":"0.2.3-deprecated","description":"A lease-based registry and discovery server for A2A agents","type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","bin":{"a2a-registry":"dist/cli.js"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"engines":{"node":">=22"},"scripts":{"build:ui":"npm --prefix ui run build","build":"tsc -p tsconfig.json && node -e \"require('node:fs').chmodSync('dist/cli.js', 0o755)\"","build:all":"npm run build:ui && npm run build","prepack":"npm run build:all && node scripts/prepare-ui-package.mjs","postpack":"node scripts/cleanup-ui-package.mjs","check":"tsc -p tsconfig.json --noEmit","dev":"tsx watch src/cli.ts","cli":"tsx src/cli.ts","start":"node dist/cli.js","test":"node --import tsx --test test/*.test.ts","test:watch":"node --import tsx --test --watch test/*.test.ts"},"keywords":["a2a","a2a-labs","a2a-registry-server","a2a-registry","agent2agent","agent-registry","service-discovery","etcd"],"license":"Apache-2.0","repository":{"type":"git","url":"git+https://github.com/a2a-labs/a2a-registry-server.git"},"dependencies":{"@a2a-js/sdk":"^1.0.1","pino":"^10.3.1"},"devDependencies":{"@types/node":"^24.0.0","tsx":"^4.20.3","typescript":"^5.9.2"},"publishConfig":{"access":"public"},"gitHead":"6169b64f6799b0ebbbae982c422a92d81cfacbd9","_id":"@a2a-labs/registry-server@0.2.3-deprecated","bugs":{"url":"https://github.com/a2a-labs/a2a-registry-server/issues"},"homepage":"https://github.com/a2a-labs/a2a-registry-server#readme","_nodeVersion":"26.5.0","_npmVersion":"11.17.0","dist":{"integrity":"sha512-5u2Bg/fqB4tOnSwwDHpy8Jg4sedQIWEmSOG5vfiXPHt0dz507g0u71xk8KRzKUOckgaoF2b2xEMdiM6wLpwOcQ==","shasum":"9f28b9dbd1891578a55498d80b53681dc7cb14b5","tarball":"https://registry.npmjs.org/@a2a-labs/registry-server/-/registry-server-0.2.3-deprecated.tgz","fileCount":52,"unpackedSize":461248,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCICZQpaAK78BotNAUTMaUnn0s09cI35s3uvO9no7S1bqqAiA1W8yoDJreRBqE9ur0dqSz31CQFqu6ul4awCmxr72CMA=="}]},"_npmUser":{"name":"tsangwailam","email":"tsangwailam@gmail.com"},"directories":{},"maintainers":[{"name":"tsangwailam","email":"tsangwailam@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/registry-server_0.2.3-deprecated_1787241877551_0.6368819024186954"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-15T12:26:39.076Z","modified":"2026-08-20T16:04:37.924Z","0.2.0":"2026-08-15T12:26:39.409Z","0.2.1":"2026-08-19T15:20:09.655Z","0.2.2":"2026-08-19T15:41:43.615Z","0.2.3":"2026-08-19T18:02:15.513Z","0.2.3-deprecated":"2026-08-20T16:04:37.731Z"},"bugs":{"url":"https://github.com/a2a-labs/a2a-registry-server/issues"},"license":"Apache-2.0","homepage":"https://github.com/a2a-labs/a2a-registry-server#readme","keywords":["a2a","a2a-labs","a2a-registry-server","a2a-registry","agent2agent","agent-registry","service-discovery","etcd"],"repository":{"type":"git","url":"git+https://github.com/a2a-labs/a2a-registry-server.git"},"description":"A lease-based registry and discovery server for A2A agents","maintainers":[{"name":"tsangwailam","email":"tsangwailam@gmail.com"}],"readme":"# A2A Registry Server\n\n> [!WARNING]\n> ### ⚠️ Package Deprecation Notice\n> The `@a2a-labs/registry-server` package has been deprecated and moved to [**`@a2a-lib/registry-server`**](https://www.npmjs.com/package/@a2a-lib/registry-server) due to an organization rename.\n>\n> Please install and use `@a2a-lib/registry-server` instead:\n>\n> ```bash\n> npm install -g @a2a-lib/registry-server\n> # or run via npx\n> npx @a2a-lib/registry-server\n> ```\n\n[![GitHub release](https://img.shields.io/github/v/release/a2a-labs/a2a-registry-server)](https://github.com/a2a-labs/a2a-registry-server/releases/latest)\n[![GitHub stars](https://img.shields.io/github/stars/a2a-labs/a2a-registry-server)](https://github.com/a2a-labs/a2a-registry-server/stargazers)\n[![npm downloads](https://img.shields.io/npm/dm/%40a2a-lib%2Fregistry-server)](https://www.npmjs.com/package/@a2a-lib/registry-server)\n[![CI](https://github.com/a2a-labs/a2a-registry-server/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/a2a-labs/a2a-registry-server/actions/workflows/ci.yml)\n\nA lease-based registration and discovery service for logical AI agents that publish [A2A Agent Cards](https://a2a-protocol.org/latest/specification/). A logical agent can expose multiple independently leased runtime instances that share one Agent Card. The server also provides ownership tokens, TTL heartbeats, filtering, pagination, caching, metrics, tests, and an optional distributed etcd store.\n\nThis project is a registry **for** A2A agents. Its registry REST API is intentionally separate from the A2A task/message protocol. The A2A specification standardizes Agent Cards and describes registries/catalogs as a discovery mechanism, but it does not prescribe one universal registry API.\n\n## Features\n\n- Stores current A2A 1.0 Agent Cards without stripping unknown fields\n- Accepts both v1 `supportedInterfaces[].url` and the legacy card `url`\n- Lease/heartbeat model inspired by etcd and Consul TTL checks\n- Multiple runtime instances per logical agent, with a shared Agent Card\n- Per-instance endpoint, metadata, TTL, lease token, heartbeat, and expiry\n- Optional global write bearer token controls who may create registrations\n- Discovery by skill, skill tag, capability, protocol binding, or agent name\n- Cursor pagination, ETags, readiness/liveness probes, Prometheus text metrics\n- In-memory backend for development and an etcd v3 backend for replicated deployments\n- Compatibility aliases for the routes and `ttlMs` field in the original PoC\n- No web framework; runtime dependencies are the official A2A TypeScript SDK and Pino structured logger\n\n## Quick start\n\nRequirements: Node.js 22 or newer.\n\nInstall the package globally via npm:\n\n```bash\nnpm install -g @a2a-lib/registry-server\n```\n\nStart the registry server using the CLI:\n\n```bash\na2a-registry\n```\n\nAlternatively, run it directly without global installation using `npx`:\n\n```bash\nnpx @a2a-lib/registry-server\n```\n\nThe server starts by default at `http://localhost:3003` using the in-memory store.\n\n### Web dashboard\n\nThe React dashboard is maintained as the `ui` Git submodule and can be served on the same port as the registry API:\n\n```bash\ngit clone --recurse-submodules git@github.com:a2a-labs/a2a-registry-server.git\ncd a2a-registry-server\nnpm ci\nnpm --prefix ui ci\nnpm run build:all\nnode dist/cli.js --ui\n```\n\nWhen working from an existing clone, initialize the dashboard with `git submodule update --init --recursive`. The default build path is `ui/dist`; use `--ui-dir <path>` or `REGISTRY_UI_DIR` for a different static build. If the UI build is missing, dashboard requests return a clear `503` response while API and health endpoints remain available.\n\n### CLI options\n\nThe CLI accepts configuration flags (which take precedence over environment variables) as well as dotenv-compatible files:\n\n```bash\n# Start with explicit host, port, and store\na2a-registry --host 127.0.0.1 --port 3003 --store memory\n\n# Load configuration from a .env file\na2a-registry --env-file .env\n\n# Serve the built web dashboard with the API\na2a-registry --ui\n\n# Emit only warnings and errors\na2a-registry --log-level warn\n\n# Inspect all available options\na2a-registry --help\n```\n\nUse `--help` for all options. `SIGINT` and `SIGTERM` trigger a graceful shutdown that stops accepting connections, waits for active requests, and closes the storage backend.\n\n## Deploying with Docker\n\nYou can build and deploy the registry server as a lightweight container using the included multi-stage `Dockerfile`:\n\n### 1. Build the Docker image\n\n```bash\ndocker build -t a2a-registry-server .\n```\n\n### 2. Run the container\n\n```bash\ndocker run -d \\\n  --name a2a-registry \\\n  -p 3003:3003 \\\n  -e REGISTRY_PORT=3003 \\\n  -e REGISTRY_STORE=memory \\\n  a2a-registry-server\n```\n\nTo enable a write bearer token or configure other settings, pass environment variables with `-e`:\n\n```bash\ndocker run -d \\\n  --name a2a-registry \\\n  -p 3003:3003 \\\n  -e REGISTRY_PORT=3003 \\\n  -e REGISTRY_STORE=memory \\\n  -e REGISTRY_WRITE_TOKEN=my-secret-token \\\n  a2a-registry-server\n```\n\n### 3. Container health check\n\nThe container image includes a built-in healthcheck probing `http://127.0.0.1:3003/health/ready`. You can check container status and logs:\n\n```bash\ndocker ps --filter \"name=a2a-registry\"\ndocker logs a2a-registry\n```\n\n## Registering and discovering agents\n\nRegister an agent:\n\n```bash\ncurl -i http://localhost:3003/v1/agents \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n    \"id\": \"weather-eu-1\",\n    \"ttlSeconds\": 60,\n    \"metadata\": { \"region\": \"eu-west\" },\n    \"agentCard\": {\n      \"name\": \"Weather Agent\",\n      \"description\": \"Returns local forecasts\",\n      \"version\": \"1.0.0\",\n      \"supportedInterfaces\": [{\n        \"url\": \"https://weather.example/a2a\",\n        \"protocolBinding\": \"HTTP+JSON\",\n        \"protocolVersion\": \"1.0\"\n      }],\n      \"capabilities\": { \"streaming\": true },\n      \"defaultInputModes\": [\"text/plain\"],\n      \"defaultOutputModes\": [\"application/json\"],\n      \"skills\": [{\n        \"id\": \"forecast\",\n        \"name\": \"Weather forecast\",\n        \"description\": \"Forecast by location\",\n        \"tags\": [\"weather\"]\n      }]\n    }\n  }'\n```\n\nThe create response contains the server-assigned `instance.instanceId` and a `leaseToken`. Save the lease token securely: it is returned once and is required to update, renew, or remove the registration.\n\n```bash\nexport AGENT_LEASE_TOKEN='<value from registration response>'\n\ncurl -X POST http://localhost:3003/v1/agents/weather-eu-1/heartbeat \\\n  -H \"X-Registry-Lease-Token: $AGENT_LEASE_TOKEN\"\n\ncurl 'http://localhost:3003/v1/agents?skill=forecast&capability=streaming&tag=weather'\n\ncurl -X DELETE http://localhost:3003/v1/agents/weather-eu-1 \\\n  -H \"X-Registry-Lease-Token: $AGENT_LEASE_TOKEN\"\n```\n\nSend a heartbeat well before `ttlSeconds` elapses—normally every one-third of the TTL, with jitter and retry backoff. Registrations that omit `instanceId` receive a unique UUID from the server. The returned `instance.instanceId` can be used with the instance-specific routes; the legacy agent-level heartbeat and unregister routes continue to work for a sole instance or when the lease token identifies the instance.\n\n## Multiple instances\n\nRegister named instances with the same logical agent ID and exactly the same Agent Card. Put the instance-specific URL in `endpoint`; the shared card may advertise a stable load-balancer URL while discovery clients can select from `agent.instances` directly.\n\n```bash\ncurl -i http://localhost:3003/v1/agents/weather/instances \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n    \"instanceId\": \"eu-west-1a\",\n    \"endpoint\": \"https://weather-a.example/a2a\",\n    \"ttlSeconds\": 60,\n    \"metadata\": { \"zone\": \"eu-west-1a\", \"weight\": \"100\" },\n    \"agentCard\": { \"name\": \"Weather Agent\", \"supportedInterfaces\": [{ \"url\": \"https://weather.example/a2a\" }] }\n  }'\n\ncurl -i http://localhost:3003/v1/agents/weather/instances \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n    \"instanceId\": \"eu-west-1b\",\n    \"endpoint\": \"https://weather-b.example/a2a\",\n    \"ttlSeconds\": 60,\n    \"metadata\": { \"zone\": \"eu-west-1b\", \"weight\": \"100\" },\n    \"agentCard\": { \"name\": \"Weather Agent\", \"supportedInterfaces\": [{ \"url\": \"https://weather.example/a2a\" }] }\n  }'\n```\n\nEach response has a different `leaseToken`. Heartbeat an instance at `/v1/agents/{id}/instances/{instanceId}/heartbeat`. Discovery returns one logical agent containing both active records in `instances`; an instance disappears independently when its lease expires. While multiple instances are active, a registration with a different Agent Card is rejected with `409 agent_card_mismatch`.\n\n## API\n\n| Method | Path | Purpose |\n|---|---|---|\n| `POST` | `/v1/agents` | Register an instance (server-generated UUID when `instanceId` is omitted) |\n| `GET` | `/v1/agents` | Discover logical agents and active instances |\n| `GET` | `/v1/agents/{id}` | Fetch one logical agent and its active instances |\n| `POST` | `/v1/agents/{id}/instances` | Register a named instance |\n| `GET` | `/v1/agents/{id}/instances` | List active instances |\n| `PUT` | `/v1/agents/{id}/instances/{instanceId}` | Create or replace a named instance |\n| `GET` | `/v1/agents/{id}/instances/{instanceId}` | Fetch a named instance |\n| `POST` | `/v1/agents/{id}/instances/{instanceId}/heartbeat` | Renew a named instance lease |\n| `DELETE` | `/v1/agents/{id}/instances/{instanceId}` | Unregister a named instance |\n| `PUT` | `/v1/agents/{id}` | Register an instance, generating its ID when omitted |\n| `DELETE` | `/v1/agents/{id}` | Remove the compatibility instance, or the lease-token-owned sole instance |\n| `POST` | `/v1/agents/{id}/heartbeat` | Renew the compatibility instance, or the lease-token-owned sole instance |\n| `GET` | `/health/live` | Process liveness |\n| `GET` | `/health/ready` | Storage readiness |\n| `GET` | `/metrics` | Prometheus text metrics |\n| `GET` | `/openapi.yaml` | OpenAPI 3.1 document |\n\nDiscovery accepts `skill`, `tag`, `capability`, `protocolBinding`, `name`, `limit`, and `cursor`. Pagination and `total` count logical agents, and only logical agents with at least one unexpired instance are returned. The top-level instance fields (`endpoint`, TTL, timestamps, and metadata) remain as a compatibility projection of the first active instance, preferring an explicitly named `default` instance; new clients should use `instances`.\n\nPoC-compatible aliases remain available at `/v1/registry`, `/v1/registry/register`, `/v1/registry/agents`, and `/v1/registry/heartbeat`. They use the new ownership rules.\n\n## Configuration\n\n| Variable | Default | Meaning |\n|---|---:|---|\n| `REGISTRY_HOST` | `0.0.0.0` | Listen address |\n| `REGISTRY_PORT` | `3003` | Listen port |\n| `REGISTRY_PUBLIC_URL` | local port URL | Base URL used in service metadata |\n| `REGISTRY_STORE` | `memory` | `memory` or `etcd` |\n| `REGISTRY_LOG_LEVEL` | `info` | Minimum Pino log level: `fatal`, `error`, `warn`, `info`, `debug`, `trace`, or `silent` |\n| `REGISTRY_DEFAULT_TTL_SECONDS` | `60` | Lease TTL if omitted |\n| `REGISTRY_MIN_TTL_SECONDS` | `10` | Lowest accepted TTL |\n| `REGISTRY_MAX_TTL_SECONDS` | `3600` | Highest accepted TTL |\n| `REGISTRY_WRITE_TOKEN` | unset | If set, registrations require `Authorization: Bearer …` |\n| `REGISTRY_CORS_ORIGIN` | `*` | CORS allow-origin value |\n| `REGISTRY_MAX_BODY_BYTES` | `1048576` | Maximum JSON body size |\n| `REGISTRY_UI` / `REGISTRY_ENABLE_UI` | `false` | Serve the built web dashboard |\n| `REGISTRY_UI_DIR` | package `ui/dist` | Static dashboard build directory |\n| `ETCD_ENDPOINT` | `http://localhost:2379` | etcd v3 JSON gateway |\n| `ETCD_PREFIX` | `/a2a-registry/agents/` | etcd key prefix |\n| `ETCD_USERNAME`, `ETCD_PASSWORD` | unset | etcd authentication credentials |\n| `ETCD_BEARER_TOKEN` | unset | Pre-issued etcd auth token |\n\nOperational logs are emitted as newline-delimited JSON through Pino. The `--log-level`\nCLI option overrides `REGISTRY_LOG_LEVEL`; help and version output remain plain text.\n\n## Distributed deployment with etcd\n\n```bash\ndocker compose up --build\n```\n\nThe etcd adapter grants a lease for each runtime instance and attaches that instance's registry key to it. A heartbeat atomically reattaches the key to a new lease and revokes the previous lease. When an instance stops renewing, etcd removes only that instance key even if the registry process that accepted it has failed. All registry replicas must use the same `ETCD_PREFIX` and cluster.\n\nFor production, enable etcd authentication and TLS, use a dedicated least-privilege role restricted to the registry prefix, and run an odd-sized etcd cluster. The current adapter accepts an HTTPS endpoint but does not yet expose custom CA/client-certificate file settings.\n\n## Security model\n\n- `REGISTRY_WRITE_TOKEN` is an enrollment control; enable it outside trusted development networks.\n- `X-Registry-Lease-Token` proves ownership of one runtime instance. Only its SHA-256 hash is stored.\n- Put TLS and an identity-aware proxy/API gateway in front of the server. A shared write token is not a replacement for OAuth2, workload identity, or mTLS.\n- The server validates structure and URLs but does not fetch an endpoint during registration, avoiding a registration-time SSRF path.\n- Agent Cards are public discovery metadata. Do not place credentials or internal secrets in them.\n- Signed Agent Cards are preserved but signature verification and trust policy are deployment-specific and are not performed yet.\n\n## What to add next\n\n1. **Identity and policy:** OIDC/mTLS identities, tenant namespaces, RBAC, admission policy, and audit events. Bind the authenticated identity to the registered agent ID.\n2. **Trust:** verify A2A Agent Card JWS signatures, restrict `jku` origins, maintain trusted issuers/keys, and record verification status without modifying the signed card.\n3. **Active health checks:** optional HTTP/TCP/gRPC probes and passing/warning/critical states, similar to Consul. Keep active checks separate from agent-driven TTL heartbeats.\n4. **Watch API:** Server-Sent Events or gRPC streaming over storage revisions so clients can update a local resolver without polling. etcd watches or Consul blocking queries are natural backends.\n5. **Locality-aware resolution:** add first-class zone/region/weight fields, health-aware selection, and optional client-side round-robin helpers. Until then these values can be carried in per-instance metadata.\n6. **Consul adapter:** use Consul sessions/TTL checks and KV/catalog metadata when an organization already operates Consul.\n7. **Operations:** OpenTelemetry traces, labeled/rate metrics with bounded cardinality, rate limiting, quotas, backups, chaos tests, and SLO dashboards.\n8. **Governance:** moderation/approval workflows, metadata schemas, retention, version compatibility policy, and a documented response to compromised registrations.\n\n## Development\n\n```bash\nnpm run check\nnpm test\nnpm run build\n```\n\nThe memory store is used in unit/integration tests. Add an etcd container test before changing lease behavior.\n\n## References\n\n- [A2A 1.0 specification](https://a2a-protocol.org/latest/specification/)\n- [A2A discovery guide](https://github.com/a2aproject/A2A/blob/main/docs/topics/agent-discovery.md)\n- [Official TypeScript SDK](https://github.com/a2aproject/a2a-js)\n- [etcd leases and gRPC naming](https://etcd.io/docs/v3.8/dev-guide/grpc_naming/)\n- [Consul health checks](https://developer.hashicorp.com/consul/docs/reference/service/health-check)\n- [Consul blocking queries](https://developer.hashicorp.com/consul/api-docs/features/blocking)\n","readmeFilename":"README.md"}