A small, faithful AID (Agent Identity & Discovery) v2 resolver and client in TypeScript. Given a domain, it answers the one question AID exists to answer:
"Where is the agent, and which protocol should I speak?"
It implements the DNS-first discovery algorithm, the TXT record format, the
.well-known/agent fallback, and PKA endpoint proof (RFC 9421 HTTP Message
Signatures keyed by an RFC 7638 JWK thumbprint) from the
AID v2.0.0 specification.
Zero runtime dependencies — only Node's built-in dns, crypto, url, and
global fetch (Node >= 20).
This is an independent implementation built against the public spec, not the official SDK. For production, see the reference package
@agentcommunity/aid.
A provider publishes a single DNS TXT record at _agent.<domain>:
_agent.example.com. 300 IN TXT "v=aid2;p=mcp;u=https://api.example.com/mcp;a=oauth2_code;s=Example AI Gateway"
A client resolves that record to learn the endpoint (u), protocol (p), auth
hint (a), and optionally an Ed25519 endpoint-proof key (k). Richer protocols
(MCP, A2A, OpenAPI, OAuth, …) take over from there.
| Key | Alias | Required | Meaning |
|---|---|---|---|
version |
v |
yes | Must be aid2 |
uri |
u |
yes | https://, wss://, or local locator (docker:/npx:/pip:) |
proto |
p |
yes | Protocol token (mcp, a2a, openapi, grpc, graphql, websocket, local, zeroconf, ucp) |
auth |
a |
rec. | Auth hint (none,pat,apikey,basic,oauth2_device,oauth2_code,mtls,custom) |
desc |
s |
opt. | Short display text |
docs |
d |
opt. | Absolute https:// docs URL |
dep |
e |
opt. | ISO 8601 UTC deprecation timestamp |
pka |
k |
opt. | Unpadded base64url Ed25519 public key (JWK x, 32 bytes) |
- Consuming the built library (
dist/): Node >= 20 (uses globalfetch,Bufferbase64url, and Ed25519 fromnode:crypto). - Developing (running the
.tssources directly viapnpm test/pnpm aid): Node >= 22.18, which strips TypeScript types natively with no extra tooling.
pnpm install # dev deps only: typescript, @types/node (no build scripts)
pnpm test # run the test suite (node --test, native TS)
pnpm aid -- resolve supabase.agentcommunity.org # run the CLI from sourceTo build the publishable JS + type declarations and run the aid binary:
pnpm build
node dist/cli.js resolve supabase.agentcommunity.orgaid resolve <domain> [--protocol mcp] [--require-pka] [--no-well-known] [--timeout 5000] [--json]
aid parse "<txt record>" [--allow-unknown-proto] [--json]
aid keyid <base64url-k> derive the RFC 7638 keyid (thumbprint)
aid pka <domain> resolve, then verify endpoint proof over HTTPS
Examples:
aid resolve supabase.agentcommunity.org
aid parse "v=aid2;u=https://api.example.com/mcp;p=mcp;a=pat;s=Example"
aid keyid JrQLj5P_89iXES9-vFgrIy29clF9CC_oPPsw3c5D0bs
# -> poqkLGiymh_W0uP6PZFw-dvez3QJT5SolqXBCW38r0Uimport { discover, parse, AidError } from 'aid-resolver';
try {
const { record, ttl, queryName, trustSource } = await discover('supabase.agentcommunity.org');
console.log(record.proto, record.uri, record.desc);
console.log(`via ${queryName} (${trustSource}, ttl ${ttl}s)`);
} catch (e) {
if (e instanceof AidError) console.error(e.code, e.errorCode, e.message);
else throw e;
}
// Parse a raw TXT string without touching DNS:
const rec = parse('v=aid2;u=https://api.example.com/mcp;p=mcp');When a record carries k, the resolver computes the expected keyId
(RFC 7638 thumbprint). To actually prove the endpoint controls the key, run the
nonce-bound handshake:
import { discover, fetchPkaProof } from 'aid-resolver';
const { record } = await discover('example.com', { pkaPolicy: 'require' });
const proof = await fetchPkaProof({ k: record.pka!, uri: record.uri });
console.log(proof.ok ? 'endpoint verified' : `failed: ${proof.reason}`);verifyPkaResponse() is exposed separately as a pure function (no I/O) if you
already hold the response headers — see src/pka.ts.
| Code | Name |
|---|---|
| 1000 | ERR_NO_RECORD |
| 1001 | ERR_INVALID_TXT |
| 1002 | ERR_UNSUPPORTED_PROTO |
| 1003 | ERR_SECURITY |
| 1004 | ERR_DNS_LOOKUP_FAILED |
| 1005 | ERR_FALLBACK_FAILED |
| Spec section | Where |
|---|---|
| §2.1 TXT format, aliases, forbidden keys | src/parse.ts, src/registry.ts |
| §2.3 discovery algorithm, version partition, ambiguity | src/resolve.ts (selectFromCandidates) |
| §2.4 exact-host (no parent walk) | src/resolve.ts (normalizeDomain, single query) |
§3.3 enterprise PKA policy (if-present/require) |
src/resolve.ts (enforcePka) |
| Appendix B PKA handshake (RFC 9421 / 7638) | src/pka.ts |
Appendix C .well-known/agent fallback |
src/resolve.ts (resolveViaWellKnown) |
Implemented: v2 parse + validation, DNS discovery, version preference, ambiguity
detection, exact-host semantics, .well-known fallback with its guardrails
(HTTPS-only, no redirects, JSON content-type, 64 KB cap), PKA key decoding +
thumbprint, and full RFC 9421 signature verification for the fixed aid-pka-v2
component set.
Deliberately out of scope for this small build:
- TXT TTL. Node's stdlib DNS API doesn't expose the record TTL, so DNS hits
report
DNS_TTL_MIN(300s). Swap in a raw DNS library (dns-packet,dns2) to surface the real TTL. - DNSSEC validation (
dnssec=require) and downgrade detection against retained previous state — the option surface is acknowledged but not enforced. - Legacy aid1 PKA (multibase
z…keys +i/kid) — aid1 core fields parse, but the v1 endpoint-proof profile is not implemented. localexecution — the resolver returnsdocker:/npx:/pip:locators but never runs them (the spec requires explicit consent + sandboxing).
MIT