A local MCP server for AT Protocol development and debugging.
Communicates via stdio using JSON-RPC 2.0, enabling AI assistants to work with AT Protocol records, identities, and lexicons. Exposes nine tools for computing content identifiers, resolving handles and DIDs, validating lexicon schemas, parsing rich text facets, retrieving records, describing lexicons, validating XRPC requests, and invoking XRPC methods.
create_record_cidvalidate_lexicon_schemaresolve_handle_to_didalice.bsky.social) to its DID identifier.resolve_identityparse_facetsget_recordget_lexiconvalidate_xrpcinvoke_xrpc> Compute the CID for this post record: {"text": "Hello world", "$type": "app.bsky.feed.post", "createdAt": "2024-01-01T00:00:00Z"}
atpmcp - create_record_cid (MCP)
record: {"text":"Hello world","$type":"app.bsky.feed.post","createdAt":"2024-01-01T00:00:00Z"}
bafyreigdbtyg3kz4i3ci4zaym5uphiixi5dwca7ddfop2xlvx3m7rpjzxe
The CID for that post record is:
bafyreigdbtyg3kz4i3ci4zaym5uphiixi5dwca7ddfop2xlvx3m7rpjzxe
> Resolve the handle ngerakines.me to a DID.
atpmcp - resolve_handle_to_did (MCP)
handle: "ngerakines.me"
did:plc:cbkjy5n7bk3ax2wplmtjofq2
The handle ngerakines.me resolves to did:plc:cbkjy5n7bk3ax2wplmtjofq2.
> Get the full DID document for did:plc:cbkjy5n7bk3ax2wplmtjofq2.
atpmcp - resolve_identity (MCP)
did: "did:plc:cbkjy5n7bk3ax2wplmtjofq2"
{"@context": [...], "id": "did:plc:cbkjy5n7bk3ax2wplmtjofq2", ...}
Here's the full DID document for did:plc:cbkjy5n7bk3ax2wplmtjofq2:
- ID: did:plc:cbkjy5n7bk3ax2wplmtjofq2
- Also Known As:
- at://ngerakines.me
- at://nick.gerakines.net
- at://nick.thegem.city
- https://github.com/ngerakines
- https://ngerakines.me/
- dns:ngerakines.me
- Service:
- PDS: https://pds.cauda.cloud (AtprotoPersonalDataServer)
- Verification Method:
- Type: Multikey
- Public Key (multibase): zQ3shXvCK2RyPrSLYQjBEw5CExZkUhJH3n1K2Mb9sC7JbvRMF
> Fetch the record at at://did:plc:cbkjy5n7bk3ax2wplmtjofq2/app.bsky.feed.post/3mgn37sytoc2c.
atpmcp - get_record (MCP)
uri: "at://did:plc:cbkjy5n7bk3ax2wplmtjofq2/app.bsky.feed.post/3mgn37sytoc2c"
{"$type": "app.bsky.feed.post", "createdAt": "2026-03-09T14:34:06.694Z", ...}
Here's the record:
- Type: app.bsky.feed.post
- Created: 2026-03-09T14:34:06.694Z
- Language: English
- Text:
atpmcp is a local MCP server that is part of the atproto-crates project.
It supports the same authentication as the atpxrpc tool and is using the
latest MCP features.
Install with homebrew: brew tap ngerakines/tap && brew install atpmcp
Read more at ngerakines.me/atpmcp.html
- Embed: External link to atpmcp
- Facet: Link facet on "ngerakines.me/atpmcp.html" (bytes 252-277)
> Parse the facets from this text: Hello @alice.bsky.social, check out https://atproto.com #atdev
atpmcp - parse_facets (MCP)
text: "Hello @alice.bsky.social, check out https://atproto.com #atdev"
[{"index": {"byteStart": 6, "byteEnd": 24}, ...}, ...]
Three facets were parsed from the text:
1. Mention (bytes 6-24): @alice.bsky.social resolved to did:plc:vdjlpwlhbnug4fnjodwr3vzh
2. Link (bytes 36-55): https://atproto.com
3. Tag (bytes 56-62): #atdev
> Validate this lexicon schema and tell me if there are any issues: {
"id": "community.lexicon.bookmarks.bookmark",
"defs": { "main": { "key": "tid", "type": "record", ... } },
"$type": "com.atproto.lexicon.schema",
"lexicon": 1
}
atpmcp - validate_lexicon_schema (MCP)
schema: {"id":"community.lexicon.bookmarks.bookmark", ...}
Lexicon schema 'community.lexicon.bookmarks.bookmark' is valid.
The schema is valid. The lexicon community.lexicon.bookmarks.bookmark is well-formed with:
- A proper NSID
- Lexicon version 1
- A record type with tid key
- Required fields subject (URI format) and createdAt (datetime format)
- An optional tags array of strings
> Get the app.bsky.feed.post lexicon schema and draft a post with the text "Hello from atpmcp".
atpmcp - get_lexicon (MCP)
nsid: "app.bsky.feed.post"
{"id": "app.bsky.feed.post", "defs": { ... }}
Based on the lexicon, an app.bsky.feed.post record requires two fields: text
and createdAt. Here's the drafted post record:
{
"$type": "app.bsky.feed.post",
"text": "Hello from atpmcp",
"createdAt": "2026-03-09T00:00:00.000Z",
"langs": ["en"]
}
Key schema details:
- text (required): string, max 3000 bytes / 300 graphemes
- createdAt (required): datetime string (client-declared timestamp)
- langs (optional): language tags, included "en" for clarity
- facets (optional): for mentions, URLs, hashtags
- embed (optional): for images, video, external links, or quote posts
- reply (optional): root + parent strong refs for threading
> Publish this to my PDS using com.atproto.repo.createRecord and invoke_xrpc as ngerakines.me.
atpmcp - invoke_xrpc (MCP)
nsid: "com.atproto.repo.createRecord"
auth_handle: "ngerakines.me"
body: {"repo":"ngerakines.me","collection":"app.bsky.feed.post","record":{...}}
{"uri": "at://did:plc:cbkjy5n7bk3ax2wplmtjofq2/app.bsky.feed.post/3mgniby6dyf2i", ...}
Post published successfully!
- AT-URI: at://did:plc:cbkjy5n7bk3ax2wplmtjofq2/app.bsky.feed.post/3mgniby6dyf2i
- CID: bafyreigrnwyhnc3bftgpuhvonqlirpbikgc4purstawljuwhysusrhymxu
- Validation: valid
> Use the com.atproto.identity.resolveHandle lexicon to validate a request with the
parameter handle set to "ngerakines.me". Then try validating it again with an empty
handle to see how validation catches missing or invalid input.
atpmcp - validate_xrpc (MCP)
nsid: "com.atproto.identity.resolveHandle"
params: {"handle":"ngerakines.me"}
Validation passed for query com.atproto.identity.resolveHandle: params validated successfully.
atpmcp - validate_xrpc (MCP)
nsid: "com.atproto.identity.resolveHandle"
params: {"handle":""}
Error: XRPC validation failed: params: Invalid handle format for '': handle cannot be empty
brew tap ngerakines/tap
brew install atpmcp
Add atpmcp to your Claude Code MCP configuration at ~/.claude/claude_code_config.json:
{
"mcpServers": {
"atpmcp": {
"command": "atpmcp",
"args": []
}
}
}