WebFinger & Agent Card Guide
Single-line summary: WebFinger is the discovery hub binding @agent@domain to its ActivityPub actor and AgentCard; AgentCard then advertises A2A endpoints, supported extensions, and the agent’s signing key.
See also: architecture-overview, transport-module-guide, identity-auth-guide, building-a-connector, glossary
Spec source: docs/spec/webfinger.md, docs/spec/agent-card.md. Type source: packages/core/src/webfinger.ts, packages/core/src/agent-card.ts.
WebFinger Lookup
GET https://{domain}/.well-known/webfinger?resource=acct:{local}@{domain}
Accept: application/jrd+json
The response is a JRD (JSON Resource Descriptor):
{
"subject": "acct:scheduler@example.com",
"aliases": ["https://example.com/agents/scheduler"],
"links": [
{
"rel": "self",
"type": "application/activity+json",
"href": "https://example.com/ap/scheduler"
},
{
"rel": "https://mentionable.dev/ns/rel/agent-card",
"type": "application/json",
"href": "https://example.com/agents/scheduler/card.json"
},
{
"rel": "http://webfinger.net/rel/profile-page",
"type": "text/html",
"href": "https://example.com/agents/scheduler"
},
{
"rel": "mailto",
"href": "mailto:scheduler@example.com"
}
]
}
Required Links
rel | Required | Purpose |
|---|---|---|
self | When the agent supports ActivityPub | The actor IRI; type: "application/activity+json". |
https://mentionable.dev/ns/rel/agent-card | Always | The Agent Card URL; type: "application/json". |
http://webfinger.net/rel/profile-page | Optional | Human-readable profile; type: "text/html". |
mailto | Optional but conventional | Mail address for the agent. |
Link order in the JRD is normative: self, agent-card, profile-page, mailto. All non-mailto: URLs MUST be HTTPS.
Agent Card Structure
type AgentCard = {
// Identity (required)
name: string
description?: string
url: string // canonical landing URL
preferred_address: string // @scheduler@example.com
// Skills + I/O surface
skills?: Skill[] // each with id, name, input_modes[], output_modes[], examples[]
// A2A section
a2a?: {
endpoint: string // A2A JSON-RPC URL
transport: 'https+json' | 'https+sse' | 'https+jsonrpc'
auth: A2AAuth // none | bearer-jwt | oauth2
capabilities?: {
streaming?: boolean
push_notifications?: boolean
state_transition_history?: boolean
extensions?: AgentExtension[] // declares spec extensions like PolicyPart v0.1
}
}
// Optional ActivityPub section
activitypub?: {
actor_iri: string // matches WebFinger `self` href
inbox: string
outbox?: string
}
// Mentionable-specific section
mentionable?: {
signing_key: { alg: 'Ed25519'; kid: string; public_jwk: JsonWebKey }
previous_keys?: Array<{ alg: string; kid: string; public_jwk: JsonWebKey }>
identity_policy?: IdentityPolicy
}
}
AgentCard.mentionable.signing_key
This Ed25519 key is load-bearing — it gates three sign/verify pairs:
- Agent self-sign attestations. When the agent issues identity evidence about itself or a delegation chain (
urn:mentionable:auth:agent-self-sign:v0.1), it signs over canonical JSON with this key. - Outbound PolicyPart wrapping. Where wire-level integrity is needed, the agent signs the PolicyPart payload.
- Receipt signing. When the agent issues
PolicyResolutionto itself across a callback boundary, this key authenticates the receipt.
Rotation: emit a new signing_key, list the previous one in previous_keys for a grace period (recommended: ≥30 days for cross-origin verifiers caching the card), then drop. Verifiers SHOULD accept any kid listed in either field.
Extensions in agent.a2a.capabilities
type AgentExtension = {
uri: string // unique HTTPS URL
description?: string
required?: boolean
params?: Record<string, unknown>
endpoint?: string // REQUIRED for REST transport extension
}
Well-known extension URIs:
https://mentionable.dev/ns/policy/v0.1— agent emits PolicyPart per policy-part-guide.https://mentionable.dev/ns/transport-rest/v0.1— agent has a REST endpoint atendpoint. See transport-module-guide#rest.https://mentionable.dev/ns/identity/v0.1— agent honorsIdentityEvidenceper identity-auth-guide.https://mentionable.dev/ns/a2a-tool-events/v0.1— agent surfacesToolCallPartover A2A.
Connector Card
The Connector Card is the public discovery document for a Connector Instance (e.g. mentionable-slack.example). v0.1 path: /.well-known/adapter-card.
{
"issuer": "mentionable-slack.example",
"active_keys": [{ "alg": "Ed25519", "kid": "2026-05", "public_jwk": { ... } }],
"previous_keys": [{ "alg": "Ed25519", "kid": "2026-04", "public_jwk": { ... } }],
"callback_urls": {
"consent": "https://mentionable-slack.example/consent",
"payment": "https://mentionable-slack.example/payment/return"
},
"supported_methods": [
"urn:mentionable:auth:slack-workspace-member:v0.1"
]
}
adapter-card is a v0.1 compatibility path. New documentation refers to the concept as “Connector Card” while the wire URL remains /.well-known/adapter-card until a future version replaces it.
Caching
| Resource | Default TTL | Maximum TTL |
|---|---|---|
| WebFinger JRD | 1 hour | 24 hours |
| Agent Card | 1 hour (aggressive caching encouraged) | 24 hours |
| Connector Card | 1 hour | 24 hours |
Agent’s signing_key.public_jwk | 1 hour with kid pinning | 24 hours |
Cache by kid so signing-key rotation drains caches naturally. Receivers that pin a kid they have not seen before SHOULD revalidate the agent card.
basePath
When a Mentionable runtime is mounted under a path prefix (e.g. https://example.com/agents/...), the rule is:
- WebFinger is always at the root:
https://example.com/.well-known/webfinger. Neverhttps://example.com/agents/.well-known/webfinger. - All other transport endpoints (A2A, REST, AP inbox/outbox, agent card URL, profile pages) MAY live under the basePath.
This is mandatory because RFC 7033 mandates the apex path. The @mentionable/server TransportMount helper enforces this automatically — see deployment-patterns.
Common Mistakes
- Mounting WebFinger under
basePath. RFC 7033 violation; many clients won’t find you. - Returning the AP actor at
rel: "self"over plain HTTP. Reject; HTTPS only for non-mailto:links. - Forgetting the
https://mentionable.dev/ns/rel/agent-cardlink. Without it, Mentionable receivers cannot discover the A2A endpoint. - Rotating
signing_keywithout populatingprevious_keys. Cached attestations issued moments ago will fail verification. - Pinning Connector Card hosts as private/loopback in production. Reject; HTTPS public origins only.
- Letting the AgentCard’s
preferred_addressdrift from the WebFingersubject. They MUST match (acct:{local}@{domain}vs@{local}@{domain}).