Building a Connector
Audience: developers building a Slack/Teams/Discord/IDE/browser/custom client integration for Mentionable agents.
A Connector is a public integration point. It receives native platform events, normalizes them, and calls agents over a Mentionable Transport. The core safety rule is simple: the Connector may assert only what it has verified, and agents must decide whether they trust that Connector Instance.
Required Pieces
- Native verification. Verify the platform’s inbound request proof before producing any Mentionable message. Examples: Slack request signing, Discord interaction signatures, OAuth ID tokens, SIWE signatures, DKIM/DMARC, or ActivityPub HTTP signatures.
- Agent resolution. Resolve
@agent@domainthrough WebFinger and the AgentCard. Pick REST for ordinary HTTP clients or A2A for JSON-RPC agent-to-agent calls. - Message normalization. Convert the platform event into
NormalizedMessagesemantics or the target Transport’s request shape. - Identity Evidence. Attach
IdentityEvidenceonly for identities the Connector has verified. Cross-boundary evidence should useproof.type:"signed-attestation", be audience-bound, and expire quickly. - Connector Card. Publish the Connector Instance’s public metadata and
signing keys. The v0.1 compatibility route is
https://<connector-host>/.well-known/adapter-card. - PolicyPart handling. Render
consent_required,payment_required,unauthorized, and refusal parts in the native platform UI. Persist state so callback/resumption flows are single-use. - Operator documentation. Tell agents how to add the Connector Instance to
their Trusted Connector Issuer policy. For Slack-specific agent ACL examples,
see
slack-identity-acl-cookbook.md.
Identity Attestations
Connector-issued identity evidence should follow this pattern:
{
"subject": "slack:T123/U456",
"issuer": "mentionable-slack.example",
"method": "urn:mentionable:auth:slack-workspace-member:v0.1",
"assurance": "platform",
"audience": "@agent@example.com",
"issued_at": "2026-05-06T00:00:00.000Z",
"expires_at": "2026-05-06T00:05:00.000Z",
"source": {
"transport": "slack",
"connector": "slack-connector",
"channel": "C123"
},
"claims": {
"profile": {
"display_name": "JC",
"username": "jc",
"avatar": { "url": "https://avatars.slack-edge.com/..." },
"locale": "ko-KR",
"timezone": "Asia/Seoul",
"provider": "slack",
"provider_subject": "slack:T123/U456",
"extensions": {
"slack": {
"team_id": "T123",
"user_id": "U456"
}
}
}
},
"proof": {
"type": "signed-attestation",
"alg": "Ed25519",
"kid": "2026-05",
"canonicalization": "jcs",
"value": "<base64url signature>"
}
}
Do not use an email address, display name, or platform handle as a globally
linked account identifier unless the receiver has an explicit account-linking
policy. Put correlation hints in claims, not in authorization logic.
Sender Profile Facts
Connectors SHOULD populate the common sender.profile shape whenever the
platform exposes safe, user-visible facts. Keep this small and whitelisted:
- Good common fields:
display_name,username,url,avatar.url,locale,timezone,provider,provider_subject. - Good provider extensions: stable platform ids and public-ish workspace facts,
e.g.
extensions.slack.team_id,extensions.slack.user_id,extensions.slack.real_name. - Avoid by default: raw provider profile objects, emails, phone numbers, OAuth tokens, access scopes, private status text, and anything the platform treats as secret or administrator-only.
For Slack, the reference Connector reads users.info and signs a compact
snapshot alongside the workspace-member evidence. Display-name precedence is
display_name_normalized, display_name, real_name_normalized,
real_name, name, then the Slack user id. Avatar precedence is the largest
HTTPS image Slack exposes (image_512, image_192, image_72, …).
When forwarding history, attach profile facts to each HistoricalMessage.sender
when known. Historical sender profile is context for LLM attribution; unless a
fresh, audience-bound signed attestation is attached and verified, it remains
verified:false and must not drive authorization.
Lazy File Capabilities
When the source platform protects file bytes with connector-held credentials,
forward files as lazy descriptors instead of downloading them during message
dispatch. The descriptor should use the normal file part shape with a
connector-hosted bytes_ref.url:
{
"kind": "file",
"mime": "application/pdf",
"name": "report.pdf",
"size_bytes": 12345,
"bytes_ref": {
"kind": "url",
"url": "https://connector.example/api/slack/files/<signed-token>",
"expires_at": "2026-05-06T00:05:00.000Z"
}
}
The URL is a delegated capability, not identity evidence. Scope it to the source workspace/channel/file, the intended agent audience, the current turn, an expiration time, and a byte budget. The Connector should stream bytes only when the agent fetches this URL, and should fail closed when the token is expired, malformed, or out of scope.
Never forward provider credentials or private provider URLs. For Slack, do not
send url_private, url_private_download, thumbnail URLs, or bot/user OAuth
tokens to downstream agents; those URLs require a bearer token and must stay
behind the Connector boundary.
If the downstream Transport can carry typed parts, forward the descriptor as a
normal file part. A2A uses FilePart.file.uri; REST keeps the legacy
multipart user text fallback and MAY add a name="parts" JSON sidecar for
the current turn so receivers that understand Mentionable Part[] preserve
file metadata without downloading bytes.
Trust Policy
Receivers should default to deny-by-default for Connector-issued evidence:
[
{
"issuer": "mentionable-slack.example",
"methods": ["urn:mentionable:auth:slack-workspace-member:v0.1"],
"assurance": ["platform"],
"subject_prefixes": ["slack:"]
}
]
A Connector can be built by anyone, but trust is receiver-local. A valid signature from an unknown Connector proves only that the unknown Connector made the claim.
Security Checklist
- Canonicalize agent audiences as
@<local>@<domain>before signing. - Use short expirations for forwarded attestations; five minutes is the recommended default.
- Rotate Connector signing keys and publish previous keys only for the grace period.
- Never fetch Connector Cards for untrusted issuers as part of signature verification.
- Reject private, loopback, and non-HTTPS Connector Card hosts in production.
- Keep platform tokens and raw OAuth/SIWE credentials out of
claims. - Record replay IDs when the upstream platform gives you one.