Agent discovery
Agent-discovery endpoints — OAuth/OIDC discovery, OAuth Protected Resource metadata, auth.md, and DNS-AID records on every build, plus an MCP Server Card when MCP is enabled and a baseUrl is set.
Every vark build publishes a set of always-on discovery endpoints that let AI agents learn
what your site offers and how to use it. These are emitted on every build (no opt-in needed), so a
checker such as isitagentready.com clears with the best-available
information. Each endpoint is populated from your config when you provide it, and otherwise from
clearly-labelled example values derived from your baseUrl.
| Endpoint | What it tells an agent |
|---|---|
/.well-known/oauth-authorization-server |
OAuth 2.1 authorization-server metadata (RFC 8414) |
/.well-known/openid-configuration |
The OpenID Connect flavour of the same metadata |
/.well-known/oauth-protected-resource |
Which authorization server guards this resource (RFC 9728) |
/auth.md |
A human- and agent-readable summary of how to authenticate |
/.well-known/dns-aid/records.json + records.zone |
DNS records for agent-interface discovery |
One more discovery endpoint is opt-in rather than always-on: the MCP Server Card below is
emitted only when mcp: is enabled and a baseUrl is set.
MCP Server Card
/.well-known/mcp/server-card.json advertises the Model Context Protocol server that
vark serve --mcp hosts — its transport endpoint and the read-only documentation
tools it exposes (search_documentation, fetch_document, list_documentation,
get_full_corpus). Unlike the endpoints above, the card is written only when
mcp: true is set alongside a baseUrl. Publishing the card is an opt-in
declaration, not a liveness check: mcp: true is what makes the build write the search/metadata
corpus the MCP tools serve, and the baseUrl makes the advertised endpoint an absolute URL —
whether /mcp is actually served remains vark serve’s own --mcp/--no-mcp
runtime decision.
This is the card this site publishes:
{
"serverInfo": { "name": "Aardvark", "version": "0.4.1" },
"protocolVersion": "2025-06-18",
"endpoint": "https://aardvarkdocs.com/mcp",
"transport": "streamable-http",
"capabilities": { "tools": { "listChanged": false } },
"tools": [
{ "name": "search_documentation", "description": "Full-text search across this documentation site." },
{ "name": "fetch_document", "description": "Fetch the raw Markdown of one documentation page." },
{ "name": "list_documentation", "description": "List every page on this site with its title, path, description, breadcrumb and headings." },
{ "name": "get_full_corpus", "description": "Return the entire documentation site as one Markdown document." }
]
}
serverInfo.name is your site.name (falling back to aardvark-docs), and serverInfo.version is
the Aardvark version that ran the build — 0.4.1 here — so it moves with every upgrade rather than
being something you set. The endpoint is {baseUrl}/mcp. Beyond mcp: true and baseUrl, no
configuration is required — the card reflects the tools the MCP server actually serves. A card you
ship yourself at static/.well-known/mcp/server-card.json wins, and is published with or without
the gate.
OAuth / OIDC discovery
Aardvark publishes OAuth 2.1 / OpenID Connect discovery metadata so an agent knows where to
authenticate. It does not become an OAuth authorization server — these documents only advertise
discovery information. Point them at your real identity provider (for example Cloudflare Access);
left unset, every field is derived from baseUrl as a labelled example endpoint.
oauth:
issuer: https://aardvarkdocs.com
authorizationEndpoint: https://aardvarkdocs.com/oauth/authorize
tokenEndpoint: https://aardvarkdocs.com/oauth/token
jwksUri: https://aardvarkdocs.com/.well-known/jwks.json
registrationEndpoint: https://aardvarkdocs.com/oauth/register
scopesSupported: [openid, profile, email, offline_access]
# Generic OAuth grants plus the two agent-auth profile grants (JWT-bearer assertion exchange and
# the WorkOS claim-ceremony polling grant); these are also the build default when the key is unset.
grantTypesSupported: [authorization_code, refresh_token, client_credentials, urn:ietf:params:oauth:grant-type:jwt-bearer, urn:workos:agent-auth:grant-type:claim]
responseTypesSupported: [code]
oidc: true # also publish /.well-known/openid-configuration (default on)
agentAuth: # how an autonomous agent registers and proves identity
registerUri: https://aardvarkdocs.com/oauth/register
claimUri: https://aardvarkdocs.com/oauth/claim
revocationUri: https://aardvarkdocs.com/oauth/revoke
identityTypesSupported: [identity_assertion, anonymous]
assertionTypesSupported: [urn:ietf:params:oauth:token-type:id-jag, verified_email]
credentialTypesSupported: [access_token]
eventsSupported: [https://schemas.workos.com/events/agent/auth/identity/assertion/revoked]
The build writes /.well-known/oauth-authorization-server (RFC 8414) and, when oauth.oidc is on
(the default), /.well-known/openid-configuration. The discovery check accepts either, so a
plain-OAuth site can set oidc: false to drop the OIDC copy. Both documents include an agent_auth
block describing the agent registration / identity flow, plus
token_endpoint_auth_methods_supported (client_secret_basic, private_key_jwt, none) and
code_challenge_methods_supported (S256); the OIDC document adds userinfo_endpoint
(oauth.userinfoEndpoint, example-derived when unset), subject_types_supported: [public] and
id_token_signing_alg_values_supported: [RS256].
A few more keys are read when you set them: revocationEndpoint and userinfoEndpoint at the top
level; resource, authorizationServers and bearerMethodsSupported for the Protected Resource
document below; and skill, identityEndpoint, claimEndpoint and eventsEndpoint inside
agentAuth.
- Registration and revocation are each one URL. The top-level
registrationEndpoint,agentAuth.registerUriandagentAuth.identityEndpointall resolve to a single shared value (the first one set, in that order), and so dorevocationEndpointandagentAuth.revocationUri. Set whichever you like — every field that names that endpoint stays consistent. A site that needs a distinct Dynamic Client Registration endpoint and agent-identity endpoint should hand-author these documents understatic/.well-known/instead. identityTypesSupportedaccepts onlyidentity_assertionandanonymous. Any other value is dropped with a build warning, so the block never advertises a methodauth.mddoesn’t describe; if nothing recognized remains, the default pair is advertised.- A list-valued key set to
[]behaves like an unset key — the defaults are published, never an empty list.
The agent_auth block
The agent_auth block follows the WorkOS auth.md profile’s
recognized registration flows. identity_types_supported lists the methods the service accepts, and
each gets a nested object: identity_assertion carries the assertion types it accepts (the ID-JAG
urn:ietf:params:oauth:token-type:id-jag and verified_email) plus the issued credential types,
and anonymous carries its credential types. events_supported advertises the upstream revocation
event the registration layer can ingest. Inside agent_auth, the registration / claim / revocation
surface is published in BOTH vocabularies — the readiness scanner’s register_uri / claim_uri /
revocation_uri and the canonical WorkOS identity_endpoint / claim_endpoint / events_endpoint
aliases — so either reader finds what it expects. Revocation is additionally exposed as the standard
RFC 7009 revocation_endpoint at the top level of the authorization-server metadata (a sibling
of agent_auth, not a field inside it):
{
"revocation_endpoint": "https://aardvarkdocs.com/oauth/revoke",
"agent_auth": {
"skill": "https://aardvarkdocs.com/auth.md",
"register_uri": "https://aardvarkdocs.com/oauth/register",
"identity_endpoint": "https://aardvarkdocs.com/oauth/register",
"claim_uri": "https://aardvarkdocs.com/oauth/claim",
"claim_endpoint": "https://aardvarkdocs.com/oauth/claim",
"revocation_uri": "https://aardvarkdocs.com/oauth/revoke",
"events_endpoint": "https://aardvarkdocs.com/agent/event/notify",
"identity_types_supported": ["identity_assertion", "anonymous"],
"identity_assertion": {
"assertion_types_supported": ["urn:ietf:params:oauth:token-type:id-jag", "verified_email"],
"credential_types_supported": ["access_token"]
},
"anonymous": { "credential_types_supported": ["access_token"] },
"events_supported": ["https://schemas.workos.com/events/agent/auth/identity/assertion/revoked"]
}
}
Protected Resource metadata
/.well-known/oauth-protected-resource (RFC 9728) names which authorization server guards the
resource and how to present a token:
{
"resource": "https://aardvarkdocs.com",
"authorization_servers": ["https://aardvarkdocs.com"],
"scopes_supported": ["openid", "profile", "email", "offline_access"],
"bearer_methods_supported": ["header"],
"resource_documentation": "https://aardvarkdocs.com/auth.md"
}
resource and authorization_servers default to baseUrl and the issuer (override them with
oauth.resource / oauth.authorizationServers), and resource_documentation always points at the
site’s own auth.md.
auth.md
/auth.md restates the same facts in Markdown an agent (or a human) can read directly — its # auth.md
heading is what readiness checkers look for. It also embeds the authorization-server metadata,
including the agent_auth block, as a fenced JSON code block (mirroring the canonical WorkOS
AUTH.md), so the agent_auth metadata is parseable from auth.md itself and not only from the
separate well-known document. It is written on every build and served inline as text/plain.
DNS-AID records
DNS-based Agent Interface Discovery lets an agent find your machine interfaces from DNS alone, by
looking up SVCB/HTTPS records under _<service>._agents.<domain>. Configure the services:
dnsAid:
services:
- name: a2a # -> _a2a._agents.<domain>
target: aardvarkdocs.com
alpn: h2
port: 443
params:
path: /.well-known/agent.json
- name: mcp
target: aardvarkdocs.com
alpn: h2
port: 443
params: { path: /mcp }
alpn accepts either a single protocol (alpn: h2) or a list (alpn: [h2, h3]); a list is
rendered as the comma-joined SVCB value alpn="h2,h3". type picks the record type — SVCB (the
default) or HTTPS — and target defaults to your baseUrl host, so both can be left out. params
are rendered in the order you list them, after alpn and port, so the artifact is stable from
build to build. With no dnsAid.services at all, the build emits three clearly-labelled example
services — a2a, mcp and index — so the artifacts are never empty.
<domain> is your baseUrl host. With no baseUrl, or one whose host is an IP address or localhost (a
local preview such as http://localhost:8000, which has no DNS zone), it is the example domain
docs.example.com.
Every build writes a copy-pasteable zone snippet at /.well-known/dns-aid/records.zone and a
machine-readable mirror at /.well-known/dns-aid/records.json:
_a2a._agents.aardvarkdocs.com. 3600 IN SVCB 1 aardvarkdocs.com. alpn="h2" port=443 path="/.well-known/agent.json"
_mcp._agents.aardvarkdocs.com. 3600 IN SVCB 1 aardvarkdocs.com. alpn="h2" port=443 path="/mcp"
This step is manual (and required)
The DNS-AID check validates via DNS-over-HTTPS against your domain’s real authoritative
nameservers (cloudflare-dns.com / dns.google) — not against your web server. That means no
HTTP server, including vark serve, can make the DNS-AID check pass. A static-site generator
cannot operate your DNS or sign DNSSEC for you. To go green you must:
vark build writes /.well-known/dns-aid/records.zone and records.json.records.zone at your DNS provider (Cloudflare DNS,
Route 53, etc.) — paste them into your zone, matching the owner names and SvcParams exactly.https://cloudflare-dns.com/dns-query?name=_mcp._agents.aardvarkdocs.com&type=SVCB.vark build writes /.well-known/dns-aid/records.zone and records.json.records.zone at your DNS provider (Cloudflare DNS,
Route 53, etc.) — paste them into your zone, matching the owner names and SvcParams exactly.https://cloudflare-dns.com/dns-query?name=_mcp._agents.aardvarkdocs.com&type=SVCB.The records.json mirror served over HTTP is provided only for convenience and tooling — it cannot
substitute for publishing the records at DNS.
How they’re served
/auth.md and the .json artifacts are served by the normal static resolver. The extension-less
OAuth/OIDC paths get dedicated vark serve routes (a suffix-less path is treated
as an HTML request by the resolver, so the raw file would otherwise 404). On Cloudflare Pages /
Netlify the generated _headers rules set Content-Type: application/json
(with open CORS) on each. The homepage also advertises the Protected Resource metadata — and, when
the card is emitted, the MCP server card — via an RFC 8288 Link header.