Core Types
Branded types and AT Protocol identifiers used throughout the server.
Branded Types
Branded types provide type safety for AT Protocol identifiers, preventing accidental misuse of strings.
DID (Decentralized Identifier)
type DID = string & { readonly __brand: 'DID' };Description: Permanent, unique identifier for users and repositories.
Format: did:plc: followed by base32-encoded identifier
Examples:
const userDid: DID = 'did:plc:abc123xyz789' as DID;
const repoDid: DID = 'did:plc:def456uvw012' as DID;Characteristics:
- Permanent (never changes)
- Globally unique
- Cryptographically verifiable
- Use for internal references
ATURI (AT Protocol URI)
type ATURI = string & { readonly __brand: 'ATURI' };Description: URI for AT Protocol resources (posts, likes, follows, etc.).
Format: at://[DID]/[collection]/[rkey]
Examples:
const postUri: ATURI = 'at://did:plc:abc123/app.bsky.feed.post/xyz789' as ATURI;
const likeUri: ATURI =
'at://did:plc:abc123/app.bsky.feed.like/like123' as ATURI;
const followUri: ATURI =
'at://did:plc:abc123/app.bsky.graph.follow/follow456' as ATURI;Components:
- DID: Repository identifier
- Collection: Record type (e.g.,
app.bsky.feed.post) - Record Key (rkey): Unique record identifier
NSID (Namespaced Identifier)
type NSID = string & { readonly __brand: 'NSID' };Description: Namespaced identifier for Lexicon schemas and collections.
Format: Reverse domain notation with segments
Examples:
const postType: NSID = 'app.bsky.feed.post' as NSID;
const likeType: NSID = 'app.bsky.feed.like' as NSID;
const profileType: NSID = 'app.bsky.actor.profile' as NSID;Common NSIDs:
app.bsky.feed.post- Postsapp.bsky.feed.like- Likesapp.bsky.feed.repost- Repostsapp.bsky.graph.follow- Followsapp.bsky.graph.block- Blocksapp.bsky.actor.profile- Profiles
CID (Content Identifier)
type CID = string & { readonly __brand: 'CID' };Description: Content-addressed identifier using IPFS CID format.
Format: Base32-encoded multihash
Examples:
const postCid: CID = 'bafyreiabc123xyz789...' as CID;
const imageCid: CID = 'bafkreidef456uvw012...' as CID;Characteristics:
- Content-addressed (hash of content)
- Immutable
- Verifiable
- Used for data integrity
AT Protocol Types
IAtpSession
interface IAtpSession {
did: DID;
handle: string;
accessJwt: string;
refreshJwt: string;
active: boolean;
}Description: Authenticated session information.
Fields:
did- User's DIDhandle- User's handleaccessJwt- Access token (2 hour lifetime)refreshJwt- Refresh token (90 day lifetime)active- Whether session is active
IAtpProfile
interface IAtpProfile {
did: DID;
handle: string;
displayName?: string;
description?: string;
avatar?: string;
banner?: string;
followersCount?: number;
followsCount?: number;
postsCount?: number;
}Description: User profile information.
Fields:
did- User's DID (required)handle- User's handle (required)displayName- Display namedescription- Bio textavatar- Avatar image URLbanner- Banner image URLfollowersCount- Number of followersfollowsCount- Number of followspostsCount- Number of posts
IAtpPost
interface IAtpPost {
uri: ATURI;
cid: CID;
author: IAtpProfile;
record: {
text: string;
createdAt: string;
reply?: {
root: { uri: ATURI; cid: CID };
parent: { uri: ATURI; cid: CID };
};
embed?: unknown;
langs?: string[];
labels?: unknown;
tags?: string[];
};
replyCount?: number;
repostCount?: number;
likeCount?: number;
indexedAt: string;
viewer?: {
repost?: ATURI;
like?: ATURI;
};
}Description: Post data structure.
Fields:
uri- Post URIcid- Post CIDauthor- Post author profilerecord- Post record datatext- Post text contentcreatedAt- Creation timestampreply- Reply information (if reply)embed- Embedded contentlangs- Language codestags- Hashtags
replyCount- Number of repliesrepostCount- Number of repostslikeCount- Number of likesindexedAt- Index timestampviewer- Viewer-specific data (when authenticated)
Validators and Type Guards
The server exports a validator and a type guard for each branded type.
The validate* functions throw on invalid input and return the value narrowed to the branded type. The is* functions are non-throwing type guards that return a boolean.
// Throwing validators (cast + runtime validation)
validateDID(value: string): DID; // requires 'did:method:identifier'
validateATURI(value: string): ATURI; // requires 'at://authority/collection/rkey'
validateCID(value: string): CID; // alphanumeric, length >= 10
validateNSID(value: string): NSID; // reverse-DNS, >= 3 segments
// Non-throwing type guards
isDID(value: unknown): value is DID;
isATURI(value: unknown): value is ATURI;
isCID(value: unknown): value is CID;
isNSID(value: unknown): value is NSID;Example:
// Throws if invalid
const did = validateDID('did:plc:abc123xyz789');
// Narrows without throwing
if (isATURI(input)) {
// input is ATURI here
}Usage Examples
Tools are snake_case MCP tools
The MCP tools exposed by this server use snake_case names (get_user_connections, like_post, reply_to_post, ...). The camelCase calls below are illustrative TypeScript using the branded types; they are not the MCP tool names.
Working with DIDs
// A DID is a permanent reference; store it rather than the handle
const userDid: DID = profile.did;Working with AT URIs
// A post is referenced by its AT URI plus CID
const postUri: ATURI = post.uri;
const postCid: CID = post.cid;Working with CIDs
// CIDs are content-addressed and immutable — useful for integrity checks
const cid: CID = post.cid;Best Practices
Type Safety
- Use branded types for all AT Protocol identifiers
- Don't cast strings to branded types without validation
- Implement type guards for runtime validation
- Use TypeScript strict mode
Identifier Storage
- Store DIDs for permanent references
- Store AT URIs for resource references
- Store CIDs for content verification
- Don't rely on handles (they can change)
Validation
- Validate format before casting to branded types
- Check for null/undefined values
- Handle invalid identifiers gracefully
- Log validation errors